从view.post再看消息处理
大家都知道view.post可以在主线程执行一段Runnable,并且相比自己定义Handler 而言,更加简洁方便。然而这个方法跟handler.post 有怎样的区别。 本文从源码角度简单探究一下两种方式的实现细节。
view.post 实现
该方法的实现非常简单
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;
}
可以看到第一个逻辑分支,仍然是通过handler进行消息分发。 而第二个分之是通过一个RunQueue进行的分发动作。
第一个逻辑分支相对简易,是直接通过该view 获取的attachInfo中的Handler进行处理。而这个attachinfo 是在
void dispatchAttachedToWindow(AttachInfo info, int visibility)
方法中进行赋值的,只有当该方法被调用后,才会执行第一个逻辑分支。
再看第二个逻辑分支。
RunQueue 对应的事实上是 HandlerActionQueue 类的一个对象。 HandlerActionQueue 的post方法中会对将要发送的Runnable进行缓存,直到executeAction(Handler handler)方法被外部调用,则会依次将待处理的Runnable 添加到入参的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;
}
}
那么这个方法是什么时候被调用的呢。一共有两个地方
- 第一是在ViewRootImpl的performTraversal方法中,传入mAttachInfo的handler执行。
- 第二是在View 的dispatchAttachedToWindow方法中,也是通过attachInfo的handler来执行缓存的Runnable 方法
熟悉 ViewRootImpl 的同学知道,这是整个视窗的根视图,并且在每一帧渲染时,它的performTravsal会被调用用以整个页面的布局。然而我们实际使用中,往往是通过一个顶层view进行post动作,因而更多地是出发第二的方法。
而对于第二个方法,我们又和第一个逻辑的判断条件分支殊途同归了。
至此,我们能够确定的是 dispatchAttachedToWindow 执行后,view.post 的Runnable才能够被执行。
dispatchAttachedToWindow
dispatchAttachedToWindow 是Android视图渲染的关键方法。感兴趣的同学可以查阅相关资料,这里我们只是简述一下它的执行机制。
我们知道视图的根实际上是一个ViewGroup, 它的dispatchAttachedToWindow方法为
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
combineVisibility(visibility, child.getVisibility()));
}
final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
View view = mTransientViews.get(i);
view.dispatchAttachedToWindow(info,
combineVisibility(visibility, view.getVisibility()));
}
}
即,遍历所有子view 一次分发attachToWindow这一个函数通知,使得子view一次执行。在View框架整个调用过程 ,如果是非叶节点,首先调用父类的dispatchAttachedToWindow,然后调用子节点的dispatchAttachedToWindow;如果是叶节点,则会调用View的dispatchAttachedToWindow,整个调用过程可以看成树的先序遍历。而在ViewRootImpl中,只有当mFirst == true时,也就是第一次布局时,才会调用根的dispathAttachedToWindow方法。也就印证了这个函数名,当视图第一次被添加到视窗时执行。
综上, 调用view.post方法,如果对应的view 并没有被添加到视窗(i.e. 第一次performTraversal 没有被执行),则该Runnable 会被缓存至视图添加完后,再被添加到MainLooper的消息队列的对应位置执行。 对比直接使用Handler 进行post 的动作,更加适合操作一些视图元素。