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点
- 通过
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);}
}
mIdleHandlers是一个列表,会保存每一个添加进来的IdleHandler
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
- 在
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的监控实现
- 从
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,比较多同学错误地认为,这个Handler的作用是主线程空闲状态时才执行它,那么用它做一些耗时操作也没所谓。可是IdleHandler在主线程的MessageQueue中,执行queueIdle()默认当然也是执行在主线程中的࿰…...
数学小课堂:数学和自然科学的关系(数学方法,让自然科学变成科学体系。)
文章目录 引言I 数学方法,让自然科学变成科学体系。1.1 天文学1.2 博物学1.3 化学1.4 医药学1.5 物理学II 自然科学的升华过程III 数学方法的意义引言 19世纪初,英国人把采用实验的方法,系统地构造和组织知识,解释和预测自然的学问称为科学。 科学研究的是自然现象和自然…...
[蓝桥杯 2020 省 A1] 分配口罩
思路比较容易想到,因为口罩全部只有15批,因此直接暴力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、缓存…...
一文带你了解,前端模块化那些事儿
文章目录前端模块化省流:chatGPT 总结一、参考资料二、发展历史1.无模块化引出的问题:横向拓展2.IIFE3.Commonjs(cjs)4.AMD引出的问题:5.CMD6.UMD7.ESM往期精彩文章前端模块化 省流:chatGPT 总结 该文章主要讲述了前端模块化的发展历史和各个…...
(六十五)大白话设计索引的时候,我们一般要考虑哪些因素呢?(中)
今天我们继续来说一下,在设计索引的时候要考虑哪些因素。之前已经说了,你设计的索引最好是让你的各个where、order by和group by后面跟的字段都是联合索引的最左侧开始的部分字段,这样他们都能用上索引。 但是在设计索引的时候还得考虑其他的…...
Spring事务管理
文章目录1 事务1.1 需求1.2 原因分析1.3 错误解决1.4 yml配置文件中开启事务管理日志1 事务 1.1 需求 当部门解散了不仅需要把部门信息删除了,还需要把该部门下的员工数据也删除了。可当在删除员工数据出现异常时,就不会执行删除员工操作,出…...
数字化工厂装配线生产管理看板系统
电力企业业务复杂,组织结构复杂,不同的业务数据,管理要求也不尽相同。生产管理看板系统针对制造企业的生产应用而开发,能够帮助企业建立一个规范准确即时的生产数据库。企业现状:1、计划不清晰:生产计划不能…...
vxe-grid 全局自定义filter过滤器,支持字典过滤
一、vxe-table的全局筛选器filters的实现 官网例子:https://vxetable.cn/#/table/renderer/filter 进入之后:我们可以参照例子自行实现,也可以下载它的源码,进行调整 下载好后并解压,用vscode将解压后的文件打开。全局…...
ECharts 环形图组件封装
一、ECharts引入1.安装echarts包npm install echarts --save2.引入echarts这里就演示全局引入了,挂载到vue全局,后面使用时,直接使用 $echartsimport * as echarts from echarts Vue.prototype.$echarts echarts二、写echarts组件这里演示环…...
c++ 怎么调用python 提供的函数接口
在 C 中调用 Python 函数有多种方法,以下是其中的两种常见方法:使用 Python/C APIPython 提供了 C/C API,可以通过该 API 在 C 中调用 Python 函数。使用这种方法,需要先将 Python 解释器嵌入到 C 代码中,然后可以通过…...
【动态规划】背包问题(01背包,完全背包)
Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法......感兴趣就关注我吧!你定不会失望。 🌈个人主页:主页链接 🌈算法专栏:专栏链接 我会一直往里填充内容哒! &…...
记录 UE5 完全重新构建 UE C++项目
不知道搞了什么,C项目的实时代码编译罢工了,搞了半天都修不好,只能又重建了 UE5 版本为 v5.1.1 删除以下文件夹 /Binaries /Intermediate /SavedBinaries 文件夹是编译后的模块 Intermediate 文件夹里是中间层的C代码,完全由ue…...
java版云HIS系统源码 微服务架构支持VUE
云his系统源码 一个好的HIS系统,要具有开放性,便于扩展升级,增加新的功能模块,支撑好医院的业务的拓展,而且可以反过来给医院赋能,最终向更多的患者提供更好地服务。 私信了解更多! 本套基于…...
苹果内购支付检验错误码
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概念 层叠样式表(英文全称:Cascading Style Sheets)是一种用来表现HTML(标准通…...
智慧赋能,聚力开源——第四届OpenI/O 启智开发者大会开源治理专场顺利举办!
为汇聚国内外知名开源组织共同探讨中国开源生态建设及开源治理相关议题,推进产学研用开源合作,2月24日下午,第四届OpenI/O启智开发者大会在深圳人才研修院智汇中心举办以“构建开源联合体,共建开源生态”为主题的开源治理专场分论…...
Java工程师应该如何成长?
近几年,不少开发者会抱怨“面试造火箭,天天拧螺丝”,每天进行重复业务开发,似乎能力被日常工作限制,无法突破提高。 极客时间《Java 核心技术 36 讲》专栏作者杨晓峰认为,如果处于新手阶段,全面…...
【数据分析师求职面试指南】必备编程技能整理之Hive SQL必备用法
文章目录熟悉Python懂R语言掌握SQL大数据基础数据库常用类型多表查询更多聚合函数distinctcase when窗口函数动态更新一行变多行调优内容整理自《拿下offer 数据分析师求职面试指南》—徐粼著 第四章编程技能考查其他内容:【数据分析师求职面试指南】必备基础知识整…...
Maven - Linux 服务器 Maven 环境安装与测试
目录 一.引言 二.安装流程 1.获取安装包 2.解压并安装 3.配置环境 4.mvn 验证 三.测试踩坑 1.Permission denied 2.Plugin or dependencies Error 一.引言 通道机上的 java 项目需要 mvn package 提示没有 mvn 命令,下面记录下安装 maven 的全过程。 二.安…...
大数据-253 离线数仓 - Airflow 入门与任务调度实战:DAG、Operator、Executor 部署排错指南
TL;DR 场景:面向离线数仓与定时任务场景,快速理解 Airflow 的核心概念、DAG 编排方式与基础命令。结论:本文内容适合作为 Airflow 入门示例,但代码与命令明显偏旧,需区分 Airflow 1.x 与 2.x 版本差异。产出ÿ…...
OpenClaw是什么?OpenClaw能做什么?OpenClaw详细介绍及保姆级部署教程-周红伟
1. 什么是 OpenClaw? 1.1 核心定义 OpenClaw(前身为 Clawdbot/Moltbot)是一款开源、本地优先、可执行任务的 AI 自动化代理引擎,遵循 MIT 协议。它以自然语言指令为驱动,在本地或私有云环境中完成文件操作、流程编排…...
终极指南:5分钟掌握Piper鼠标地图组件与SVG渲染核心技术
终极指南:5分钟掌握Piper鼠标地图组件与SVG渲染核心技术 【免费下载链接】piper GTK application to configure gaming devices 项目地址: https://gitcode.com/gh_mirrors/pip/piper Piper是一款功能强大的GTK应用程序,专为配置游戏设备而设计。…...
嵌入式开发代码版本比较工具与技巧
1. 嵌入式开发中的代码版本差异查看方法在嵌入式开发过程中,代码版本管理是每个工程师必须掌握的核心技能。随着项目迭代和功能更新,我们经常需要比较不同版本代码之间的差异,无论是为了代码审查、问题排查还是版本合并。作为一名嵌入式开发者…...
DeEAR语音情感三维建模:如何用DeEAR输出可量化的Arousal-Nature-Prosody指标
DeEAR语音情感三维建模:如何用DeEAR输出可量化的Arousal-Nature-Prosody指标 1. 语音情感分析的新维度 传统语音情感识别系统通常只能识别"喜怒哀乐"等基础情绪,而DeEAR(Deep Emotional Expressiveness Recognition)系统通过wav2vec2深度学习…...
用Python+Matplotlib动手验证:标准DH和改进DH建模同一机械臂,结果真的相同吗?
PythonMatplotlib实战:标准DH与改进DH建模机械臂的等价性验证 机械臂运动学建模是机器人学中的基础课题,而Denavit-Hartenberg(DH)参数法则是其中最经典的建模方法之一。标准DH(sDH)与改进DH(mD…...
学浪视频下载终极方案:Fiddler+N_m3u8D联动配置避坑指南
学浪视频高效下载实战:Fiddler与N_m3u8D深度配置指南 在知识付费盛行的时代,学浪平台汇聚了大量优质课程资源。对于需要反复学习或离线观看的用户而言,掌握一套稳定高效的视频下载方法显得尤为重要。本文将深入探讨如何通过Fiddler抓包工具与…...
MacOS上Rust安装全攻略:从权限问题到成功验证(附常见错误解决)
MacOS上Rust安装全攻略:从权限问题到成功验证 最近两年Rust在开发者社区的热度持续攀升,Stack Overflow的年度调查显示它已经连续七年成为"最受喜爱编程语言"。但对于刚接触Rust的Mac用户来说,安装过程可能会遇到一些棘手的权限问题…...
iOS设备安全定制指南:使用Cowabunga Lite实现零风险个性化配置
iOS设备安全定制指南:使用Cowabunga Lite实现零风险个性化配置 【免费下载链接】CowabungaLite iOS 15 Customization Toolbox 项目地址: https://gitcode.com/gh_mirrors/co/CowabungaLite iOS系统的封闭性常让用户陷入个性化与安全性的两难选择——越狱虽能…...
MacBook Pro用户必看:5分钟搞定StarUML破解(M1/M2芯片专用指南)
M1/M2芯片MacBook高效配置StarUML全流程指南 当你在M1/M2芯片的MacBook上第一次打开StarUML时,可能会遇到各种兼容性问题。作为一款强大的UML建模工具,StarUML在ARM架构下的表现确实有些水土不服。但别担心,经过多次实践,我总结出…...
