Bootstrap

深入理解Android View绘制流程 源码详解

前言

一直都有阅读学习源码的习惯,但是没从来没有想过系统的对其进行一个梳理,每次只是看过了以后就过去了,然后过一段时间,就又会遗忘了,所以打算开始慢慢养成将其源码分析写出来,这样一个可以加深对源码的原理理解,另一个也方便以后进行一个回顾。
这里全文是在Android SDK 29的源码上进行分析理解的,众所周知,Android View的绘制流程有三大步骤 OnMeasure(对控件进行测量设置其宽高),OnLayout(根据对控件测量出来的宽高进行屏幕位置布局摆放),OnDraw(将控件内容绘制在屏幕之上),下面就带大家一步一步从源码分析:

ViewRootImpl 绘制入口分析

入口 — ActivityThread#handleResumeActivity,以下都是一些伪代码

class ActivityThread
{
   
	//绘制三大步骤的流程入口
	@Overrrid 
	public void handleResumeActivity(IBiner token,boolean finalStateRquest ,boolean isForward,boolean reason)
	{
   
		...
		//这里将会执行到Activity的onResume()方法
		final ActivityClientRecord r = performResumeActivity(token,finalStateRequest,reason);
		
		....
		
		 if (r.window == null && !a.mFinished && willBeVisible) 
		 {
   
		 	//获取当前Activity关联的Window窗口类,其真正的对象是一个PhoneWindow
            r.window = r.activity.getWindow();
            //然后获取当前Window的顶级父容器DecorView,这里的DecorView是一个FrameLayout
            View decor = r.window.getDecorView();
            //会先设置DecorView不可见,目前的绘制流程还未开始
            decor.setVisibility(View.INVISIBLE);
            
            //获取ViewManager类型的实例 它只是一个接口,定义了addView,removeView的一些行为
            //那么真的对象是哪一个呢? 
            //答:Activity#getWindowManager()
            // ---> mWindow.getWindowManager(); --Activity#attach()方法中
            // ---> ((WindowManagerImpl)wm).createLocalWindowManager(this); --- Window#setWindowManager方法中
            // --->  new WindowManagerImpl(mContext, parentWindow) ;  --- WindowManagerImpl#createLocalWindowManager方法中
            //故 这里的实际对象是WindowManagerImpl对象 ,感兴趣的可以按照此流程去查阅源码
            ViewManager wm = a.getWindowManager();

            //获取当前Window的LayoutParmas布局参数(该布局参数中包含了当前窗口的宽,高等属性)
            WindowManager.LayoutParams l = r.window.getAttributes();	
            		
			....
			
			 if (a.mVisibleFromClient) 
			 {
   
                if (!a.mWindowAdded) 
                {
   
                	//设置该属性,表示当前窗口已经附加到Activity之上了
                    a.mWindowAdded = true;
                    //将界面顶级布局容器DecoreView 和 Window 的布局参数传递给WindowManager进行添加布局
                    //这里就是的View的绘制流程开始
                    //从前文得知 wm 的真正类型是WindowManagerImpl,所以wm.addView将会执行到 WindowManagerImpl#addView方法中
                    //而WindowManagerImpl#addView --> WindowManagerGlobal#addView
                    // ---> ViewRootImpl#setView
                     wm.addView(decor, l);
                } 
            }
			
         }
	}
}

从上面源码分析可得知真正绘制流程逻辑是发生在WindowManagerGlobal#addView方法中的,然后在该方法中会创建一个ViewRootImpl对象,该对象是连接WindowManager和DecorView的纽带,View的三大流程就是通过ViewRootImpl来完成的。

总结一下,在ActivityThread中,Activity对象被创建完之后,DecorView会和Window进行绑定,然后通过ManagerManager这个中介去调用WindowManagerGlobal 来创建ViewRootImpl,自此ViewRootImpl就会和DecorView建立了关联,同时ViewRootImpl
对象也会被保存到WindowManagerGlobal对象中

public class RootViewImpl implements ViewParent
{
   
	/**
	*
	* @params view Decorview
	* @parmas attrs Window的布局参数
	*/
	 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) 
	 {
   
	 	  synchronized (this) 
	 	  {
   
            if (mView == null) 
            {
   
            	//持有当前传过来的DecorView
                mView = view;
				...
				//这里最终会执行到View的OnMeasure,OnLayout,OnDraw
				requestLayout();
				
				...
				
				//ViewRootImpl 与 DecorView 的绑定
				//该方法参数ViewParent 而ViewRootImpl正是实现了ViewParent接口的,所以这里就将DecorView和ViewRootImpl
				//进行了绑定。正如我们都知道每个Activity的根布局都是DecorView,也就是说实际上View的刷新都是由ViewRootImpl
				//来控制的。即使是界面上一个小小的View发起了重回请求,都要层层走到ViewRootImpl,然后由它发起重绘请求
				//然后再有它来开始遍历View树,一直遍历到这个需要重绘的View再调用它的onDraw方法进行绘制
				view.assignParent(this);
				
				...
            }
	 }
}

总结一下,再打开一个Activity后,当它的onCreate-onStart-onReumse走完了以后,才将它的DecorView与一个新建的ViewRootImpl 对象进行绑定,同时开始安排一次遍历View的任务 也就是View树的操作等待执行,然后再讲DecorView的parent
设置成ViewRootImpl对象,所以这就是为什么在onCreate-onStart-onResume里获取不到View的宽高原因,因为在这个时刻ViewRootImpl甚至都还没开始创建,更不用说是否已经执行过测量操作

public class RootViewImpl implements ViewParent
{
   
	@Override
    public void requestLayout() 
    {
   
        if (!mHandlingLayoutInLayoutRequest)
         {
   
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

 	void scheduleTraversals() 
 	{
   
        if (!mTraversalScheduled) 
        {
   
        	//这里是为了过滤一帧内重复的刷新请求
            mTraversalScheduled = true;
            //这里发送一个同步屏障消息,防止app接收到屏幕刷新信号时,来不及第一时间就去执行刷新屏幕的操作,
            //等到下一帧刷新信号又到来时,就有可能造成丢帧的情况,所以这里采用发送同步屏障消息来防止掉帧
            //具体的实现原理(较为复杂) 后面可能会写一篇关于这里消息处理逻辑
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
			
			//这里将执行 doTraversal逻辑 包装成一个Runnable 等待 onVsync 屏幕刷新信号回调执行
			//这个具体实现原理(较为复杂),同上可能会一起写一篇原理逻辑,所以这里就不做过的解释
			//这里就可以直接理解成当底层发出VSync信号后,doTraversals就会被回调执行
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }
	
	 final class TraversalRunnable implements Runnable {
   
        @Override
        public void run() {
   
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
    void doTraversal()
    {
   
        if (mTraversalScheduled) 
        {
   
        	//这里执行的都是和 scheduleTraversals的 相反的操作
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
       		...
       		//执行刷新逻辑(onMeasure,onLayout,onDraw)
            performTraversals();
			...
       
        }
    }
	
	private void perf

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;