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 实体对象
相关推荐
zhangphil2 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲2 小时前
CentOS7使用源码安装PHP8教程整理
android
Ciderw3 小时前
MySQL为什么使用B+树?B+树和B树的区别
c++·后端·b树·mysql·面试·golang·b+树
翻晒时光3 小时前
深入解析Java集合框架:春招面试要点
java·开发语言·面试
编程、小哥哥3 小时前
python操作mysql
android·python
Couvrir洪荒猛兽4 小时前
Android实训十 数据存储和访问
android
五味香6 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录7 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽8 小时前
Android实训九 数据存储和访问
android
aloneboyooo9 小时前
Android Studio安装配置
android·ide·android studio