关于IdleHandler的疑惑

作者: 2c3d4f7ba0d4 | 来源:发表于2019-08-19 15:33 被阅读10次

    奇怪的疑惑

    在日常工作或者学习时,总会生出各种奇怪的疑惑,寻找答案的过程(一把梭,搜)非常有意思,得到的答案(一把梭,猜)也非常有价值。那么这份意思和价值就应该分享出来~

    疑问来源

    看到项目老代码做了一件事,自定义了一个页面的RootView,在首次onDraw的时候进行一些初始化工作,伪代码如下:

    RootView {
      onDraw() {
        if (firstDraw) {
          mActivity.handler.postDoSomething();
        }
      }
    }
    

    乍一看,哟,这小伙儿想在页面首次绘制完成后(偷摸摸)干一些事儿,操作骚的很嘛

    不过骚操作我也会,这种蜜汁需求我好像还有其他方法完成,诶,记得上次看了个IdleHandler可以用来做类似的事,待我翻翻以前看到的文章IdleHandler,页面启动优化神器

    该文章分析到,我们可以通过IdleHandler实现在界面绘制完成后进行一个回调,从而(偷摸摸)干上一些事儿

    问题来了,IdleHandler真这么骚?

    验他一验

    好嘛,这么骚的操作,我来验证一下:Open Demo Project

    Activity :

    @Override
    protected void onResume() {
        super.onResume();
    
        Log.i("TEST_TAG", "onResume: ");
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                Log.i("TEST_TAG", "queueIdle: ");
                return false;
            }
        });
    }
    

    RootView :

    RootView {
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            Log.i("TEST_TAG", "onDraw: ");
            mAcrivity.handler.post(new Runnable() {
                @Override
                public void run() {
                    Log.i("TEST_TAG", "run: post");
                }
            });
        }
    }
    

    Run It!

    分析一下

    情况符合预期,由于在ActivityThread内先调用Activity.onResume(),后调用window.addView(),所以输出onResume日志之后输出onDraw日志,而后因为在onDraw内post了一个任务,输出了run日志,在这之后由于主线程idle了(没活儿了)所以调用了IdleHandler的回调输出queueIdle日志

    再生疑惑

    之前测试在IdleHandler回调中返回了false,表示执行完成后从MessageQueue中移除掉自己,如果哪次手抽返回了true了?岂不是因为Looper死循环一直获取MessageQueue.next(),导致一直判断没有下一个message,从而一直疯狂的调用idleHandler?就像下面Message.next()的源码所说:

     Message next() {
            ...一坨代码
            for (;;) {
                ...一坨代码
                if (msg != null) {
                    if (now < msg.when) {
                        ...省略
                    } else {
                        ...省略
                        return msg;
                    }
                }
                ...一坨代码
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
                ... 省略
            }
        }
    
    

    源码说,如果当前时刻没有message需要处理,那我就应该翻牌IdleHandler。

    但是,如果长时间没有message需要处理,处在for (;;)死循环内的咱们,唯一离开循环的条件message != null长时间不成立,岂不是一直在循环,一直在调用IdleHandler?

    恍然大悟

    哦不!我这该死的脑子🧠总是记不住东西!精华全忘,代码都少抠了!

    Message next() {
            ...一坨代码
            for (;;) {
                ...一坨代码
    
                // 居然忘了你!
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                if (msg != null) {
                    if (now < msg.when) {
                        ...省略
                    } else {
                        ...省略
                        return msg;
                    }
                }
            }
    }
    

    居然忘了没有消息的时候线程通过nativePollOnce(ptr, nextPollTimeoutMillis);进入了休眠状态

    也就是说,IdleHandler的调用时机相关描述应该为:

    每次消费掉一个有效message,在获取下一个message时,如果当前时刻没有需要消费的有效(需要立刻执行)的message,那么会执行IdleHandler一次,执行完成之后线程进入休眠状态,直到被唤醒

    老有问题

    那么问题又来了,学挖掘....(啪,老实点!)

    哦不,问题是IdleHandler到底能用来解决什么问题?实现什么鬼需求?

    不要怀疑Google的Coder

    Google的Coder既然把IdleHandler放在了这,那么...那么我们就看看他用来干什么了。

    打开MessageQueue.java找到interface IdleHandler,按住command点他一点

    粗略看了看(挑简单不费事能看明白的看),比如ActivityThread内GcIdler用于某些情况下强制进行BinderInternal.GC(虽然我不知道这是在干啥),比如在MonitoringInstrumentation内用作数据检测用,比如在TextToSpeechService内结合HandlerThread用于任务完成后进行广播通知用。

    我还需要更多答案

    看Google大佬用IdleHandler还真发现一些用法,那他还有更多的骚操作嘛?

    这个问题比较严肃,就需要交给Google了

    [图片上传失败...(image-2eeb1f-1566198944827)]

    <figcaption></figcaption>

    经过检索提炼,发现每日一问 听说过Handler中的IdleHandler吗?链接内对这个问题有讨论,也分析了系统怎么用它,看起来有些货

    来自链接内,建议进入阅读

    @bigdevil

    ...省略部分...,目前可以想到的场景

    1. Activity启动优化:onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
    2. 想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
    3. 发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
    4. 一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查看

    结语

    如果我的文章确实有帮助到你,请不要忘了点一下👍

    难免有很多地方理解不到位甚至解释错误,还请请直接指出

    作者:不归路上的码渣
    链接:https://juejin.im/post/5d55779b518825168e6a0430

    关注+加群:

    Android进阶技术交流 (895077617 )免费获取整理版的资料

    群里可以与大神一起交流并走出迷茫。新手可进群免费领取学习资料,看看前辈们是如何在编程的世界里傲然前行!有想学习Android Java的,工作中想提升自己能力的,正在学习的小伙伴欢迎加入。(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,ViewPager,Bitmap,组件化架构,四大组件等深入学习视频资料以及Android、Java全方面面试资料)


    相关文章

      网友评论

        本文标题:关于IdleHandler的疑惑

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