前言
我们知道 Activity 的 onCreate、onStart、onResume 这些回调中通通拿不到 View 的宽高,而通过调用 View.post 在回调中却能拿到宽高这是为什么?来探究下源码看看。
源码分析
View 的 post 方法源码如下
// View.java |
如果有 mAttachInfo 那么直接使用其内部的 Handler 进行 post,否则就获取 HandlerActionQueue 队列进行 post。先暂时忽略这个 mAttachInfo,看看看 HandlerActionQueue 的 post 方法是如何进行操作的。
public void post(Runnable action) { |
内部就是简单使用一个数组将 HandlerAction 全部进行缓存。那么这些缓存的 HandlerAction 什么时候拿出来被执行呢?通过查阅源码发现它还有个 executeActions 方法
public void executeActions(Handler handler) { |
内部遍历所有的 HandlerAction 实例,分别调用 handler.postDelayed 方法。那就继续跟,哪里会调用这个 executeActions 方法,查阅源码发现一共有以下两个地方调用了该方法。
- ViewRootImpl.performTraversals 方法
- View.dispatchAttachedToWindow 方法
由于 View.post 是将当前 HandlerAction 放到当前 View 自己的 HandlerActionQueue 中因此只有可能是下面那个方法调用执行了。
void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
这个方法内部不仅看到了 mAttachInfo 被赋值了,同时也看到调用了 executeActions 去执行了所有带执行的任务。注意到一个细节那就是最终任务都是都是交给 mAttachInfo 中的 Handler 进行执行的,不管调用 post 时到底有没有 mAttachInfo。现在问题来了,谁调用的 View.dispatchAttachedToWindow 方法,查阅源码发现实际是 ViewGroup.dispatchAttachedToWindow 一层层调用下来的,最终其实是在 ViewRootImpl.performTraversals 中被调用的。
// ViewRootImpl.java |
可以看到在第一次执行 performTraversals 时会调用 dispatchAttachedToWindow 让 View 将所有待执行的任务通过 mAttachInfo 中的 ViewRootHandler 发送消息到主线程的消息队列,然后发送完毕后就执行了 View 的三大流程 measure、layout、draw,这三大流程执行完毕后 View 的宽高就都有了,等主线程后面从消息队列拿出消息,执行任务的时候就能取到宽高了。
总结
View 的 post 方法原理是如果 ViewRootImpl 的 performTraversals 已经被执行过一次了(也就是 View 内部的 mAttachInfo 已经存在了),那么直接发送消息到主线程,否则就放入一个队列等待 performTraversals 被首次执行。只要 performTraversals 被执行了,那么后续发送到主线程的消息被取出执行时必然可以获取到 View 的宽高。