View 的 post 消息处理,原理解析(Android Q)
View 的 post 消息处理,原理解析
在开发中,我们经常会使用某个视图组件(View 的实例)的 post 方法或 postDelayed 方法,用于将操作发送给主线程执行,或者延迟执行某项任务。这种方式非常的方便,那么这么高频的操作,你知道它的执行原理吗?
接下来我们来分析它的运行机制。
View 的 post 方法和 postDelayed 方法
首先我们来看 View 的 post 方法和 postDelayed 方法:
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;
}
public boolean postDelayed(Runnable action, long delayMillis) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.postDelayed(action, delayMillis);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().postDelayed(action, delayMillis);
return true;
}
这两个方法的逻辑实际上是大同小异,区别就是是否延迟执行罢了。这里我们以 post 方法为例进行分析。
post 方法处理过程
- 如果 mAttachInfo 不为空,表示该 View 已经添加到视图树了,直接调用 attachInfo.mHandler.post 执行消息即可。关于 attachInfo 和 mHandler 是什么,稍后详细介绍。
- 如果 mAttachInfo 为空,则表示 View还未添加到视图树,这时,调用 getRunQueue().post 方法,将消息暂时存储在 mRunQueue 属性中。
attachInfo 属性介绍
-
mAttachInfo 是控件系统中很重要的对象,它存储了此当前控件树所以贴附的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的 mAttachInfo 变量中。mAttachInfo 中所保存的信息有 WindowSession,窗口的实例(即 mWindow),ViewRootImpl 实例,窗口所属的 Display,窗口的 Surface 以及窗口在屏幕上的位置等等。mAttachInfo 是 View.AttachInfo 类的实例。
-
mAttachInfo 对象是谁创建的呢?其实它是在 ViewRootImpl 对象初始化时创建的,并且在子 View 添加到视图树的时候,传递给子 View 的。(通过 View 的 dispatchAttachedToWindow 方法传递过来的,我们稍后分析)
-
attachInfo.mHandler 保存了 ViewRootImpl 对象的 mHandler 属性的引用。
mRunQueue 属性介绍
post 方法调用到了 getRunQueue() 方法,它返回的是一个 HandlerActionQueue 对象,我们来看:
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
mRunQueue 是一个 HandlerActionQueue 对象,每个 View 对象对应一个,但是会延迟创建。HandlerActionQueue 对象其实只是用于保存消息的队列,在 View 未添加到视图时,临时保存 post 或 postDelayed 发出的消息。
我们来看它的执行方法 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;
}
}
executeActions 方法,遍历所有存储在 HandlerActionQueue 对象中的消息,然后使用参数 handler 来处理消息。
其实过程已经非常清晰了,但是有个疑问,mAttachInfo 是什么时候设置的呢?mRunQueue 中存储的消息,又是什么时候执行的呢?
View 的 mAttachInfo 属性的设置和 mRunQueue 消息的执行
我们来看 dispatchAttachedToWindow 方法:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
……
if (mRunQueue != null) {//如果有需要执行的消息
//通过 AttachInfo 轻易获取 ViewRootImpl 的 mHandler,然后调用 mHandler 的 post 方法执行。
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
……
}
上面的疑问,在这里得到了解答:
-
dispatchAttachedToWindow 执行的时候,会将 ViewRootImpl 对象的 mAttachInfo 属性,传递过来,并赋值给 View 对象的 mAttachInfo 属性。
-
接下来会通过 mRunQueue 判断,是否在 View 添加到视图树之前,向 View 添加了要处理的消息,如果有,则调用 mRunQueue 对象的 executeActions 方法(并且传递参数为 ViewRootImpl 对象的 mHandler 属性)执行消息。executeActions 方法我们在上文已经分析过了,可以回过头看下。
这里我们可能又会有疑问,dispatchAttachedToWindow 通过名字,我们知道,它应该是在 View 添加到窗口时执行的,那么具体是在哪里调用的呢?
dispatchAttachedToWindow 的调用
从前文《View的显示过程原理详解(Android Q)》中,我们知道 performTraversals 方法执行了测量(measure)、布局(layout)、绘制(draw)三大流程。
我来看 ViewRootImpl 的 performTraversals 方法中有一行代码:
host.dispatchAttachedToWindow(mAttachInfo, 0);
从前文逻辑可以知道,这类的 host 其实就是 DecorView 对象,也就是我们的视图树根节点。
DecorView 继承自 ViewGroup,它的 dispatchAttachedToWindow 方法实现,其实是在 ViewGroup 中。
ViewGroup 的 dispatchAttachedToWindow 方法
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 并调用子 View 的 dispatchAttachedToWindow 方法。也就是 View 中的 dispatchAttachedToWindow 方法,源头是从 ViewRootImpl 的 performTraversals 方法中调用的。
总结
本文分析了我们经常使用的 View 的 post 方法或 postDelayed 方法的实现原理,最终其实都是发送给 ViewRootImpl 对象的 mHandler 属性,以在主线程得以执行的。
-
mAttachInfo 是控件系统中很重要的对象,它存储了此当前控件树所以贴附的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的 mAttachInfo 变量中。mAttachInfo 中所保存的信息有 WindowSession,窗口的实例(即 mWindow),ViewRootImpl 实例,窗口所属的 Display,窗口的 Surface 以及窗口在屏幕上的位置等等。mAttachInfo 是 View.AttachInfo 类的实例。
-
mAttachInfo 对象是谁创建的呢?其实它是在 ViewRootImpl 对象初始化时创建的,并且在子 View 添加到视图树的时候,传递给子 View 的。
-
attachInfo.mHandler 保存了 ViewRootImpl 对象的 mHandler 属性的引用。
-
dispatchAttachedToWindow 执行的时候,会将 ViewRootImpl 对象的 mAttachInfo 属性,传递过来,并赋值给 View 对象的 mAttachInfo 属性。
-
接下来会通过 mRunQueue 判断,是否在 View 添加到视图树之前,向 View 添加了要处理的消息,如果有,则调用 mRunQueue 对象的 executeActions 方法(并且传递参数为 ViewRootImpl 对象的 mHandler 属性)执行消息。
-
View 中的 dispatchAttachedToWindow 方法,源头是从 ViewRootImpl 的 performTraversals 方法中调用的。
-
当调用 post/postDelayed 方法时,如果 mAttachInfo 不为空,表示该 View 已经添加到视图树了,直接调用 attachInfo.mHandler.post 执行消息即可。关于 attachInfo 和 mHandler 是什么,稍后详细介绍。如果 mAttachInfo 为空,则表示 View还未添加到视图树,这时,调用 getRunQueue().post 方法,将消息暂时存储在 mRunQueue 属性中,将来在视图添加到 Window 时执行。
-
View 的 post 方法或 postDelayed 方法,最终其实都是发送给 ViewRootImpl 对象的 mHandler 属性,以在主线程得以执行的。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
重要事说三遍,嘿嘿~
本文地址:https://blog.csdn.net/u011578734/article/details/110238869
上一篇: 经历了阿里、腾讯、蚂蚁金服Java技术面试,我总结出了以下几点
下一篇: Lua脚本语言入门笔记