Android:IdleHandler的简单理解和使用

2023-05-16

IdleHandler的简单理解和使用

  • 1、IdleHandler 是什么
  • 2、IdleHandler 使用方式
    • 2.1、添加和删除
    • 2.2、执行
  • 3、常见问题和使用场景
    • 3.1、使用场景
    • 3.2、常见问题
  • 参考

1、IdleHandler 是什么

IdleHandler 说白了,就是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机制。

IdleHandler 被定义在 MessageQueue 中,它是一个接口。

public final class MessageQueue {
    public static interface IdleHandler {
        boolean queueIdle();
    }
}
  • 返回值为 false,即只会执行一次;
  • 返回值为 true,即每次当消息队列内没有需要立即执行的消息时,都会触发该方法。

基本使用方法

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
            @Override
            public boolean queueIdle() {
                Log.v(TAG, "#looper message has worked out");
                return false;
            }
        });
    }

2、IdleHandler 使用方式

2.1、添加和删除

在MessageQueue中:

private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<>();

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
    synchronized (this) {
        mIdleHandlers.remove(handler);
    }
}

通过一个全局变量mIdleHandlers管理所有IdleHandler

2.2、执行

既然 IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么何时出现空闲?

MessageQueue 是一个基于消息触发时间的优先级链表,所以出现空闲存在两种场景。

  • MessageQueue 为空,没有消息
  • MessageQueue 中最近需要处理的消息,是一个延迟消息when>currentTime),需要滞后执行;

这两个场景,都会尝试执行 IdleHandler。

处理 IdleHandler 的场景,就在 Message.next() 这个获取消息队列下一个待执行消息的方法中,我们跟一下具体的逻辑。

    /** MessageQueue.class */
    @UnsupportedAppUsage
    Message next() {
        // ...

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                // 注释1 屏障消息处理,获取异步消息
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                // 注释2 获取到Message不为null,则说明存在需要处理的Message
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    // MessageQueue无消息,设置延迟为-1,nativePollOnce无限等待,直到有消息
                    nextPollTimeoutMillis = -1;
                }

                // 注释3 执行到此处,说明没有需要执行的Message(MessageQueue为空,或者存在延迟Message)
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 注释4 IdleHandler队列为空,不执行,进入下一个循环,此后不再会执行IdleHandler判断,除非下次进入next方法
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                // 注释5 mIdleHandlers数组赋值给 mPendingIdleHandlers
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 注释6 执行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 {
                    // 注释7 执行任务逻辑,并返回任务释放移除
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        // 注释8 移除任务,下次不再执行
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 注释9 重置IdleHandler的Count为0,避免下次重复执行IdleHandler队列
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

1、如果本次循环拿到的Message为空,或者这个Message是一个延时的消息now < mMessages.when)而且还没到指定的触发时间,那么,就认定当前的队列为空闲状态,(此时nextPollTimeoutMillis为-1)。

2、遍历mPendingIdleHandlers数组(这个数组里面的元素每次都会到mIdleHandlers中去拿)来调用每一个IdleHandler实例的queueIdle方法。

  • pendingIdleHandlerCount < 0 时,根据 mIdleHandlers.size() 赋值给
    pendingIdleHandlerCount,它是后期循环的基础;
  • mIdleHandlers 中的 IdleHandler 拷贝到 mPendingIdleHandlers数组中,这个数组是临时的,之后进入 for 循环;
  • 循环中从数组中取出 IdleHandler,并调用其 queueIdle() 记录返回值存到 keep 中;

3、如果这个方法返回false的话,那么这个实例就会从 mIdleHandlers 中移除,也就是当下次队列空闲的时候,不会继续回调它的 queueIdle 方法了。

4、处理完 IdleHandler 后会将 nextPollTimeoutMillis 设置为0,也就是不阻塞消息队列,当然要注意这里执行的代码同样不能太耗时,因为它是同步执行的,如果太耗时肯定会影响后面的 message 执行。

在这里插入图片描述

3、常见问题和使用场景

3.1、使用场景

根据IdleHandler的特性,其使用场景遵循一个基本原则:在不影响其他任务,在消息队列空闲状态下执行

例如:Android Framework层的GC场景就使用了这个机制,只有当cpu空闲的时候才会去GC:

ApplicationThread收到GC_WHEN_IDLE消息后,会触发scheduleGcIdler方法:

class H extends Handler {
    ......
    public static final int GC_WHEN_IDLE = 120;
    ......
    
    public void handleMessage(Message msg) {
        switch (msg.what) {
            ......
            case GC_WHEN_IDLE:
                scheduleGcIdler();
            ......
        }
    }
}

ActivityThreadscheduleGcIdler方法中(ApplicationThreadActivityThread的非静态内部类,所以可以直接调用对应方法):

// 添加垃圾回收的IdleHandler
void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

// 移除垃圾回收的IdleHandler
void unscheduleGcIdler() {
    if (mGcIdlerScheduled) {
        mGcIdlerScheduled = false;
        Looper.myQueue().removeIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

final GcIdler mGcIdler = new GcIdler();

final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

void doGcIfNeeded() {
    mGcIdlerScheduled = false;
    final long now = SystemClock.uptimeMillis();
    if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
        // 执行垃圾回收
        BinderInternal.forceGc("bg");
    }
}

这里通过一个自定义的IdleHandler进行GC的调用,在线程空闲的时候会调用mGcIdler,最终通过BinderInternal.forceGc("bg")方法触发GC,这个GC的最小间隔是5秒(MIN_TIME_BETWEEN_GCS的值)。并且注意了,mGcIdlerqueueIdle返回的是false,所以这个mGcIdler会长期存在于主线程的MessageQueue中。

其他常见场景:IdleHandler基本使用及应用案例分析

3.2、常见问题

Q:IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行,那么何时出现空闲?

MessageQueue 是一个基于消息触发时间的优先级队列,所以队列出现空闲存在两种场景。

MessageQueue 为空,没有消息;
MessageQueue 中最近需要处理的消息,是一个延迟消息(when>currentTime),需要滞后执行;

Q:IdleHandler 有什么用?

IdleHandler 是 Handler 提供的一种在消息队列空闲时,执行任务的时机;
当 MessageQueue 当前没有立即需要处理的消息时,会执行 IdleHandler;

Q:MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?

不是必须;
IdleHandler.queueIdle() 的返回值,可以移除加入 MessageQueue 的 IdleHandler;

Q:当 mIdleHanders 一直不为空时,为什么不会进入死循环?

只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

Q:是否可以将一些不重要的启动服务,搬移到 IdleHandler 中去处理?

不建议;
IdleHandler 的处理时机不可控,如果 MessageQueue 一直有待处理的消息,那么 IdleHander 的执行时机会很靠后;

Q:IdleHandler 的 queueIdle() 运行在那个线程?

陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;

参考

1 、面试官:“看你简历上写熟悉 Handler 机制,那聊聊 IdleHandler 吧?”
2、深入浅出Handler(七) IdleHandler的巧用
3、关于空闲任务IdleHandler的那些事
4、源码阅读#Handler(下)同步屏障与IdleHandler

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android:IdleHandler的简单理解和使用 的相关文章

  • 深度学习语法篇

    一 基本常识 图像的分辨率的通道数 分辨率和通道数是两个不同的概念 分辨率指的是图像的像素数量 xff0c 它反映了图像的清晰度和细节程度 例如 xff0c 一个分辨率为64x64的图像意味着它有64个像素行和64个像素列 xff0c 总共
  • 第二讲:线性表示及坐标

    第二讲 xff1a 线性表示及坐标 一 线性表示 1 线性表示定义 xff1a 设 是线性空间V中的向量 xff0c 若存在V中一组向量 1 xff0c 2 xff0c xff0c n xff0c 及一组数x1 xff0c x2 xff0c
  • 快速理解掌握指针

    p gt next 61 q 像这种语句 xff0c 表示改变了p后面的连接关系 p 61 q gt next 这类语句 xff0c 没改变连接关系 xff0c 只是赋值而已 解读代码中指针所代表的节点之间的前后连接关系 只要输出该指针对应
  • 第三讲:子空间

    第三讲 xff1a 子空间 一 子空间定义 1 子空间 xff1a 设V是数域F上的线性空间 xff0c W是V的子集 xff0c 若对W中的任意元素 xff0c 及数K F xff0c 按V中的加法和数乘有 xff1a 1 xff09 4
  • Qt多线程之线程之间的传递数据

    hpp span class token macro property span class token directive keyword ifndef span MAINWINDOW H span span class token ma
  • 循环队列c代码实现

    循环队列的抽象数据类型 ADT 队列 xff08 Queue xff09 Data 同线性表 元素具有相同的类型 xff0c 相邻元素具有前驱和后继的关系 Operator span class token function InitQue
  • CMake(四):变量

    前面展示了如何定义基本目标和生成构建输出 就其本身而言 xff0c 这已经很有用了 xff0c 但CMake还附带了一大堆其他特性 xff0c 这些特性带来了极大的灵活性和便利性 本章涵盖了CMake最基本的部分之一 xff0c 即变量的使
  • CMake(六):使用子目录

    对于简单的项目 xff0c 将所有内容保存在一个目录中是可以的 xff0c 但是大多数实际项目倾向于将它们的文件分割到多个目录中 通常可以找到不同的文件类型或分组在各自的目录下的独立模块 xff0c 或者将属于逻辑功能组的文件放在项目目录层
  • CMake(九):生成器表达式

    当运行CMake时 xff0c 开发人员倾向于认为它是一个简单的步骤 xff0c 需要读取项目的CMakeLists txt文件 xff0c 并生成相关的特定于生成器的项目文件集 例如Visual Studio解决方案和项目文件 xff0c
  • CNNs系列---AlexNet网络介绍

    CNNs系列 AlexNet介绍 导言AlexNet介绍1 网络结构 1 参数量 计算量和输出尺寸计算公式 2 网络参数解析 2 AlexNet中涉及到的知识点 1 基本概念 2 AlexNet网络结构的贡献 导言 我们将开启关于卷积神经网
  • CNNS:基于AlexNet的分类任务

    CNNS 基于AlexNet的分类任务 数据集介绍1 pokeman数据集介绍2 flower数据集介绍 超参数对模型的影响1 激活函数对模型的影响 1 使用 96 Sigmoid 96 进行训练 2 使用 96 tanh 96 进行训练
  • CNNs: AlexNet补充

    CNNs AlexNet的补充 导言对 96 AlexNet 96 模型进行调整模型不同层的表征其他探索总结 导言 上上篇和上一篇我们详细地讲述了AlexNet的网络结构和不同超参数对同一数据集的不同实验现象 本节 xff0c 我们就Ale
  • 解决“Permission denied, please try again.”的问题

    在Ubuntu的终端输入命令 ssh highlight highlight是本地主机名称提示输入用户密码 当密码输入正确时 xff0c 仍返回错误 xff1a Permission denied please try again 解决的办
  • leetcode学习常用网站

    C 43 43 网站 cplusplus com map find C 43 43 Reference github com leopeng1995 acplusplus Morris Traversal方法遍历二叉树 xff08 非递归
  • arctan对照表

    注 xff1a 实际调用的是C 43 43 的atan2接口 arctan y x resultstd cout lt lt atan2 0 1 lt lt std endl 0std cout lt lt atan2 0 707 0 70
  • 关于optimized out

    根据网络上的说法 xff0c 调试期间如果一个变量的值显示 optimized out xff0c 那么就表明编译器将该变量进行了优化 xff0c 导致其值不可见 解决的方法是 xff0c 设置编译优化选项 xff0c 禁止相关的优化 可以
  • Ubuntu命令行中重复执行一个程序

    以下示例中 xff0c 执行program 10次 xff0c 并将运行日志以追加的方式重定向到log txt文件中 xff0c progam的入口参数是param for i in 1 10 do program param gt gt
  • 技术知识库

    我对自动控制技术发展趋势的理解 对数学理论的运用越来越深入 xff0c 对计算机的依赖越来越高 xff1b 与人们生产生活的契合越来越紧密以至于无法分割 xff1b 越来越向人类思维的本质倾向 心想事成靠拢 xff0c 让少数人的大脑和肢体
  • [AR论文阅读] Tracking Requirements for Augmented Reality

    论文作者 xff1a RONALD AZUMA年份 xff1a 1993论文主题 xff1a 阐述AR系统对6DoF跟踪性能的技术要求 要点 xff1a 三个核心要求 xff1a 高精度 xff0c 低延迟 xff0c 大范围 跟踪精度指标
  • cv::Mat和std::vector的相互转化

    声明 xff1a 代码来自StackOverFlow xff0c 原文链接 span class hljs keyword using span span class hljs keyword namespace span cv span

随机推荐