View.post源码分析

144 阅读4分钟

一、测试获取view尺寸

class MainActivity : BaseLifecycleActivity() {

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // 在 onCreate() 中获取宽高
        Log.e("measure","measure in onCreate: width=${window.decorView.width}, height=${window.decorView.height}")

        // 在 View.post() 回调中获取宽高
        binding.activity.post {
            Log.e("measure","measure in View.post: width=${window.decorView.width}, height=${window.decorView.height}")
        }
    }

    override fun onResume() {
        super.onResume()
        // 在 onResume() 回调中获取宽高
        Log.e("measure","measure in onResume: width=${window.decorView.width}, height=${window.decorView.height}")
    }
}

上面的示例,运行结果是

  • E/measure: measure in onCreate: width=0, height=0
  • E/measure: measure in onResume: width=0, height=0
  • E/measure: measure in View.post: width=1080, height=2340

结论是:在onCreate和onResume中是无法获取到屏幕尺寸的

二、分析View绘制的时间节点

2.1、从ActivityThread#performLaunchActivity开始

*在handleLaunchActivity()会调用到performLaunchActivity*

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    // 获取 ComponentName
    ComponentName component = r.intent.getComponent();
    ...
    // 创建 ContextImpl 对象
    ContextImpl appContext = createBaseContextForActivity(r);
    ...
    // 反射创建 Activity 对象
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ......
    // 创建 Application 对象
    Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    ......      
    // attach 方法中会创建 PhoneWindow 对象
    activity.attach(appContext, this, getInstrumentation(), r.token,
            r.ident, app, r.intent, r.activityInfo, title, r.parent,
            r.embeddedID, r.lastNonConfigurationInstances, config,
            r.referrer, r.voiceInteractor, window, r.configCallback);


    // 执行 onCreate()
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    } else {
        mInstrumentation.callActivityOnCreate(activity, r.state);
    }
    ...
    return activity;
}
  • mInstrumentation.callActivityOnCreate() 方法最终会回调 Activity.onCreate() 方法。
  • 到这里,setContentView() 方法就被执行了。
    • setContentView() 中主要执行两件事情:
      • 创建 DecorView ,然后根据我们传入的布局文件 id 解析 xml,
      • 将得到的 view 塞进 DecorView 中。
  • 注意,到现在,我们得到的只是一个 空壳子 View 树,它并没有被添加到屏幕上,其实也不能添加到屏幕上。
  • 所以,在 onCreate() 回调中获取视图宽高显然是不可取的。

2.2、从ActivityThread#handleResumeActivity

(1)第一步

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
    String reason) {
    ...
    // 1. 回调 onResume
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ···
    // 2. 获取在onCreate中创建好的decorView
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams lp = r.window.getAttributes();
    // 3. 添加 decorView 到 WindowManager
    wm.addView(decor, lp);
    ...
}
  • onResume() 回调中获取 view 的宽高其实和 onCreate() 中没啥区别,都获取不到

(2)第二步

  • wm.addView(decor, lp),最终会调到WindowManagerGlobal.addView()

      public void addView(View view, ViewGroup.LayoutParams params,
          Display display, Window parentWindow) {
          ...
          // 1. 初始化 ViewRootImpl
          root = new ViewRootImpl(view.getContext(), display);
          // 2. 发起绘制并显示到屏幕上
          root.setView(view, wparams, panelParentView);
      }
    
  • 1>在ViewRootImpl的构造函数中:

     public ViewRootImpl(Context context, Display display) {
         ...
         // 1. IWindowSession 代理对象,与 WMS 进行 Binder 通信
         mWindowSession = WindowManagerGlobal.getWindowSession();
         ...
         // 2.
         mWidth = -1;
         mHeight = -1;
         ...
         // 3. 初始化 AttachInfo
         // 记住 mAttachInfo 是在这里被初始化的
         mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                 context);
         ...
         // 4. 初始化 Choreographer,通过 Threadlocal 存储
         mChoreographer = Choreographer.getInstance();
     }
    
    • 初始化mWindowSession,与WMS 进行 Binder 通信
    • 这里能看到宽高还未赋值
    • 初始化 AttachInfo
    • 初始化 Choreographer
  • 2>ViewRootImpl#setView()方法

     // 参数 view 就是 DecorView
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         synchronized (this) {
             if (mView == null) {
                 mView = view;
    
                 // 1. 发起首次绘制
                 requestLayout();
    
                 // 2. Binder 调用 Session.addToDisplay(),将 window 添加到屏幕
                         res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                 getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                                 mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                 mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    
                 // 3. 将 decorView 的 parent 赋值为 ViewRootImpl
                 view.assignParent(this);
             }
         }
     }
     
    

(3)第三步 ViewRootImpl#requestLayout()方法

       public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                // 检查线程
                checkThread();
                mLayoutRequested = true;
                // 重点
                scheduleTraversals();
            }
       }

(4)第四步 -ViewRootImpl.scheduleTraversals()

       void scheduleTraversals() {
         //防止重复调用,一次Vsync信号期间调用多次没什么用
        if (!this.mTraversalScheduled) {
            this.mTraversalScheduled = true;
            // 利用了Handler的同步屏障机制,发送一个同步屏障,保证优先处理异步消息
            this.mTraversalBarrier = this.mHandler.getLooper().getQueue().postSyncBarrier();
            
            //提交一个异步任务mTraversalRunnable
            this.mChoreographer.postCallback(3, this.mTraversalRunnable, (Object)null);
            if (!this.mUnbufferedInputDispatch) {
                this.scheduleConsumeBatchedInput();
            }

            this.notifyRendererOfFramePending();
            this.pokeDrawLockIfNeeded();
        }
    }
  • (1)Choreographer.postCallback() 方法通过 DisplayEventReceiver.nativeScheduleVsync() 方法向系统底层注册了下一次 vsync 信号的监听。

  • (2)当下一次 vsync 来临时,系统会回调其 dispatchVsync() 方法,最终回调 FrameDisplayEventReceiver.onVsync() 方法。

  • (3)FrameDisplayEventReceiver.onVsync() 方法中取出之前提交的 mTraversalRunnable 并执行。这样就完成了一次绘制流程。 (5)第五步

  • mTraversalRunnable 中执行的是 doTraversal() 方法。

      final class TraversalRunnable implements Runnable {
           TraversalRunnable() {
           }
           public void run() {
               //最终还是回调到ViewRootImpl
               ViewRootImpl.this.doTraversal();
           }
       }
    

(6)第六步

  • ViewRootImpl#doTraversal

       void doTraversal() {
           if (this.mTraversalScheduled) {
               //把这个变量重置一下
               this.mTraversalScheduled = false;
               // 移除同步屏障
               this.mHandler.getLooper().getQueue().removeSyncBarrier(this.mTraversalBarrier);
               if (this.mProfile) {
                   Debug.startMethodTracing("ViewAncestor");
               }
               //调用自己的方法处理绘制流程
               this.performTraversals();
               if (this.mProfile) {
                   Debug.stopMethodTracing();
                   this.mProfile = false;
               }
           }
       }
    

(7)第七步

  • ViewRootImpl#performTraversals

      private void performTraversals() { 
           ... 
           // 1. 绑定 Window ,这个地方在下面的3.3分析会用到
           host.dispatchAttachedToWindow(mAttachInfo, 0); 
           mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); 
           getRunQueue().executeActions(mAttachInfo.mHandler); 
    
           // 2. 请求 WMS 计算窗口大小 
           relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); 
    
           // 3. 测量 
           performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
    
           // 4. 布局 
           performLayout(lp, mWidth, mHeight); 
    
           // 5. 绘制 
           performDraw(); 
       }
    

三、View.post()做了什么

3.1、View#post()方法

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        //1. attachInfo 不为空,通过 mHandler 发送
        return attachInfo.mHandler.post(action);
    }

    //2. attachInfo 为空,放入队列中
    getRunQueue().post(action);
    return true;
}
  • attachInfo 是在 ViewRootImpl 的构造函数中初始化的,
  • ViewRootImpl 是在 WindowManagerGlobal.addView() 创建的
  • WindowManagerGlobal.addView() 是在 ActivityThread 的 handleResumeActivity() 中调用的,但是是在 Activity.onResume() 回调之后。
  • 针对“2”处,把 post() 方法要执行的 Runnable 存储在一个队列中,在合适的时机(View 已被测量)拿出来执行

3.2、HandlerActionQueue

  • getRunQueue()获取的Queue就是HandlerActionQueue

  • 官方解释就是:

    • Class used to enqueue pending work from Views when no Handler is attached.
  • 是一个初始容量是 4 的 HandlerAction 数组

      public void executeActions(Handler handler) {
           synchronized (this) {
               final HandlerAction[] actions = mActions;
               for (int i = 0, count = mCount; i < count; i++) {
                   final HandlerAction handlerAction = actions[i];
                   handler.postDelayed(handlerAction.action, handlerAction.delay);
               }
               mActions = null;
               mCount = 0;
           }
       }
    
  • executeActions方法就是在View的dispatchAttachedToWindow中调用

  • 通过传入的 handler 进行任务分发

3.3、View#dispatchAttachedToWindow

  • ViewRootImpl#performTraversals->View#dispatchAttachedToWindow()->HandlerActionQueue#executeActions(info.mHandler)
  • 先调用的 dispatchAttachedToWindow() ,再进行的测量流程
  • performTraversals() 是在主线程消息队列的一次消息处理过程中执行的,而 dispatchAttachedToWindow() 间接调用的 mRunQueue.executeActions() 发送的任务也是通过 Handler发送到主线程消息队列的,那么它的执行就一定在这次的 performTraversals() 方法执行完之后

四、总结

  • 根据ViewRootImpl是否已经创建,View.post()会执行不同的逻辑。
    • 如果ViewRootImpl已创建
      • 即mAttachInfo已经初始化,直接通过Handler发送消息来执行任务。
    • 如果ViewRootImpl未创建
      • 即View尚未开始绘制,会将任务保存为HandlerAction,暂存在队列HandlerActionQueue中,
      • 等到View开始绘制,执行performTraversal()方法时,在dispatchAttachedToWindow()方法中
  • 通过Handler分发HandlerActionQueue中暂存的任务。
  • 简单的时序图总结一下:

View.post.png