当前位置: 首页 > 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
http://www.lryc.cn/news/35539.html

相关文章:

  • 数学小课堂:数学和自然科学的关系(数学方法,让自然科学变成科学体系。)
  • [蓝桥杯 2020 省 A1] 分配口罩
  • 第五章:C语言数据结构与算法之双向带头循环链表
  • 一文带你了解,前端模块化那些事儿
  • (六十五)大白话设计索引的时候,我们一般要考虑哪些因素呢?(中)
  • Spring事务管理
  • 数字化工厂装配线生产管理看板系统
  • vxe-grid 全局自定义filter过滤器,支持字典过滤
  • ECharts 环形图组件封装
  • c++ 怎么调用python 提供的函数接口
  • 【动态规划】背包问题(01背包,完全背包)
  • 记录 UE5 完全重新构建 UE C++项目
  • java版云HIS系统源码 微服务架构支持VUE
  • 苹果内购支付检验错误码
  • day27_css
  • 智慧赋能,聚力开源——第四届OpenI/O 启智开发者大会开源治理专场顺利举办!
  • Java工程师应该如何成长?
  • 【数据分析师求职面试指南】必备编程技能整理之Hive SQL必备用法
  • Maven - Linux 服务器 Maven 环境安装与测试
  • 5G模块可以注册到4G,不能注册到5G;SIM卡接到5G手机是可以注册到5G网络的?
  • 宝塔webhook自动化打包vue项目时,npm不生效问题
  • 嵌入式 Linux进程间通信之信号量
  • 谷粒学院开发(一):基础准备
  • Photoshop如何安装ZXP扩展插件?
  • c++面试技巧-基础篇4
  • openEuler用户软件仓(EUR)介绍
  • MySQL的图形化界面开发工具DataGrip的下载安装
  • Azure Portal 访问安全性增强
  • mysql安全值守数据库常用语句
  • CSS快速入门