在Android Handler:手把手带你深入分析 Handler机制源码中我们说到了在子线程中更新UI的几种方法,其中就有一种是使用
view.post
,那么到底view.post
做了什么呢?我们今天就从源码的角度分析一下(基于Android10的源码,各个版本的源码大同小异)。
为什么view.post
可以更新UI?
- 首先我们看看源码部分:
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; }
- 这里首先判断
attachInfo
是否为null,我们先看为空的情况下,就会执行getRunQueue().post(action)
, getRunQueue返回的是一个HandlerActionQueue
,这里我们看下它的源码:public class HandlerActionQueue { private HandlerAction[] mActions; private int mCount; 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++; } } }
- 我们看到这里其实是将我们post进入的
runnable
包装成HandlerAction
存储起来,并没有真正的执行它。并且原始数组能存储4个runnable,如果需要的话会扩充的。 - 接着我们看,如果不为null就会执行
attachInfo.mHandler.post(action)
, 查看源码很容易得知这里使用的就是Handler机制,也比较容易理解。但是这里的attachInfo
是什么呢?官方解释:A set of information given to a view when it is attached to its parent window
, 意思是当一个view被attach到它的父类window上的时候,被赋予的一系列的信息。这里我们关注的是这个attachInfo
什么时候被赋值,也就是什么时候不为空?查找源码attachInfo
赋值的地方:void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; ... // Transfer all pending runnables. onAttachedToWindow(); }
- 我们发现在
dispatchAttachedToWindow
开始的时候mAttachInfo
被赋值, 然后调用我们常用的onAttachedToWindow()
方法做一些处理工作,那么这个地方又是哪里调用赋值的呢?熟悉View绘制流程的小伙伴就很清楚了,是在ViewRootImpl.performTraversals()
中调用的,ViewRootImpl
是ViewRoot
的实现类,它是连接WindowManager
和DecorView
的桥梁,是通过调用windowManager.addView
方法的时候,紧接着调用了viewRoot.setView(view)
触发了联系的。这里的dispatchAttachedToWindow
经过调查发现只在ViewRootImpl
的performTraversals
中有调用,下面我们看源码部分:private void performTraversals() { final View host = mView; ... host.dispatchAttachedToWindow(mAttachInfo, 0); ... getRunQueue().executeActions(mAttachInfo.mHandler); ... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, mWidth, mHeight); ... performDraw(); }
- 我们看到确实在
performTraversals
中调用了dispatchAttachedToWindow
方法,对mAttachInfo
赋值,然后调用了executeActions
去执行, 我们查看如下源码部分得知,其实走的是Handler机制: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; } }
- 其实到这里我们对
view.post
的源码分析结束了,也解决了最开始提出的问题,为什么view.post可以更新UI?
, 但是对这个方法的深入研究还没有结束。我们知道在Activity.onCreate()
中直接调用view.getWidth
得到的往往是0, 而通过view.post
得到的值却不是0,这又是为何呢?
为什么view.post
可以更新得到UI控件的宽高?
- 想要回答上面的问题,我们就得从上面
ViewRootImpl.performTraversals()
方法说起,源码如下:private void performTraversals() { final View host = mView; ... host.dispatchAttachedToWindow(mAttachInfo, 0); ... getRunQueue().executeActions(mAttachInfo.mHandler); ... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, mWidth, mHeight); ... performDraw(); }
- 我们知道Handler机制Android Handler:手把手带你深入分析 Handler机制源码中的
MessageQueue
是以一种队列的形式来存取的,所以先加入队列中的任务先执行,而且这里executeActions(mAttachInfo.mHandler);
中的handler其实指的是主线程的handler,这个mAttachInfo
是从哪里赋值的吗?我们在ViewRootImpl
的构造方法中看到源码如下:final ViewRootHandler mHandler = new ViewRootHandler(); public ViewRootImpl(Context context, Display display) { mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context) }
- 我们看到这里的handler实际是
ViewRootHandler
, 所以实际的代码是在主线程执行的。 - 从上面
performTraversals
的源码中看到是先执行了executeActions(mAttachInfo.mHandler);
,然后依次执行了performMeasure
,performLayout
,performDraw
方法,我们知道executeActions
其实就是去执行我们post的runnable, 所以会给人一个感觉就是先执行runnable,然后执行的performMeasure
,这样理解当然是不对的,如果真是这样的话获取控件的宽高肯定是0。所以实际是什么样呢 ? - 其实
performTraversals()
方法也是使用handler.post执行的runnable中的一个方法,源码如下:final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { ... performTraversals(); ... } }
performTraversals
中的executeActions(mAttachInfo.mHandler);
,其实是将要执行的runnable再一次的入队列,源码如下: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; } }
- 所以实际上通过
view.post(runnable)
执行的runnable实际是在performMeasure
,performLayout
,performDraw
之后执行的,所以才能够获取到真正的view的宽高。