美文网首页性能优化
IdleHandler原理及使用

IdleHandler原理及使用

作者: 小O机 | 来源:发表于2021-01-20 16:52 被阅读0次

    一、什么是IdleHandler?

    IdleHandlerandroid.os.MessageQueue的一个内部接口,其定义如下

    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }
    

    可以把它理解为RunnableIdleHandler会在MessageQueue中没有Message要处理或者要处理的Message都是延时任务的时候得到执行,这种特性很重要,因为当MessageQueue中没有Message要处理或者要处理的Message都是延时任务的时候,就表明当前线程为空闲状态,如果是在主线程,则表明当前UI没有绘制动作,所以可以根据监听IdleHandler是否执行来判断UI是否绘制完成,从而避免在UI绘制的时候进行耗时操作,影响UI绘制效率。

    二、IdleHandler原理

    上面说到IdleHander会在MessageQueue处于空闲状态时执行,既然跟MessageQueue有关,那就很容易让人想到Handler原理。简单介绍一下Handler原理,就是Handler将任务封装成Message,通过与Handler绑定的Looper,放到MessageQueue里面,Looper会不断从MessageQueue里去取Message执行。一旦当前MessageQueue没有任务需要执行,那么就会触发IdleHandler执行,所以这一步的核心就是在MessageQueue中取Message,因为只有取不到才会触发IdleHandler执行逻辑。下面是MessageQueue#next()方法:

    Message next() {
        ...
    
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
    
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
                ...
                -----------------------IdleHanlder处理逻辑--------------------
                // 当mMessages链表为null或者下一个需要执行的Message是个delay任务
                // 的时候,开始统计IdlerHandler数量
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 如果没有IdlerHandler,则进行下一个循环
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
    
                // 新建一个数组,用来复制存放IdleHandler,因为后面会对mIdleHandlers进行remove元素操作
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }
    
            // 执行IdleHandler
            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);
                    }
                }
            }
    
            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
            -----------------------IdleHanlder处理逻辑--------------------
    
            ...
        }
    }
    

    在取Message的时候,如果当前没有需要处理的Message,就会进入IdleHandler处理逻辑,IdleHandler执行的返回值表示需不需要从mIdleHandlers中移除该IdleHandler

    这里有一个细节需要注意,pendingIdleHandlerCount的初始值为-1,在执行完所有IdleHandler后会被置成0,而这里仅当pendingIdleHandlerCount < 0的时候才会进入,这样就保证了所有IdleHandler只会在MessageQueue#next()的循环里执行一次。下一次通过Looper#loop()进入MessageQueue#next()的时候pendingIdleHandlerCount会重置为-1,继续判断是不是Idle状态。
    另外,当pendingIdleHandlerCount=0的时候,mBlocked会被置为true,这个字段涉及线程唤醒的操作,有兴趣的同学可以自行查阅源码。

    三、IdleHandler使用

    MessageQueue提供了添加IdleHandler的方法

    /**
     * Add a new {@link IdleHandler} to this message queue.  This may be
     * removed automatically for you by returning false from
     * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
     * invoked, or explicitly removing it with {@link #removeIdleHandler}.
     *
     * <p>This method is safe to call from any thread.
     *
     * @param handler The IdleHandler to be added.
     */
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    

    下面通过一个例子展示怎么使用IdleHandler

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                Log.e("DBL", "queueIdle");
                // UI第一帧绘制完成(可以理解为页面可见)
                return false;  // 返回false表示MessageQueue在执行完这段代码后将该IdleHandler删除,反之不删除,下一次继续执行
            }
        });
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        Log.e("DBL", "onResume");
    }
    

    上面例子将IdleHandler添加到主线程MessageQueue中,queueIdle()方法回调,说明UI第一帧绘制完成,可以理解为UI首次可见,这个比onResume精确的多,因为onResume回调的时候界面还没有开始绘制,此时界面是不可见的。

    四、IdleHandler应用场景

    1. 在应用启动时我们可能希望把一些优先级没那么高的操作延迟一点处理,一般会使用 Handler.postDelayed(Runnable r, long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能较差可能需要延迟较多,有的性能较好可以允许较少的延迟时间。所以在做项目性能优化的时候可以使用 IdleHandler,它在主线程空闲时执行任务,而不影响其他任务的执行。
    2. 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用View.post()也能实现,区别就是前者会在消息队列空闲时执行
    3. 发送一个返回 true 的 IdleHandler,在里面让某个 View 不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作

    五、注意事项

    1. MessageQueue 提供了add/remove IdleHandler方法,但是我们不一定需要成对使用它们,因为IdleHandler.queueIdle() 的返回值返回 false 的时候可以移除 IdleHanlder。
    2. 不要将一些不重要的启动服务放到 IdleHandler 中去管理,因为它的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么它的执行时机会很靠后。
    3. 当 mIdleHanders 一直不为空时,为什么不会进入死循环?
    • 只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
    • pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

    相关文章

      网友评论

        本文标题:IdleHandler原理及使用

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