欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

View 的 post 消息处理,原理解析(Android Q)

程序员文章站 2022-04-20 08:03:38
View 的 post 消息处理,原理解析在开发中,我们经常会使用某个视图组件(View 的实例)的 post 方法或 postDelayed 方法,用于将操作发送给主线程执行,或者延迟执行某项任务。这种方式非常的方便,那么这么高频的操作,你知道它的执行原理吗?接下来我们来分析它的运行机制。View 的 post 方法和 postDelayed 方法首先我们来看 View 的 post 方法和 postDelayed 方法: public boolean post(Runnable acti...

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 方法处理过程

  1. 如果 mAttachInfo 不为空,表示该 View 已经添加到视图树了,直接调用 attachInfo.mHandler.post 执行消息即可。关于 attachInfo 和 mHandler 是什么,稍后详细介绍。
  2. 如果 mAttachInfo 为空,则表示 View还未添加到视图树,这时,调用 getRunQueue().post 方法,将消息暂时存储在 mRunQueue 属性中。

attachInfo 属性介绍

  1. mAttachInfo 是控件系统中很重要的对象,它存储了此当前控件树所以贴附的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的 mAttachInfo 变量中。mAttachInfo 中所保存的信息有 WindowSession,窗口的实例(即 mWindow),ViewRootImpl 实例,窗口所属的 Display,窗口的 Surface 以及窗口在屏幕上的位置等等。mAttachInfo 是 View.AttachInfo 类的实例。

  2. mAttachInfo 对象是谁创建的呢?其实它是在 ViewRootImpl 对象初始化时创建的,并且在子 View 添加到视图树的时候,传递给子 View 的。(通过 View 的 dispatchAttachedToWindow 方法传递过来的,我们稍后分析)

  3. 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;
        }
        ……
    }

上面的疑问,在这里得到了解答:

  1. dispatchAttachedToWindow 执行的时候,会将 ViewRootImpl 对象的 mAttachInfo 属性,传递过来,并赋值给 View 对象的 mAttachInfo 属性。

  2. 接下来会通过 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 属性,以在主线程得以执行的。

  1. mAttachInfo 是控件系统中很重要的对象,它存储了此当前控件树所以贴附的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的 mAttachInfo 变量中。mAttachInfo 中所保存的信息有 WindowSession,窗口的实例(即 mWindow),ViewRootImpl 实例,窗口所属的 Display,窗口的 Surface 以及窗口在屏幕上的位置等等。mAttachInfo 是 View.AttachInfo 类的实例。

  2. mAttachInfo 对象是谁创建的呢?其实它是在 ViewRootImpl 对象初始化时创建的,并且在子 View 添加到视图树的时候,传递给子 View 的。

  3. attachInfo.mHandler 保存了 ViewRootImpl 对象的 mHandler 属性的引用。

  4. dispatchAttachedToWindow 执行的时候,会将 ViewRootImpl 对象的 mAttachInfo 属性,传递过来,并赋值给 View 对象的 mAttachInfo 属性。

  5. 接下来会通过 mRunQueue 判断,是否在 View 添加到视图树之前,向 View 添加了要处理的消息,如果有,则调用 mRunQueue 对象的 executeActions 方法(并且传递参数为 ViewRootImpl 对象的 mHandler 属性)执行消息。

  6. View 中的 dispatchAttachedToWindow 方法,源头是从 ViewRootImpl 的 performTraversals 方法中调用的。

  7. 当调用 post/postDelayed 方法时,如果 mAttachInfo 不为空,表示该 View 已经添加到视图树了,直接调用 attachInfo.mHandler.post 执行消息即可。关于 attachInfo 和 mHandler 是什么,稍后详细介绍。如果 mAttachInfo 为空,则表示 View还未添加到视图树,这时,调用 getRunQueue().post 方法,将消息暂时存储在 mRunQueue 属性中,将来在视图添加到 Window 时执行。

  8. View 的 post 方法或 postDelayed 方法,最终其实都是发送给 ViewRootImpl 对象的 mHandler 属性,以在主线程得以执行的。


PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。
PS:更多分析文章,请查看系列文章–>《Android底层原理解析》专栏。

重要事说三遍,嘿嘿~

本文地址:https://blog.csdn.net/u011578734/article/details/110238869