美文网首页Android知识程序员
从view.post再看消息处理

从view.post再看消息处理

作者: Zcclucky | 来源:发表于2018-03-01 11:21 被阅读27次

    从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再看消息处理

        本文链接:https://www.haomeiwen.com/subject/grlgxftx.html