当前位置: 首页 > 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 的全过程。 二.安…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比

在机器学习的回归分析中&#xff0c;损失函数的选择对模型性能具有决定性影响。均方误差&#xff08;MSE&#xff09;作为经典的损失函数&#xff0c;在处理干净数据时表现优异&#xff0c;但在面对包含异常值的噪声数据时&#xff0c;其对大误差的二次惩罚机制往往导致模型参数…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

音视频——I2S 协议详解

I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议&#xff0c;专门用于在数字音频设备之间传输数字音频数据。它由飞利浦&#xff08;Philips&#xff09;公司开发&#xff0c;以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

MinIO Docker 部署:仅开放一个端口

MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...