View的绘制流程分析(源码级)

View的绘制流程分析(源码级)

View 在什么时候添加到屏幕(window)上的

结论:从源码分析得知 View Activity 的 onResume() 执行后 才会绘制到屏幕上, 在 (ViewRootImpl)root.setView(view, wparams, panelParentView, userId); 里面的编舞者 把view送到屏幕上

scss 复制代码
ActivityThread.handleResumeActivity
	performResumeActivity
		r.activity.performResume(r.startsNotResumed, reason);
			mInstrumentation.callActivityOnResume(this);
				activity.onResume();
	View decor = r.window.getDecorView();
	WindowManagerImpl.addView(decor, r.window.getAttributes());
		mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId());
			root = new ViewRootImpl(view.getContext(), display);
			mViews.add(view); // DecorView
            mRoots.add(root); // ViewRootImpl
            mParams.add(wparams); // WindowManager.LayoutParams
            root.setView(view, wparams, panelParentView, userId);

WindowManagerImpl、WindowManagerGlobal、ViewRootImpl 职责

WindowManagerImpl : 确定 View 属于哪个屏幕,哪个父窗口 WindowManagerGlobal :管理整个进程 所有的窗口信息 ViewRootImpl:WindowManagerGlobal 的实际操作者,操作自己的窗口(只有一个)

scss 复制代码
ViewRootImpl.setView
	requestLayout();
		checkThread();//确保在当前线程与view创建线程(默认主线程) 是相同的,否则抛出异常
		scheduleTraversals();
			// 插入消息屏障 ,消息链 来了个消息屏障(优先级最高的),消息链就需要开个口子(开口子的地方一般有指定的)让其插队
			mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
			// 编舞者 发送回调方法,这回调方法 运行一次 就会 自动移除
			mChoreographer.postCallback( 
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
				doTraversal()
					performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制
			notifyRendererOfFramePending();
				HardwareRenderer.nNotifyFramePending(mNativeProxy);
            pokeDrawLockIfNeeded()
            	mWindowSession.pokeDrawLock(mWindow);
    res = mWindowSession.addToDisplayAsUser // 把 窗口添加到 WMS 上
    	WindowManagerService.addWindow
    view.assignParent(this); // 指定ViewRootImpl 为父容器

面试题:UI更新只能在主线程执行吗? 答:不是的,根据checkThread() 函数可知,只要在 view的 创建线程下 更新都可以,或者在checkThread之前执行

scss 复制代码
ViewRootImpl 构造方法
	mThread = Thread.currentThread(); //拿到创建它的线程,MainThread(默认)
	// 脏区域:收集哪些地方需要更改的
	//  比如说 TextView 改变了值,这块区域就变为脏区域,
	//  下次绘制的时候就可以更好的确定哪些view变化了,需要进行重新绘制的
	mDirty = new Rect();
	mAttachInfo = new View.AttachInfo() // 保存当前窗口的一些信息

所以说UI更新,可分三种情况进行更新:

  1. 在与View创建的相同的线程里更新
  2. 在checkThread() 之前( 例如onCreate() 、onResume() ),也能在其他线程里更新
  3. 在子线程中,新建 ViewRootImpl 对象

mChoreographer编舞者 把view送到屏幕上

scss 复制代码
//更新UI的消息优先级是最高的,所以用异步消息和消息屏障插入 更新UI消息
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
	doTraversal()
		performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制

// 执行 遍历
performTraversals@ViewRootImpl.java
	// 1、预测量(lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)才会最多 测量三次
	windowSizeMayChange |= measureHierarchy()
		1、设置一个值,进行第一次测量
        2、获取一个状态值
        3、改变大小 baseSize = (baseSize+desiredWindowWidth)/2;
        4、进行第二次测量
        5、如果还不满意,直接给自己的最大值,第三次测量(不确定的)
        如果 windowSizeMayChange = true // 表示还要测量
        
    // 2、布局窗口
	relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
	
	// 3、控件树测量
	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
		mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
			onMeasure    // 必定会调用 setMeasuredDimension() 设置参数
			
	// 4、布局
	performLayout(lp, mWidth, mHeight); 
		host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            onLayout(changed, l, t, r, b);
                child.layout  // ViewGroup 管子view的布局
    
    // 5、绘制
	performDraw();
        draw()
            // 这里的滚动,为了防止核心的控件被遮住而进行的滚动。比如:输入框输入的时候,view向上滚动
            scrollToRectOrFocus(null, false);
            if (isHardwareEnabled()) { // 硬件加速绘制
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else { // 软件绘制
                drawSoftware(...);
                    /*
                     * Draw traversal performs several drawing steps which must be executed
                     * in the appropriate order:
                     *
                     *      1. 绘制背景 Draw the background
                     *      2. If necessary, save the canvas' layers to prepare for fading
                     *      3. Draw view's content
                     *      4. Draw children
                     *      5. If necessary, draw the fading edges and restore layers
                     *      6. Draw decorations (scrollbars for instance)
                     *      7. If necessary, draw the default focus highlight
                     */
                    mView.draw(canvas);
                        // Step 3, draw the content
                        onDraw(canvas);

                        // Step 4, draw the children
                        dispatchDraw(canvas);
            }

padding 与 margin 所占空间区别

如果是View:加上自己的 padding(占用view 本身的空间) 如果是容器:加上孩子的 margin(孩子设置的 margin 占用的是 父容器的空间)

自定义 ViewGroup 是不执行 onDraw 方法

为什么会不执行了?看以下源码分析:

scss 复制代码
// 硬件加速绘制
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);@ViewRootImpl.java
	updateRootDisplayList(view, callbacks);@ThreadedRenderer.java
		updateViewTreeDisplayList(view);
			view.updateDisplayListIfDirty();
				updateDisplayListIfDirty@View.java 
					mView.mPrivateFlags // 根据这个标志位 判断是否跳过draw方法
					if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
						dispatchDraw(canvas);
							child.draw(canvas, this, drawingTime);@ViewGroup.java // 调用 三参数的 draw函数
					} else {
				        draw(canvas);
				    }

结论:因为跳过了单参数的 draw(canvas) 方法,只执行dispatchDraw(canvas)

如何知道系统服务的实际实现类

在 系统服务注册文件 SystemServiceRegistry.java

scss 复制代码
//系统服务注册文件 SystemServiceRegistry.java
registerService(Context.WINDOW_SERVICE, WindowManager.class,
            new CachedServiceFetcher<WindowManager>() {
        @Override
        public WindowManager createService(ContextImpl ctx) {
            return new WindowManagerImpl(ctx); // WindowManagerImpl 实体对象
        }});

Activity.attach
	mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE)(注意了:获取系统服务),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
	mWindowManager = mWindow.getWindowManager(); // 所以这里 得到的是 WindowManagerImpl 实体对象
相关推荐
andrew_121925 分钟前
腾讯 IEG 游戏前沿技术 一面复盘
java·redis·sql·面试
andrew_121927 分钟前
腾讯 IEG 游戏前沿技术 二面复盘
后端·sql·面试
晨春计31 分钟前
【git】
android·linux·git
寻求出路的程序媛33 分钟前
JVM —— 类加载器的分类,双亲委派机制
java·jvm·面试
标标大人1 小时前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言
kay_5452 小时前
YOLOv8改进 | 模块缝合 | C2f 融合SCConv提升检测性能【CVPR2023】
人工智能·python·深度学习·yolo·目标检测·面试·yolov8改进
木鬼与槐2 小时前
MySQL高阶1783-大满贯数量
android·数据库·mysql
iofomo2 小时前
【Abyss】Android 平台应用级系统调用拦截框架
android·开发工具·移动端
gopher95113 小时前
qt相关面试题
开发语言·qt·面试
视觉小鸟4 小时前
【java面试每日五题之基础篇一】(仅个人理解)
java·笔记·面试