钉钉 ANR 治理最佳实践 | 定位 ANR 不再雾里看花

作者:姜凡(步定)

本文为《钉钉 ANR 治理最佳实践》系列文章首篇《定位 ANR 不再雾里看花》,主要介绍了钉钉自研的 ANRCanary 通过监控主线程的执行情况,为定位 ANR 问题提供更加丰富的信息。

后续将在第二篇文章中讲述钉钉基于分析算法得出 ANR 归因,上报到 ANR 归因监控平台,帮助研发人员更快更准确的解决 ANR 问题,并总结钉钉 ANR 实战踩坑与经验总结

相信大家对 Android 的 ANR 问题并不陌生。钉钉作为一个用户数超 5 亿,服务着 2100 万家组织的产品,基本上其他 App 遇到的 ANR 问题,我们都会遇到。

和大家一样,我们最初在分析 ANR Trace 日志的时候,都会不禁怀疑上报的堆栈是否真的有问题,总有一种雾里看花的感觉。

本系列文章主要介绍钉钉在 ANR 治理过程中的思考方向,工具建设,典型问题等,希望能够通过本次分享,为有 ANR 治理诉求的团队提供一定的参考。

术语表

系统 ANR 完整流程

系统 ANR 完整流程可以分为如下三个部分:

  • 超时检测
  • ANR 信息收集
  • ANR 信息输出

对于超时检测的逻辑,业界已经有比较详细的阐述,此处不再赘述。重点聊聊检测到超时之后的处理逻辑。详细源码可以参见:ProcessRecord.java,ANR 信息收集和 ANR 信息输出两个流程图如下:

如上图所示,从系统源码中,得到的启示有:

  • ANR Trace 的堆栈抓取时机是滞后的,其堆栈不一定是 ANR 根因。
  • System Server 会对多个进程发送 SIGQUIT 信号,请求堆栈抓取的操作。
    • 收到 SIGQUIT 不代表当前进程发生了 ANR ,可能是手机里有一个其他的 App 发生了 ANR,如果不进行 ANR 的二次确认,就会导致 ANR 误报。
  • App 可以通过进程 ANR 错误状态感知发生了前台 ANR 。

刻舟求剑的 ANR Trace

  • 以广播发送导致 ANR 的过程为例,当 System Server 进程检测到广播处理超时时,会发送SIGQUIT 信号到 App 进程, App 进程收到信号之后,会将当前所有线程的执行堆栈 Dump 下来为 ANR Trace,并最终输出。
  • 然而如图所示,这个 Dump 时机是有一定的滞后性的,真正导致 ANR 的 长耗时消息3 已经执行完了。当前执行消息5 是作为替罪羊被抓到的,甚至 当前执行消息5 到底消耗了多长时间也不确定。因此 Android 系统设计提供的用来分析 ANR 问题的 ANR Trace ,其实只是刻舟求剑, 并不一定能定位到 ANR 的根因。

ANR 误报过滤

鉴于前面提到的收到 SIGQUIT 信号,并不代表当前进程发生了 ANR,需要一个二次确认逻辑,进行误报过滤。

钉钉采用的方案是:

  • 在收到 SIGQUIT 信号之后,在 20 秒内轮询进程错误状态的确认是否为前台 ANR。
  • 与此同时,因为发生后台 ANR 之后,系统会直接杀进程,而其他进程 ANR 并不会导致进程被杀,因此可以通过持久化的方案来区分。

详细流程图如下:

ANR 监控工具

工欲善其事,必先利其器。钉钉自研的 ANRCanary 监控工具,通过轮询的方式持续记录主线程最新任务的执行耗时,到发生 ANR 时,基于耗时最长的消息定位 ANR

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ANR trace 中定位 Binder 对端可以通过查看 trace 文件中的堆栈信息来实现。在 trace 文件中,可以找到类似以下的信息: ``` DALVIK THREADS: (mutexes: tll=0 tsl=0 tscl=0 ghl=0) "main" prio=5 tid=1 SUSPENDED | group="main" sCount=1 dsCount=0 obj=0x756d7d68 self=0x756c9d48 | sysTid=21521 nice=0 cgrp=default sched=0/0 handle=0x756e0e90 | state=S schedstat=( 0 0 0 ) utm=327 stm=128 core=3 HZ=100 | stack=0xbe1c3000-0xbe1c5000 stackSize=8MB | held mutexes= at com.example.demo.MainActivity.hangup(MainActivity.java:33) - waiting to lock <0x19a4ef7c> (a com.example.demo.MainActivity) held by thread 2 at com.example.demo.MainActivity.access$000(MainActivity.java:13) at com.example.demo.MainActivity$1.onClick(MainActivity.java:25) at android.view.View.performClick(View.java:5184) at android.view.View$PerformClick.run(View.java:20910) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:145) at android.app.ActivityThread.main(ActivityThread.java:5942) at java.lang.reflect.Method.invoke(Native Method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1399) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1194) "Binder:21521_1" pri=5 tid=2 RUNNING | group="main" sCount=1 dsCount=0 obj=0x135e8d58 self=0x757aa000 | sysTid=21524 nice=0 cgrp=default sched=0/0 handle=0x757a2c00 | state=R schedstat=( 0 0 0 ) utm=0 stm=0 core=6 HZ=100 | stack=0x7579b000-0x7579d000 stackSize=1012KB | held mutexes= at android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(Binder.java:496) at android.app.ActivityManagerProxy.handleApplicationNotResponding(ActivityManagerNative.java:3934) at com.android.internal.os.RuntimeInit$UncaughtHandler.uncaughtException(RuntimeInit.java:96) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:693) at java.lang.ThreadGroup.uncaughtException(ThreadGroup.java:690) ``` 可以看到,线程 "Binder:21521_1" 在运行,并且持有了某个 mutex,但是没有释放。这时可以通过查看此线程的堆栈信息,发现它是在调用 `android.os.BinderProxy.transactNative` 方法进行 Binder 通信的。从这里可以推断出,线程 "Binder:21521_1" 所在的进程就是 Binder 对端。 在实际应用中,可以通过这种方法来定位 ANR 错误的原因,并分析出问题所在,并进行修复。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值