View.post 源码分析

前言

我们知道 Activity 的 onCreate、onStart、onResume 这些回调中通通拿不到 View 的宽高,而通过调用 View.post 在回调中却能拿到宽高这是为什么?来探究下源码看看。

源码分析

View 的 post 方法源码如下

// View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action); // getRunQueue 返回一个 HandlerActionQueue
return true;
}

如果有 mAttachInfo 那么直接使用其内部的 Handler 进行 post,否则就获取 HandlerActionQueue 队列进行 post。先暂时忽略这个 mAttachInfo,看看看 HandlerActionQueue 的 post 方法是如何进行操作的。

public void post(Runnable action) {
postDelayed(action, 0);
}
public void postDelayed(Runnable action, long delayMillis) {
final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
synchronized (this) {
if (mActions == null) {
mActions = new HandlerAction[4];
}
mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
mCount++;
}
}

内部就是简单使用一个数组将 HandlerAction 全部进行缓存。那么这些缓存的 HandlerAction 什么时候拿出来被执行呢?通过查阅源码发现它还有个 executeActions 方法

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;
}
}

内部遍历所有的 HandlerAction 实例,分别调用 handler.postDelayed 方法。那就继续跟,哪里会调用这个 executeActions 方法,查阅源码发现一共有以下两个地方调用了该方法。

  1. ViewRootImpl.performTraversals 方法
  2. View.dispatchAttachedToWindow 方法

由于 View.post 是将当前 HandlerAction 放到当前 View 自己的 HandlerActionQueue 中因此只有可能是下面那个方法调用执行了。

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}

这个方法内部不仅看到了 mAttachInfo 被赋值了,同时也看到调用了 executeActions 去执行了所有带执行的任务。注意到一个细节那就是最终任务都是都是交给 mAttachInfo 中的 Handler 进行执行的,不管调用 post 时到底有没有 mAttachInfo。现在问题来了,谁调用的 View.dispatchAttachedToWindow 方法,查阅源码发现实际是 ViewGroup.dispatchAttachedToWindow 一层层调用下来的,最终其实是在 ViewRootImpl.performTraversals 中被调用的。

// ViewRootImpl.java
public final class ViewRootImpl {
final class ViewRootHandler extends Handler {
public String getMessageName(Message message) {...}
}
public ViewRootImpl(Context context, Display display) {
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
}
private void performTraversals() {
final View host = mView;
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
}

可以看到在第一次执行 performTraversals 时会调用 dispatchAttachedToWindow 让 View 将所有待执行的任务通过 mAttachInfo 中的 ViewRootHandler 发送消息到主线程的消息队列,然后发送完毕后就执行了 View 的三大流程 measure、layout、draw,这三大流程执行完毕后 View 的宽高就都有了,等主线程后面从消息队列拿出消息,执行任务的时候就能取到宽高了。

总结

View 的 post 方法原理是如果 ViewRootImpl 的 performTraversals 已经被执行过一次了(也就是 View 内部的 mAttachInfo 已经存在了),那么直接发送消息到主线程,否则就放入一个队列等待 performTraversals 被首次执行。只要 performTraversals 被执行了,那么后续发送到主线程的消息被取出执行时必然可以获取到 View 的宽高。

0%