本篇将介绍Android中的协程
View.Post
Android源码剖析
使用场景
1 | view.post{ |
众所周知,在子线程中是不能进行UI操作的,那么想要在子线程中进行UI操作,或者在很多场景下,一些操作想要延迟处理,该怎么办呢。答案就是都可以使用Handler来解决,但是说实话写Handler还是有点麻烦,还得注意内存泄漏问题。所以就可以使用View.post()或者View.postDelay()来替代Handler
但是用归用,难道就不好奇View.post为什么可以在子线程中操作UI,为什么可以解决在Activity的OnCreate()里调用View.post()来解决获取View高宽为0的问题。所以接下来将通过查看源码来解答以上两个问题
源码解析
View#
以下所有代码都处于View中
Post
1 | public boolean post(Runnable action) { |
上面不是好奇为什么View.post()可以在子线程中操作UI吗,其实跟我们想的差不多,post内部也是使用Handler来实现的。那么我们继续看看Android是怎么封装的
post里的代码还是蛮少的,我们慢慢来分析:
首先将成员变量
mAttachInfo赋值给attachInfo,再判断attachInfo是否为空(也就是判断mAttachInfo是否为空)1
2
3
4public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
...
}如果
attachInfo不为空的话,那么就直接调用Handler.post()将Runnable传进去1
2
3
4
5
6
7public boolean post(Runnable action) {
...
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
...
}
Runnable就是个接口,实现了Runnable接口的类,称为 线程辅助类;Thread类才是真正的线程类,也就是最终还是想要通过Thread来创建线程并把线程辅助类传入进去
如果
attachInfo为空的话,则执行从队列中获取一个值来执行Post。这里我们先不管这个队列中存储的是什么,是个什么队列,先继续往下看1
2
3
4public boolean post(Runnable action) {
...
getRunQueue().post(action);
}最终返回True,表示执行完成
View.post()里的代码已经看完了,但是又多出了几个新问题,也就是mAttachInfo到底是在哪里创建的,需要什么条件。getRunQueue又是啥,那么也就只能继续往下深入了
mAttachInfo
我们直接在Android Studio使用Ctrl + F来进行查找,在搜索的时候可以使用点小技巧,搜索mAttachInfo = (等号前后都有空格)。这样就可以直接查到两处赋值操作
1 | void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
1 | void dispatchDetachedFromWindow() { |
可以看到,这两处分别对应了赋值和置空,刚好是在对应的一个生命周期内。接下来就继续了解dispatchAttachedToWindow和dispatchDetachedFromWindow分别在什么时候在哪里被调用了
好吧😛,其实可以先不用管它们两个在什么时候被调用了,至少在现在看来,在Activity.onCreate()的时候dispatchAttachedToWindow还是没有被调用的。但是我们在onCreate()里执行View.post里的操作仍然可以保证是在View的高宽计算完毕的,为什么呢?那么原因只可能在另一个return里了getRunQueue.post
getRunQueue
1 | private HandlerActionQueue getRunQueue() { |
这里的getRunQueue就是返回HandlerActionQueue,外部确实就是调用HandlerActionQueue.post,继续深入
HandlerActionQueue#
以下所有代码都处于HandlerActionQueue中
1 | public class HandlerActionQueue { |
GrowingArrayUtils:从名字就可以看出来,是一个可以自动扩展的Array工具类
HandlerActionQueue.post内部则是调用了postDelayed(),postDelayed()内部则是将Runnable和long作为参数创建一个HandlerAction,之后再添加到mActions数组中去,那就顺便看一下HandlerAction
HandlerAction
1 | private static class HandlerAction { |
蛮简单的一个类,主要作用就是把View.post传入的Runnable保存起来,如果是postDelay(),那就也把延迟时间用delay保存起来
现在我们回到上层,梳理一下。我们一开始调用的View.post传入的Runnable,在传到HandlerActionQueue里会包装成HanlderAction,然后再缓存起来。再HanlderActionQueue内部创建了个默认大小为4的HanlderAction数组,当数组不够用的时候,就会通过GrowingArrayUtils.append()来进行扩展。
总结大概流程:
Activity.onCreate –> 调用View.post() –> 还没进行dispatchAttachedToWindow –> HandlerActionQueue保存到数组中
那么什么时候这些存储起来的Runnable来会被执行呢,这又得回到HandlerActionQueue这里了
executeActions
1 | public void executeActions(Handler handler) { |
在HandlerActionQueue里面,有一个executeActions的这么一个方法,就是用来处理HandlerAction的。还要一个主要的地方就是这些被缓存的Runnable也都是被Handler处理的,那么这个Hanlder是哪里来的呢
View#
以下所有代码都处于View中
dispatchAttachedToWindow
1 | void dispatchAttachedToWindow(AttachInfo info, int visibility) { |
从代码中很清晰的就可以看到,只要mRunQueue不为空,在执行dispatchAttachedToWindow的时候就会去运行
再次总结大概流程🎉:
Activity.onCreate –> 调用View.post() –> 还没进行dispatchAttachedToWindow –> 使用HandlerActionQueue将Runnable保存到数组中 –> View.dispatchAttachedToWindow被执行 –> mRunQueue不为空 –> mRunQueue.executeActions()运行被储存的Runnable —>由于View.dispatchAttachedToWindow被执行,mAttachInfo不为空,则剩下的都交给mAttachInfo.mHandler
图片流程:

到这里我们就可以回答一开始提的第二个问题,为什么可以解决在Activity的OnCreate()里调用View.post()来解决获取View高宽为0的问题。答案就是View先会将传入进来的Runnable都缓存起来,等到dispatchAttachedToWindow 执行,在先将缓存起来的Runnable遍历运行,之后的就正常交给mAttachInfo.mHandler。最后我们就只用搞清楚mAttachInfo是在哪里初始化的和什么时候会调用dispatchAttachedToWindow
通过查询,会在ViewRootlmpl.performTraversals()和ViewGroup.addViewInner()里被调用
ViewGroup#
以下所有代码都处于ViewGroup 中
咱们先来看ViewGroup,在ViewGroup的addViewInner()方法内部会调用到dispatchAttachedToWindow
1 | private void addViewInner(View child, int index, LayoutParams params,boolean preventRequestLayout) { |
addViewInner()是ViewGroup在添加View时内部的逻辑,也就是当ViewGroup.addView()的时候,如果mAttachInfo不为空,则会去调用子类的dispatchAttachedToWindow(),并将自身的AttachInfo传入过去。然后子View则会将传递过来的mAttachInfo赋值给自己的AttachInfo
那么ViewGroup的AttachInfo是那来的呢,我们继续深入,最终找到的结果非常Amazed啊!。ViewGroup是继承View的,ViewGroup中的AttachInfo是直接使用View中AttachInfo,可以很牛,直接绕回来了
1 | public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource { |
在ViewGroup内部也有一个dispatchAttachedToWindow
1 |
|
在该方法被调用时,会先调用父类的dispatchAttachedToWindow,也就是调用View.dispatchAttachedToWindow(View中的AttachInfo也是在这个时候被赋值的),接着遍历所有子类,调用子类的dispatchAttachedToWindow,并传入自身的AttachInfo。但是这样一来,我们就直接进入循环了… 所以我们还是去看看ViewRootlmpl,希望那里会有答案
ViewRootlmpl#
以下所有代码都处于ViewRootlmpl中
performTraversals()
1 | private void performTraversals() { |
在上述代码中,performTraversals()这个方法是用来通知Activity的View树来开始测量、布局、绘制。而DecorView就是Activity的根布局,View树的起点,是一个FrameLayout(是一个ViewGroup)。所以就是在这里第一次将mAttachInfo传给了DecorView中的所有子类
可以了,现在只用找到mAttachInfo是在哪里进行的初始化就可以了
1 | public ViewRootImpl(Context context, Display display, IWindowSession session,boolean useSfChoreographer) { |
找到了,在ViewRootImpl的一个构造方法中,创建了一个AttachInfo。所以说ViewRootImpl是AttachInfo的开头,AttachInfo是从这里开始往下传递的
但又有一个小问题出现了,那就是创建AttachInfo中放入的参数mHandler,mHandler是那来的呢?
1 | final ViewRootHandler mHandler = new ViewRootHandler(); |
mHandler是在内部被创建的,又因为ViewRootImpl是在主线程中,所以这里创建的Handler会自动的获取当前线程的Looper,所以也就可以回答一开始所问的问题,为什么可以在子线程中调用View.post来更新ui,因为内部的Handler始终都是在主线程上的
结论
当调用View.post的时候,View内部会先判断是否调用过dispatchAttachedToWindow,也就是mAttachInfo是否为空。为空的话,则会先将传入进来的Runnable通过调用HandlerActionQueue封装成HandlerAction保存在数组中,等待dispatchAttachedToWindow被执行。当dispatchAttachedToWindow被执行的时候,则会先将存储在HandlerActionQueue里的数组中的HandlerAction提取出来运行,之后的Runnable都将直接调用mAttachInfo.post()来运行
关于mAttachInfo的流程:一开始ViewRootlmpl通知Activity根View开始测量、布局、绘制。也就是通知Activity.DevorView去进行测量会调用view.dispatchAttachedToWindow()的方法,将自身的mAttachInfo传给根View。而根View(DevorView)是一个ViewGroup,所以在ViewGroup的dispatchAttachedToWindow又会遍历子View,通过调用子View的dispatchAttachedToWindow将mAttachInfo传递给每个子View