从view.post再看消息处理

从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;
        }
    }

那么这个方法是什么时候被调用的呢。一共有两个地方

  1. 第一是在ViewRootImpl的performTraversal方法中,传入mAttachInfo的handler执行。
  2. 第二是在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 的动作,更加适合操作一些视图元素。

你可能感兴趣的:(从view.post再看消息处理)