Android 卡死如何监控?
The following article is from 程序员江同学 Author 程序员江同学
前言
3. 如何定位 ANR 原因?
发生 ANR 后,系统会采集许多进程数据,进行堆栈转储,以生成 ANR Trace文件。其中,第一个被采集的进程必定是发生 ANR 的进程。 系统会向这些应用进程发送 SIGQUIT 信号,这些应用进程收到信号后开始进行堆栈转储。 应用进程 Dump 堆栈成功后通过 Socket 与系统进程通信写 Trace 文件。 在 Trace 文件写入完成后,如果发生 ANR 的进程是前台进程则弹出 Dialog,否则则直接杀死进程。
ANR WatchDog 检测思路
不准确,超时条件不一定会导致 ANR,例如,5 秒超时只是在 TouchEvent 未被消耗时发生 ANR 的条件之一,而其他条件则不一定是 5 秒。 漏检测:如果超时时间定为 5 秒,去检测 TouchEvent 的 ANR 存在一定的漏检测的概率(周期不同步)。
ANR 信号监听思路
误报情况处理
比如可能是其它进程 ANR 了,发生 ANR 的进程不是唯一需要进行堆栈转储的进程。系统会收集许多其他进程进行堆栈转储,用于生成 ANR Trace 文件。 厂商或者是开发者自己发送的SIGQUIT信号,发送SIGQUIT信号其实是很容易的一件事情。
private static boolean checkErrorState() {
try {
Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
if (procs == null) return false;
for (ActivityManager.ProcessErrorStateInfo proc : procs) {
if (proc.pid != android.os.Process.myPid()) continue;
if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
return true;
}
return false;
} catch (Throwable t){
MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
}
return false;
}
如上所示,我们可以在监听到信号时判断当前进程是否被标记为 NOT_RESPONDING 来判断当前进程是否发生了 ANR。
漏报情况处理
后台ANR(SilentAnr): 后台 ANR 会直接杀死进程,不会走到标记状态的代码。 厂商定制逻辑: 相当一部分机型(比如 OPPO、VIVO 两家的高版本 Android )修改了 ANR 的逻辑,即使是前台 ANR 也会直接杀死进程。
private static boolean isMainThreadStuck(){
try {
MessageQueue mainQueue = Looper.getMainLooper().getQueue();
Field field = mainQueue.getClass().getDeclaredField("mMessages");
field.setAccessible(true);
final Message mMessage = (Message) field.get(mainQueue);
if (mMessage != null) {
long when = mMessage.getWhen();
if(when == 0) {
return false;
}
long time = when - SystemClock.uptimeMillis();
long timeThreshold = BACKGROUND_MSG_THRESHOLD;
if (foreground) {
timeThreshold = FOREGROUND_MSG_THRESHOLD;
}
return time < timeThreshold;
}
} catch (Exception e){
return false;
}
return false;
}
通过反射获取主线程Looper的mMessage对象,该消息的when变量,就表示该消息的入队时间。 将入队时间与当前时间进行比较,就可以获取该消息的等待时间。 当等待时间超过一定阈值的话,我们就认为主线程处于阻塞状态,结合 SIGQUIT 信号,判断为发生了 ANR。
系统资源不足,其它进程或线程存在严重资源抢占,如 IO,Mem,CPU。 线程间存在资源抢占,比如死锁等。 主线程繁忙,用户输入得不到及时响应。
获取系统负载信息
val am = application.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val processesInErrorStates = am.processesInErrorState
# shortMessage
ANR Input dispatching timed out (8445a92 com.android.test/com.android.test.anr.ANRActivity (server) is not responding. Waited 5000ms for MotionEvent(action=DOWN))
# longMessage
ANR in com.android.test (com.android.test/.anr.ANRActivity)
PID: 23283
Reason: Input dispatching timed out (8445a92 com.android.test/com.android.test.anr.ANRActivity (server) is not responding. Waited 5000ms for MotionEvent(action=DOWN))
Parent: com.android.test/.anr.ANRActivity
ErrorId: 91ceb0ce-0af6-496e-8c4f-781075c056db
Frozen: false
Load: 0.0 / 0.29 / 0.33 # 表示 1, 5 和 15 分钟内的 CPU 平均负载
----- Output from /proc/pressure/memory -----
# avg10、avg60、avg300 分别代表 10s、60s、300s 的时间周期内因内存资源阻塞的时间百分比
# some 表示任一任务,full 表示所有非 idle 任务
some avg10=0.00 avg60=0.00 avg300=0.00 total=150136881
full avg10=0.00 avg60=0.00 avg300=0.00 total=51283028
----- End output from /proc/pressure/memory -----
CPU usage from 153ms to 605ms later (2023-05-04 22:38:19.034 to 2023-05-04 22:38:19.486):
79% 1990/system_server: 35% user + 43% kernel / faults: 1598 minor
43% 23375/AnrConsumer: 8.1% user + 35% kernel
21% 2008/HeapTaskDaemon: 19% user + 2.7% kernel
2.7% 2919/InputDispatcher: 2.7% user + 0% kernel
32% 23283/com.android.test: 16% user + 16% kernel / faults: 7 minor
28% 23315/RenderThread: 16% user + 12% kernel
4% 23306/binder:23283_3: 0% user + 4% kernel
4% 23354/binder:23283_5: 4% user + 0% kernel
17% 1195/surfaceflinger: 17% user + 0% kernel
10% 1195/surfaceflinger: 10% user + 0% kernel
2.5% 1347/binder:1195_1: 2.5% user + 0% kernel
2.5% 1414/TimerDispatch: 2.5% user + 0% kernel
5% 1071/vendor.qti.hardware.display.composer-service: 2.5% user + 2.5% kernel
2.5% 1071/composer-servic: 0% user + 2.5% kernel
//...
21% TOTAL: 10% user + 10% kernel + 0.8% irq + 0.2% softirq
获取进程内所有线程状态。
定位主线程问题
头条和虾皮都基于这种思路开发了一些 ANR 监控工具,详情可见:今日头条 ANR 优化实践系列 - 监控工具与分析思路与Android 卡顿与 ANR 的分析实践。
https://juejin.cn/post/6942665216781975582
https://juejin.cn/post/7136008620658917407
这些工具目前都没有开源,也有开发者基于头条的思路开源了一套实现,感兴趣的同学可以看看:app卡顿系列四 :今日头条卡顿监控方案落地。
https://juejin.cn/post/7031834640034103304
参考资料
微信Android客户端的ANR监控方案
https://mp.weixin.qq.com/s/fWoXprt2TFL1tTapt7esYg
今日头条 ANR 优化实践系列 - 监控工具与分析思路
https://juejin.cn/post/6942665216781975582
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
用 Kotlin写 gradle 脚本,与groovy有何不同?- Gradle探究
点击 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!