• Handler.post和View.post的区别


    缘起

    在Android开发中,我们经常会见到下面的代码,比如:

    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            System.out.println("onCreate===");
            setContentView(R.layout.activity_main);
    
            rootBtn = findViewById(R.id.rootBtn);
            // 代码1
            UIHandler.post(new Runnable() {
                @Override
                public void run() {
                    System.out.println("Handler.post===");
                }
            });
            // 代码2
            rootBtn.post(new Runnable() {
                @Override
                public void run() {
                    System.out.println("View.post===");
                }
            });
        }
    

    你曾经有没有想过这两者到底有什么区别?我该使用哪种呢?

    常见的Handler.post揭秘

    Handler的工作机制,网上介绍的文章太多了,这里我就不赘述了,想继续了解的同学可以参考下这篇文章:Handler源码分析。一句话总结就是通过Handler对象,不论是post Msg还是Runnable,最终都是构造了一个Msg对象,插入到与之对应的Looper的MessageQueue中,不同的是Running时msg对象的callback字段设成了Runnable的值。稍后这条msg会从队列中取出来并且得到执行,UI线程就是这么一个基于事件的循环。所以可以看出Handler.post相关的代码在onCreate里那一刻时就已经开始了执行(加入到了队列,下次循环到来时就会被真正执行了)。

    View.post揭秘

    要解释它的行为,我们就必须深入代码中去找答案了,其代码如下:

    public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                // 注意这个判断,这个变量时机太早的话是没值的,
               // 比如在act#onCreate阶段 
                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;
        }
    

    从上面的源码,我们大概可以看出mAttachInfo字段在这里比较关键,当其有值时,其实和普通的Handler.post就没区别了,但有时它是没值的,比如我们上面示例代码里的onCreate阶段,那么这时执行到了getRunQueue().post(action);这行代码,从这段注释也大概可以看出来真正的执行会被延迟(这里的Postpone注释);我们接着往下看看getRunQueue相关的代码,如下:

    /**  其实这段注释已经说的很清楚明了了!!!
         * Queue of pending runnables. Used to postpone calls to post() until this
         * view is attached and has a handler.
         */
    private HandlerActionQueue mRunQueue;
    
    private HandlerActionQueue getRunQueue() {
            if (mRunQueue == null) {
                mRunQueue = new HandlerActionQueue();
            }
            return mRunQueue;
        }
    

    从上面我们可以看出,mRunQueue就是View用来处理它还没attach到window(还没对应的handler)时,客户代码发起的post调用的,起了一个临时缓存的作用。不然总不能丢弃吧,这样开发体验就太差了!!!
    紧接着,我们继续看下HandlerActionQueue类型的定义,代码如下:

    public class HandlerActionQueue {
        private HandlerAction[] mActions; 
        private int mCount;
    
        public void post(Runnable action) {
            postDelayed(action, 0);
        }
    
        public void postDelayed(Runnable action, long delayMillis) {
            final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
    
            synchronized (this) {
                if (mActions == null) {
                    mActions = new HandlerAction[4];
                }
                mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
                mCount++;
            }
        }
    
        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;
            }
        }
    
        private static class HandlerAction {
            final Runnable action;
            final long delay;
    
            public HandlerAction(Runnable action, long delay) {
                this.action = action;
                this.delay = delay;
            }
    
            public boolean matches(Runnable otherAction) {
                return otherAction == null && action == null
                        || action != null && action.equals(otherAction);
            }
        }
    }
    

    注意:这里的源码部分,我们只摘录了部分关键代码,其余不太相关的直接略去了。
    从这里可以看出,我们前面的View.post调用里的Runnable最终会被存储在这里的mActions数组里,这里最关键的一点就是其executeActions方法,因为这个方法里我们之前post的Runnable才真正通过handler.postDelayed方式使其进入handler对应的消息队列里等待执行;

    到此为止,我们还差知道View里的mAttachInfo字段何时被赋值以及这里的executeActions方法是什么时候被触发的,答案就是在View的dispatchAttachedToWindow方法,其关键源码如下:

    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            mAttachInfo = info;
            ...
            // Transfer all pending runnables.
            if (mRunQueue != null) {
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
            }
            performCollectViewAttributes(mAttachInfo, visibility);
            onAttachedToWindow();
            ...
    }
    

    而通过之前的文章,我们已经知道了此方法是当Act Resume之后,在ViewRootImpl.performTraversals()中触发的,参考View.onAttachedToWindow调用时机分析

    总结

    1. Handler.post,它的执行时间基本是等同于onCreate里那行代码触达的时间;

    2. View.post,则不同,它说白了执行时间一定是在Act#onResume发生后才开始算的;或者换句话说它的效果相当于你上面的View.post方法是写在Act#onResume里面的(但只执行一次,因为onCreate不像onResume会被多次触发);

    3. 当然,虽然这里说的是post方法,但对应的postDelayed方法区别也是类似的。

    平时当你项目很小,MainActivity的逻辑也很简单时是看不出啥区别的,但当act的onCreateonResume之间耗时比较久时(比如2s以上),就能明显感受到这2者的区别了,而且本身它们的实际含义也是很不同的,前者的Runnable真正执行时,可能act的整个view层次都还没完整的measure、layout完成,但后者的Runnable执行时,则一定能保证act的view层次结构已经measure、layout并且至少绘制完成了一次。



    作者:tmp_zhao
    链接:https://www.jianshu.com/p/7280b2d3b4d1
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    SQLServer 时间差运算
    phpStudy
    解决Apache/PHP无法启动的问题
    apche的主配置文件)
    知识总结
    学完了js的知识,一起分享总结知识点
    JS的学习体会与分享
    SpringBoot -- pom.xml文件
    c++基本知识点
    c语言基本常识5
  • 原文地址:https://www.cnblogs.com/chenxibobo/p/14086899.html
Copyright © 2020-2023  润新知