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

ANR系列(二)——ANR监听方案之IdleHandler

前言

关于IdleHandler,比较多同学错误地认为,这个Handler的作用是主线程空闲状态时才执行它,那么用它做一些耗时操作也没所谓。可是IdleHandler在主线程的MessageQueue中,执行queueIdle()默认当然也是执行在主线程中的,这里的耗时操作其实很容易引起卡顿和ANR。

IdleHandler的介绍

IdleHandler是一种在只有当消息队列没有消息时或者是队列中的消息还没有到执行时间时才会执行的IdleHandler。从源码上看,IdleHandler是一个回调接口,当线程中的消息队列将要阻塞等待消息的时候,就会回调该接口,也就是说消息队列中的消息都处理完毕了,没有新的消息了,处于空闲状态时就会回调该接口。

public static interface IdleHandler {boolean queueIdle();
}

IdleHandler的使用

IdleHandler是MessageQueue的静态内部接口,通过静态方法就能拿得到,不过要注意的事,当前Looper是主线程的Looper的话,取到的也是主线程的MessageQueue

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {@Overridepublic boolean queueIdle() {//空闲时处理逻辑return false;}
});

IdleHandler的问题

IdleHandler如果是主线程的执行超过5s同样也是会报ANR,我们通过主线程模拟休眠,会发现App直接ANR

Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {override fun queueIdle(): Boolean {Log.e("TAG", "[queueIdle] sleep(5000) start")Thread.sleep(5000)Log.e("TAG", "[queueIdle] sleep(5000) end")return false}
})

IdleHandler的监控分析

为了防止IdleHandler滥用,监控起来也是很有必要,特别是第三方产商经常通过这个接口做一些耗时操作。通过查看源码,找到IdleHandler的Hook点

  1. 通过Looper.myQueue().addIdleHandler()开始,可以看到是每次通过mIdleHandlers加入到队列中
public void addIdleHandler(@NonNull IdleHandler handler) {if (handler == null) {throw new NullPointerException("Can't add a null IdleHandler");}synchronized (this) {mIdleHandlers.add(handler);}
}
  1. mIdleHandlers是一个列表,会保存每一个添加进来的IdleHandler
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
  1. Message#next()中,执行完Handler消息空闲后,会将当前的IdleHandler列表循环遍历执行queueIdle()
Message next() {.....for (;;) {.....if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}// 1、取出所有IdleHandlermPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {// 2、执行IdleHandler的queueIdle()keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}}
}

IdleHandler的监控实现

  1. IdleHandler的执行流程可以看出,Hook点就在mIdleHandlers列表中,将当前的MessageQueue中的mIdleHandlers列表替换成自己的列表
class IdleHandlerMonitor {private HandlerThread idleHandlerThread;private Handler idleHandlerHandler;private static String mIdleHandler = null;IdleHandlerMonitor() {// 1、创建子线程的handler(idleHandlerHandler),方便后续在子线程发送消息不被主线程卡住影响if (Build.VERSION.SDK_INT >= 23) {this.idleHandlerThread = new HandlerThread("IdleHandlerThread");this.idleHandlerThread.start();this.idleHandlerHandler = new Handler(this.idleHandlerThread.getLooper());this.detectIdleHandler();}}@RequiresApi(api = 23)private void detectIdleHandler() {// 2、修改MessageQueue中的mIdleHandlers变量,传入自定义的Listtry {MessageQueue mainQueue = Looper.getMainLooper().getQueue();Field field = MessageQueue.class.getDeclaredField("mIdleHandlers");field.setAccessible(true);CustomArrayList<MessageQueue.IdleHandler> myIdleHandlerArrayList = new CustomArrayList();field.set(mainQueue, myIdleHandlerArrayList);} catch (Throwable var4) {var4.printStackTrace();}}private class CustomArrayList<T> extends ArrayList {public boolean add(Object o) {if (o instanceof MessageQueue.IdleHandler) {// 3、将原来的IdleHandler包装进自己的CustomIdleHandlerCustomIdleHandler customIdleHandler = new CustomIdleHandler((MessageQueue.IdleHandler) o);return super.add(customIdleHandler);}return super.add(o);}public boolean remove(@Nullable Object o) {if (o instanceof CustomIdleHandler) {return super.remove(((CustomIdleHandler) o));}return super.remove(o);}}private class CustomIdleHandler implements MessageQueue.IdleHandler {private MessageQueue.IdleHandler idleHandler;CustomIdleHandler(MessageQueue.IdleHandler idleHandler) {this.idleHandler = idleHandler;}@Overridepublic boolean queueIdle() {mIdleHandler = this.idleHandler.toString();idleHandlerHandler.removeCallbacks(idleHanlderRunnable);idleHandlerHandler.postDelayed(idleHanlderRunnable, 3000L);// 4、将包装起来的IdleHandler取出来,执行queueIdle,包装前设置多一项3s的延时任务。// 只要queueIdle在3s内没执行完,将执行当前的idleHanlderRunnableboolean ret = this.idleHandler.queueIdle();idleHandlerHandler.removeCallbacks(idleHanlderRunnable);return ret;}}// 5、报告输出当前Idle信息超时通知private static Runnable idleHanlderRunnable = () -> {Log.e("TAG", "[queueIdle] more then 3000L \n message=" + mIdleHandler);};
}

hook的巧妙点在于将当前的List换成自己的List,然后在List的添加和删除中,偷梁换柱成自己的IdleHandler进行加工处理

IdleHandler的验证

Hook解决完之后,我们来通过Demo验证下是否是我们想要的监控,通过初始化IdleHandlerMonitor()启动监控,然后模拟3次IdleHandler发送不同时间的消息,最后看日志输出,是否被捕获到超时的Idle任务

private fun initIdelHandler() {IdleHandlerMonitor()Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {override fun queueIdle(): Boolean {Log.e("TAG", "[queueIdle] sleep(2000) start")Thread.sleep(2000)Log.e("TAG", "[queueIdle] sleep(2000) end")return false}})Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {override fun queueIdle(): Boolean {Log.e("TAG", "[queueIdle] sleep(5000) start")Thread.sleep(5000)Log.e("TAG", "[queueIdle] sleep(5000) end")return false}})Looper.myQueue().addIdleHandler(object : MessageQueue.IdleHandler {override fun queueIdle(): Boolean {Log.e("TAG", "[queueIdle] sleep(10000) start")Thread.sleep(10000)Log.e("TAG", "[queueIdle] sleep(10000) end")return false}})
}

通过日志输出结果,可以看到当前的Idle阻塞3s时候的代码类位置

E/TAG: [queueIdle] sleep(2000) start
E/TAG: [queueIdle] sleep(2000) end
E/TAG: [queueIdle] sleep(5000) start
E/TAG: [queueIdle] more then 3000L message=com.example.syncbarriermonitor.MainActivity$initIdelHandler$2@4d9ab66
E/TAG: [queueIdle] sleep(5000) end
E/TAG: [queueIdle] sleep(10000) start
E/TAG: [queueIdle] more then 3000L message=com.example.syncbarriermonitor.MainActivity$initIdelHandler$3@be7cc0

相关文章:

ANR系列(二)——ANR监听方案之IdleHandler

前言 关于IdleHandler&#xff0c;比较多同学错误地认为&#xff0c;这个Handler的作用是主线程空闲状态时才执行它&#xff0c;那么用它做一些耗时操作也没所谓。可是IdleHandler在主线程的MessageQueue中&#xff0c;执行queueIdle()默认当然也是执行在主线程中的&#xff0…...

数学小课堂:数学和自然科学的关系(数学方法,让自然科学变成科学体系。)

文章目录 引言I 数学方法,让自然科学变成科学体系。1.1 天文学1.2 博物学1.3 化学1.4 医药学1.5 物理学II 自然科学的升华过程III 数学方法的意义引言 19世纪初,英国人把采用实验的方法,系统地构造和组织知识,解释和预测自然的学问称为科学。 科学研究的是自然现象和自然…...

[蓝桥杯 2020 省 A1] 分配口罩

思路比较容易想到&#xff0c;因为口罩全部只有15批&#xff0c;因此直接暴力dfs搜索即可 //dfs #include<bits/stdc.h> using namespace std; int ans 9999; int num[] {9090400, 8499400, 5926800, 8547000, 4958200, 4422600, 5751200, 4175600, 6309600, 5865200, …...

第五章:C语言数据结构与算法之双向带头循环链表

系列文章目录 文章目录系列文章目录前言一、哨兵位的头节点二、双向链表的结点三、接口函数的实现1、创建结点2、初始化3、尾插与尾删4、头插与头删5、打印6、查找7、随机插入与随机删除8、判空、长度与销毁四、顺序表和链表的对比1. 不同点2. 优缺点五、缓存命中1、缓存2、缓存…...

一文带你了解,前端模块化那些事儿

文章目录前端模块化省流&#xff1a;chatGPT 总结一、参考资料二、发展历史1.无模块化引出的问题:横向拓展2.IIFE3.Commonjs(cjs)4.AMD引出的问题&#xff1a;5.CMD6.UMD7.ESM往期精彩文章前端模块化 省流&#xff1a;chatGPT 总结 该文章主要讲述了前端模块化的发展历史和各个…...

(六十五)大白话设计索引的时候,我们一般要考虑哪些因素呢?(中)

今天我们继续来说一下&#xff0c;在设计索引的时候要考虑哪些因素。之前已经说了&#xff0c;你设计的索引最好是让你的各个where、order by和group by后面跟的字段都是联合索引的最左侧开始的部分字段&#xff0c;这样他们都能用上索引。 但是在设计索引的时候还得考虑其他的…...

Spring事务管理

文章目录1 事务1.1 需求1.2 原因分析1.3 错误解决1.4 yml配置文件中开启事务管理日志1 事务 1.1 需求 当部门解散了不仅需要把部门信息删除了&#xff0c;还需要把该部门下的员工数据也删除了。可当在删除员工数据出现异常时&#xff0c;就不会执行删除员工操作&#xff0c;出…...

数字化工厂装配线生产管理看板系统

电力企业业务复杂&#xff0c;组织结构复杂&#xff0c;不同的业务数据&#xff0c;管理要求也不尽相同。生产管理看板系统针对制造企业的生产应用而开发&#xff0c;能够帮助企业建立一个规范准确即时的生产数据库。企业现状&#xff1a;1、计划不清晰&#xff1a;生产计划不能…...

vxe-grid 全局自定义filter过滤器,支持字典过滤

一、vxe-table的全局筛选器filters的实现 官网例子&#xff1a;https://vxetable.cn/#/table/renderer/filter 进入之后&#xff1a;我们可以参照例子自行实现&#xff0c;也可以下载它的源码&#xff0c;进行调整 下载好后并解压&#xff0c;用vscode将解压后的文件打开。全局…...

ECharts 环形图组件封装

一、ECharts引入1.安装echarts包npm install echarts --save2.引入echarts这里就演示全局引入了&#xff0c;挂载到vue全局&#xff0c;后面使用时&#xff0c;直接使用 $echartsimport * as echarts from echarts Vue.prototype.$echarts echarts二、写echarts组件这里演示环…...

c++ 怎么调用python 提供的函数接口

在 C 中调用 Python 函数有多种方法&#xff0c;以下是其中的两种常见方法&#xff1a;使用 Python/C APIPython 提供了 C/C API&#xff0c;可以通过该 API 在 C 中调用 Python 函数。使用这种方法&#xff0c;需要先将 Python 解释器嵌入到 C 代码中&#xff0c;然后可以通过…...

【动态规划】背包问题(01背包,完全背包)

Halo&#xff0c;这里是Ppeua。平时主要更新C语言&#xff0c;C&#xff0c;数据结构算法......感兴趣就关注我吧&#xff01;你定不会失望。 &#x1f308;个人主页&#xff1a;主页链接 &#x1f308;算法专栏&#xff1a;专栏链接 我会一直往里填充内容哒&#xff01; &…...

记录 UE5 完全重新构建 UE C++项目

不知道搞了什么&#xff0c;C项目的实时代码编译罢工了&#xff0c;搞了半天都修不好&#xff0c;只能又重建了 UE5 版本为 v5.1.1 删除以下文件夹 /Binaries /Intermediate /SavedBinaries 文件夹是编译后的模块 Intermediate 文件夹里是中间层的C代码&#xff0c;完全由ue…...

java版云HIS系统源码 微服务架构支持VUE

云his系统源码 一个好的HIS系统&#xff0c;要具有开放性&#xff0c;便于扩展升级&#xff0c;增加新的功能模块&#xff0c;支撑好医院的业务的拓展&#xff0c;而且可以反过来给医院赋能&#xff0c;最终向更多的患者提供更好地服务。 私信了解更多&#xff01; 本套基于…...

苹果内购支付检验错误码

21000 The request to the App Store didn’t use the HTTP POST request method. 对App Store的请求没有使用HTTP POST请求方法。 21001 The App Store no longer sends this status code. App Store不再发送此状态代码。 21002 The data in the receipt-data property…...

day27_css

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、CSS 零、 复习昨日 见代码 一 、引言 1.1CSS概念 ​ 层叠样式表(英文全称&#xff1a;Cascading Style Sheets)是一种用来表现HTML&#xff08;标准通…...

智慧赋能,聚力开源——第四届OpenI/O 启智开发者大会开源治理专场顺利举办!

为汇聚国内外知名开源组织共同探讨中国开源生态建设及开源治理相关议题&#xff0c;推进产学研用开源合作&#xff0c;2月24日下午&#xff0c;第四届OpenI/O启智开发者大会在深圳人才研修院智汇中心举办以“构建开源联合体&#xff0c;共建开源生态”为主题的开源治理专场分论…...

Java工程师应该如何成长?

近几年&#xff0c;不少开发者会抱怨“面试造火箭&#xff0c;天天拧螺丝”&#xff0c;每天进行重复业务开发&#xff0c;似乎能力被日常工作限制&#xff0c;无法突破提高。 极客时间《Java 核心技术 36 讲》专栏作者杨晓峰认为&#xff0c;如果处于新手阶段&#xff0c;全面…...

【数据分析师求职面试指南】必备编程技能整理之Hive SQL必备用法

文章目录熟悉Python懂R语言掌握SQL大数据基础数据库常用类型多表查询更多聚合函数distinctcase when窗口函数动态更新一行变多行调优内容整理自《拿下offer 数据分析师求职面试指南》—徐粼著 第四章编程技能考查其他内容&#xff1a;【数据分析师求职面试指南】必备基础知识整…...

Maven - Linux 服务器 Maven 环境安装与测试

目录 一.引言 二.安装流程 1.获取安装包 2.解压并安装 3.配置环境 4.mvn 验证 三.测试踩坑 1.Permission denied 2.Plugin or dependencies Error 一.引言 通道机上的 java 项目需要 mvn package 提示没有 mvn 命令&#xff0c;下面记录下安装 maven 的全过程。 二.安…...

AI-调查研究-01-正念冥想有用吗?对健康的影响及科学指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; &#x1f680; AI篇持续更新中&#xff01;&#xff08;长期更新&#xff09; 目前2025年06月05日更新到&#xff1a; AI炼丹日志-28 - Aud…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

Qt的学习(一)

1.什么是Qt Qt特指用来进行桌面应用开发&#xff08;电脑上写的程序&#xff09;涉及到的一套技术Qt无法开发网页前端&#xff0c;也不能开发移动应用。 客户端开发的重要任务&#xff1a;编写和用户交互的界面。一般来说和用户交互的界面&#xff0c;有两种典型风格&…...

【若依】框架项目部署笔记

参考【SpringBoot】【Vue】项目部署_no main manifest attribute, in springboot-0.0.1-sn-CSDN博客 多一个redis安装 准备工作&#xff1a; 压缩包下载&#xff1a;http://download.redis.io/releases 1. 上传压缩包&#xff0c;并进入压缩包所在目录&#xff0c;解压到目标…...