本篇将介绍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