当前位置: 首页 > news >正文

用IdleHandler来性能优化及原理源码分析

背景:

经常在做一些app冷启动速度优化等性能优化工作时候,经常可能会发现有时候需要引入一些第三方sdk,或者库,这些库一般会要求我们在onCreate中进行初始化等,但是onCreate属于生命周期的回调方法,如果onCreate中业务过多就会影响整个app的冷启动时长,很多人这个时候第一想到可能是放入子线程等,但是经常又发现有的调用还要求在主线程调用等限制,所以针对这种情况下就经常会使用到一个IdleHandler,这个当初使用时候大家可能就只是字面意思理解为空闲的Handler,在空闲时候执行,其实没有深入去看看它到底实现原理是什么,今天刚好有机会就来给大家进行一个剖析。

IdleHandler 的概念

IdleHandler 是 MessageQueue 提供的一个接口,用于监听消息队列的空闲状态。它允许开发者在主线程处于空闲时,执行一些低优先级的任务。IdleHandler 的核心方法 queueIdle() 会在系统检测到消息队列空闲时调用。返回值决定了 IdleHandler 是否继续在队列中保留:若返回 true,则该回调在下次队列空闲时继续执行;反之,返回 false 则表示仅执行一次后移除。

IdleHandler 和性能优化

IdleHandler 的出现正好迎合了性能优化的需求。在实际开发中,有一些任务并非必须实时完成,例如日志上报、资源预加载、缓存清理、数据统计、一些动画或预渲染任务等。利用 IdleHandler,可以将这些不紧急的任务推迟到主线程消息队列空闲时再执行,从而避免干扰用户看到的实时界面更新,提高整体界面流畅度和响应速度。因此,在 UI 主线程相对繁忙时,通过 IdleHandler 来分摊任务,可以让系统先处理用户的核心交互,就比如onCreate是生命周期方法,如理里面初始化太多东西影响冷启动速度,针对一些可以延后不那么紧急任务可以待系统空闲时再处理任务,充分利用 CPU 空闲时间。

源码分析IdleHandler

为了更加全面了解IdleHandler,下面将要从以下几个部分进行IdleHandler的源码分析。
1、
IdleHandler的接口解释:
frameworks/base/core/java/android/os/MessageQueue.java

    /*** 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();}

上面注释就可以看出来,这个IdleHandler属于一个回调接口,这个接口在线程即将要阻塞等待更多消息时候会被调用。
queueIdle就是对应的回调函数,这个queueIdle有一个返回值true或false,这两个值分别有如下区别:
如果返回true意味着下一次空闲依旧会执行这个IdleHandler的回调函数。
如果返回false就代表当前IdleHandler已经执行完毕,下次空闲不会再执行该IdleHandler。

添加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这个集合中mIdleHandlers.add(handler);}}

这里的addIdleHandler添加就是简单加入的mIdleHandlers这个集合中,方便后面执行时候进行遍历

IdleHandler的触发执行

    @UnsupportedAppUsageMessage next() {int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {//进入等待消息,要被唤醒2种情况,1被其他函数wake,2等待时间nextPollTimeoutMillis到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;//当消息的Handler为空时,则查询异步消息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());}if (msg != null) {//检测msg时间是还没到了if (now < msg.when) {// Next message is not ready.  Set a timeout to wake up when it is ready.//还没到msg的处理时间,那么就会与当前时间进行差值,这个差值就是等待时间nextPollTimeoutMillisnextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {//设置下一个消息为mMessagesmMessages = msg.next;}msg.next = null;msg.markInUse();//直接return回去消息,也就是有消息都在这里return了不会执行下面业务return msg;}} else {//没有找到消息// No more messages.nextPollTimeoutMillis = -1;}// 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.//找了一圈发现没有要处理的消息了,那么就开始判断是否有IdleHandler的消息要处理//注意这里的pendingIdleHandlerCount第一次进入才是-1,后续一旦执行IdleHandler都是变成0,不会再进入if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {//把mIdleHandlers集合大小赋值给pendingIdleHandlerCountpendingIdleHandlerCount = mIdleHandlers.size();}//如果发现没有IdleHandler,那么就返回循环进行block阻塞等待唤醒if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}//如果mPendingIdleHandlers这个数组没有初始化就进行初始化,这里最小大小为4应该是为了性能以防频繁扩容if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}//mIdleHandlers转成了数组mPendingIdleHandlersmPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.//开始正式执行对应的IdleHandlerfor (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {//执行IdleHandler的对应回调业务方法keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}//这里的keep就是queueIdle的返回值,如果返回false就会从mIdleHandlers进行移除if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}//把pendingIdleHandlerCount变成0,不是-1,这样防止死循环执行IdleHandler// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;//这里是为了让尽快遍历一下是否有消息,因为可能执行IdleHandler期间有新消息// 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;}}

上面next方法,关于IdleHandler执行总结如下:
1、next方法遍历消息队列,确实没有寻找到要处理的消息任务
2、没有要处理的消息则开始处理IdleHandler相关的任务
3、如果IdleHandler的queueIdle返回false则会从mIdleHandlers删除,下次空闲就不会在执行这个IdleHandler,否则true的话会每次空闲都执行

实战案例

测试一下queueIdle返回false,即只执行一次queueIdle

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//TODO 执行一些不那么紧急任务                Trace.beginSection("queueIdle no next runing");Log.i("lsm66666","queueIdle return false,no next runing");Trace.endSection();return false;}});}

验证结果:
在这里插入图片描述

再测试一下queueIdle返回true,即执行多次queueIdle

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//TODO 执行一些不那么紧急任务                Trace.beginSection("queueIdle has next runing");Log.i("lsm66666","queueIdle return true,has next runing");Trace.endSection();return true;}});}

看看日志打印情况
在这里插入图片描述

systrace结合分析IdleHandler

在这里插入图片描述

上面也可以看出来IdleHandler的执行都在没有消息可以处理了,才会执行,而且执行完成后就进入消息等待时期。
从上面也可以看出来,IdleHandler确实是在主线程空闲暂时没有消息处理时候进行执行的,可以起到一个错峰执行的目的,不过也要注意以下几点:

1、切勿让IdleHandler执行耗时操作,应该是可以快速完成的一些任务

2、尽量都让IdleHandler执行是一次性任务,即queueIdle返回false,以防每次空闲都有重复调用

文章参考:https://mp.weixin.qq.com/s/7R2ws-6oBij3OrNMA_IfTA

更多framework实战开发干货,请关注下面“千里马学框架”

相关文章:

用IdleHandler来性能优化及原理源码分析

背景&#xff1a; 经常在做一些app冷启动速度优化等性能优化工作时候&#xff0c;经常可能会发现有时候需要引入一些第三方sdk&#xff0c;或者库&#xff0c;这些库一般会要求我们在onCreate中进行初始化等&#xff0c;但是onCreate属于生命周期的回调方法&#xff0c;如果on…...

git忽略特定文件或者文件夹

如果想让 Git 忽略指定目录&#xff0c;不进行更新或提交&#xff0c;可以使用 .gitignore 文件进行配置。 &#x1f6e0; 方法&#xff1a;使用 .gitignore 忽略目录 1️⃣ 在仓库根目录创建 .gitignore 文件 如果你的项目目录下还没有 .gitignore 文件&#xff0c;可以新建…...

STM32使用无源蜂鸣器

1.1 介绍&#xff1a; 有源蜂鸣器&#xff1a;内部自带振荡源&#xff0c;将正负极接上直流电压即可持续发声&#xff0c;频率固定 无源蜂鸣器&#xff1a;内部不带振荡源&#xff0c;需要控制器提供振荡脉冲才可发声&#xff0c;调整提供振荡脉冲的频率&#xff0c;可发出不同…...

VMware 安装部署RHEL9

目录 目标一&#xff1a;创建名为RHEL9_node2的虚拟机 1.环境搭建&#xff1a;VMware 2.下载RHEL9的ISO镜像&#xff08;官网可获取&#xff09; 3.打开VMware&#xff0c;新建虚拟机 3.1 自定义安装 3.2 默认操纵至下一步操作到稍后安装系统 3.3选择操作系统为linux以及…...

智能机器人学习机WT3000A AI芯片方案-自然语音交互 打造沉浸式学习体验

一、概述 当AI浪潮席卷全球&#xff0c;教育领域也未能幸免。AI学习机&#xff0c;这个打着“个性化学习”、“精准提分”旗号的新兴产品&#xff0c;正以惊人的速度占领市场。从一线城市到偏远乡镇&#xff0c;从学龄前儿童到高考备考生&#xff0c;AI学习机的广告铺天盖地&am…...

阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1

阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型&#xff01;这款模型不仅名字萌萌哒(QwQ)&#xff0c;实力更是不容小觑&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 开源&#xff0c;采用了 Apache 2.0 开源协议。大家可通过 Qwen C…...

20250307学习记录

大家早上好呀&#xff0c;今天早上七点四十就起床了 第一部分&#xff0c;修改一下任务 完成 第二部分&#xff0c;整理MODIS数据 2023-5-30-GEE-土地覆盖处理_mcd12q1.061-CSDN博客 看完这个博客&#xff0c;我有了大致的思路 编写代码转换为tiff&#xff0c;并且将不同…...

设计模式-创建型模式详解

这里写目录标题 一、基本概念二、单例模式1. 模式特点2. 适用场景3. 实现方法4. 经典示例 三、简单工厂模式1. 模式特点2. 经典示例 四、工厂方法模式五、抽象工厂模式1. 适用场景2. 经典示例 六、建造者模式1. 模式特点2. 一般流程3. 适用场景4. 经典示例 七、原型模式 一、基…...

【蓝桥杯】每天一题,理解逻辑(2/90)【LeetCode 复写零】

闲话系列&#xff1a;每日一题&#xff0c;秃头有我&#xff0c;Hello&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;,我是IF‘Maxue&#xff0c;欢迎大佬们来参观我写的蓝桥杯系列&#xff0c;我好久没有更新博客了&#xff0c;因为up猪我寒假用自己的劳动换了…...

米尔基于STM32MP25x核心板Debian系统发布,赋能工业设备

一、系统概述 MYD-LD25X搭载的Debian系统包含以太网、WIFI/BT、USB、RS485、RS232、CAN、AUDIO、HDMI显示和摄像头等功能&#xff0c;同时也集成了XFCE轻量化桌面、VNC远程操控、SWITCH网络交换和TSN时间敏感网络功能&#xff0c;为工业设备赋予“超强算力实时响应极简运维”的…...

ES02 - ES语句

ES语句 文章目录 ES语句一&#xff1a;连接和基本的使用1&#xff1a;显示详细信息2&#xff1a;输出可显示列3&#xff1a;查看分片 二&#xff1a;Http接口 - 索引(数据库)的增删改2.1&#xff1a;插入数据2.2&#xff1a;删除数据2.3&#xff1a;更新数据2.3.1&#xff1a;P…...

C++ 学生成绩管理系统

一、项目背景与核心需求 成绩管理系统是高校教学管理的重要工具,本系统采用C++面向对象编程实现,主要功能模块包括: 学生信息管理(学号/姓名/3门课程成绩) 成绩增删改查(CRUD)操作 数据持久化存储 统计分析与报表生成 用户友好交互界面 二、系统架构设计 1. 类结构设计 …...

项目管理工具 Maven

目录 1.Maven的概念 1.1​​​​​什么是Maven 1.2什么是依赖管理 1.3什么是项目构建 1.4Maven的应用场景 1.5为什么使用Maven 1.6Maven模型 2.初识Maven 2.1Maven安装 2.1.1安装准备 2.1.2Maven安装目录分析 2.1.3Maven的环境变量 2.2Maven的第一个项目 2.2.1按照约…...

设计心得——分层和划分模块

一、分层 在实际的设计开发过程中&#xff0c;对于稍微大一些的项目&#xff0c;基本都涉及到分层。什么是分层呢&#xff1f;其实非常简单&#xff0c;就是利用某种逻辑或域的范围等把整个项目划分成多个层次。它们之间通过接口&#xff08;可能是简单的函数接口也可以是服务…...

uniapp使用蓝牙,usb,局域网,打印机打印

使用流程&#xff08;支持安卓和iOS&#xff09; 引入SDK 引入原生插件包地址如下 https://github.com/oldfive20250214/UniPrinterDemo 连接设备 安卓支持经典蓝牙、ble蓝牙、usb、局域网&#xff08;参考API&#xff09; iOS支持ble蓝牙、局域网&#xff08;参考API&…...

PQL查询和监控各类中间件

1 prometheus的PQL查询 1.1 Metrics数据介绍 prometheus监控中采集过来的数据统一称为Metrics数据&#xff0c;其并不是代表具体的数据格式&#xff0c;而是一种统计度量计算单位当需要为某个系统或者某个服务做监控时&#xff0c;就需要使用到 metrics prometheus支持的met…...

day1 redis登入指令

[rootlocalhost data]# redis-cli -h ip -p 6379 -a q123q123 Warning: Using a password with -a or -u option on the command line interface may not be safe. ip:6379> 以上&#xff0c; Bigder...

2025 年 AI 网络安全预测

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 微软和 OpenAI 宣布延长战略合作伙伴关系&#xff0c;加强对推进人工智能技术的承诺&#xff0c;这表明强大的 AI 将在未来占据主导地位。 随着人工智能 &#x…...

[Windows] 多系统键鼠共享工具 轻松跨系统控制多台电脑

参考原文&#xff1a;[Windows] 多系统键鼠共享工具 轻松跨系统控制多台电脑 还在为多台电脑需要多套键盘鼠标而烦恼吗&#xff1f;是不是在操控 Windows、macOS、Linux 不同系统电脑时手忙脚乱&#xff1f;现在&#xff0c;这些问题通通能解决&#xff01;Deskflow 软件闪亮登…...

单片机的发展

一、引言 单片机自诞生以来&#xff0c;经历了四十多年的风风雨雨&#xff0c;从最初的工业控制逐步扩展到家电、通信、智能家居等各个领域。其发展过程就像是一场精彩的冒险&#xff0c;每一次技术的革新都像是在未知的海域中开辟新的航线。 二、单片机的发展历程 &#xff…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

Git常用命令完全指南:从入门到精通

Git常用命令完全指南&#xff1a;从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?

FTP&#xff08;File Transfer Protocol&#xff09;本身是一个基于 TCP 的协议&#xff0c;理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况&#xff0c;主要原因包括&#xff1a; ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...

Mysql故障排插与环境优化

前置知识点 最上层是一些客户端和连接服务&#xff0c;包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念&#xff0c;为通过安全认证接入的客户端提供线程。同样在该层上可…...