起言
最近debug应用时,发现只要看debug的变量内容太久了,app也会爆出anr问题,遂想对ANR做一个系统的整理。
正文
导致ANR
导致ANR的直观原因在Android官网已经说的很清楚了
---- 引用自ANR | App quality | Android Developers
出现以下任何情况时,系统都会针对您的应用触发 ANR:
- 输入调度超时:如果您的应用在 5 秒内未响应输入事件(例如按键或屏幕触摸)。
- 执行服务 :如果应用声明的服务无法在几秒内完成
Service.onCreate()
和Service.onStartCommand()
/Service.onBind()
执行。 - 未调用 Service.startForeground() :如果您的应用使用
Context.startForegroundService()
在前台启动新服务,但该服务在 5 秒内未调用startForeground()
。 - intent 广播 :如果 BroadcastReceiver 在设定的一段时间内没有执行完毕。如果应用有任何前台 activity,此超时期限为 5 秒。
- JobScheduler 互动 :如果 JobService 未在几秒钟内从
JobService.onStartJob()
或JobService.onStopJob()
返回,或者如果用户发起的作业启动,而您的应用在调用JobService.onStartJob()
后的几秒内未调用JobService.setNotification()
。对于以 Android 13 及更低版本为目标平台的应用,ANR 会保持静默状态,且不会报告给应用。对于以 Android 14 及更高版本为目标平台的应用,ANR 会保持活动状态,并会报告给应用。
甚至连如何查找ANR的考量因素Google大大都为我们考虑到了,干,此文结束
治理ANR
诊断 ANR 时需要考虑以下几种常见模式:
- 应用在主线程上非常缓慢地执行涉及 I/O 的操作。
- 应用在主线程上进行长时间的计算。
- 主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。
- 主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。
- 主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。如需了解详情,请参阅维基百科上的死锁。
但是,比起这些,我更好奇的是Android官方是如何检测到ANR的发生的?要知道,ANR发生如上文所述涉及到多个组件,Google有一套通用的实现吗
监测ANR
答案就是Handler,实际上Android系统内部很多地方都用到了handler,要写出优秀的设计,很多时候都可以考虑使用handler。
可以发现,通用套路就是向Handler发送了一个延迟消息,内容为执行ANR逻辑,如果事件被消费了,就把这个延迟消息取消掉,如果没有被消费,则消息不被取消,执行ANR产生逻辑。
示例代码如下:
java
private Runnable anrRunnable = new Runnable() {
@Override
public void run() {
Log.e(TAG, "ANR detected!");
}
};
public void startDoSomething() {
// 启动服务、、处理事件、、
handler.postDelayed(anrRunnable, ANR_TIMEOUT);
}
public void finishDoSomething() {
// 做完了,可以取消了
handler.removeCallbacks(anrRunnable);
// handler.removeMessages(TAG);
}
文中没有的内容补充:
对于输入事件,第一个输入事件产生后,又产生了第二个输入事件,此时若过0.5s了第一个输入事件还没有处理完毕,则设置5s的计时器,5s后第一个输入还未处理完毕,则产生ANR。
ANR后发生了什么
- 发送 SIGQUIT 信号:system_server 进程检测到ANR后,向应用程序进程发送 SIGQUIT 信号。
- 接收 SIGQUIT 信号:应用程序进程中的 libart.so 处理该信号。
- 调用 dump 方法:libart.so 调用Java虚拟机的 dump 方法生成线程转储。
- 生成 traces.txt 文件:线程转储信息被写入 traces.txt 文件,用于分析和调试ANR问题。
结语
Android官方的很多设计都值得学习,不知道大家了解到安卓检测ANR的方式后有咩有感到惊讶呢。