关于android:手把手教你高效监控ANR

56次阅读

共计 7379 个字符,预计需要花费 19 分钟才能阅读完成。

ANR 监控是一个十分有年代感的话题了,然而市面上的 ANR 监控工具,或者并非真正意义上的 ANR 的监控(而是 5 秒卡顿监控);或者并不欠缺,监控不到到所有的 ANR。而想要失去一个欠缺的 ANR 监控工具,必须要先理解零碎整个 ANR 的流程。本文剖析了 ANR 的次要流程,给出了一个欠缺的 ANR 监控计划。该计划曾经在 Android 微信客户端上通过全量验证,稳固地运行了一年多的工夫。

咱们晓得 ANR 流程根本都是在 system_server 零碎过程实现的,零碎过程的行为咱们很难监控和扭转,想要监控 ANR 就必须找到零碎过程跟咱们本人的利用过程是否有交互,如果有,两者交互的边界在哪里,边界上利用一端的行为,才是咱们比拟容易能监控到的,想要要找到这个边界,咱们就必须要理解 ANR 的流程。

一、ANR 流程

无论 ANR 的起源是哪里,最终都会走到 ProcessRecord 中的appNotResponding,这个办法包含了 ANR 的次要流程,所以也比拟长,咱们找出一些要害的逻辑来剖析:frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java:

void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,

先是一长串 if else,给出了几种比拟极其的状况,会间接 return,而不会产生一个 ANR,这些状况包含:过程正在处于正在敞开的状态,正在 crash 的状态,被 kill 的状态,或者雷同过程曾经处在 ANR 的流程中。

另外很重要的一个逻辑就是判断以后 ANR 是否是一个 SilentAnr,所谓“缄默的 ANR”,其实就是后盾 ANR,后盾 ANR 跟前台 ANR 会有不同的体现:前台 ANR 会弹无响应的 Dialog,后盾 ANR 会间接杀死过程 。前后台 ANR 的判断的准则是: 如果产生 ANR 的过程对用户来说是有感知的,就会被认为是前台 ANR,否则是后盾 ANR。另外,如果在开发者选项中勾选了“显示后盾 ANR”,那么全副 ANR 都会被认为是前台 ANR。

咱们持续剖析这个办法:

if (!isSilentAnr && !onlyDumpSelf) {

产生 ANR 后,为了能让开发者晓得 ANR 的起因,不便定位问题,会 dump 很多信息到 ANR Trace 文件里,下面的逻辑就是抉择须要 dump 的过程。ANR Trace 文件是蕴含 许多过程 的 Trace 信息的,因为产生 ANR 的起因有可能是其余的过程抢占了太多资源,或者 IPC 到其余过程(尤其是零碎过程)的时候卡住导致的。

抉择须要 dump 的过程是一段挺有意思逻辑,咱们略微剖析下:须要被 dump 的过程被分为了 firstPids、nativePids 以及 extraPids 三类:

  • firstPIds:firstPids 是须要首先 dump 的重要过程,产生 ANR 的过程无论如何是肯定要被 dump 的,也是首先被 dump 的,所以第一个被加到 firstPids 中。如果是 SilentAnr(即后盾 ANR),不必再退出任何其余的过程。如果不是,须要进一步增加其余的过程:如果产生 ANR 的过程不是 system_server 过程的话,须要增加 system_server 过程;接下来轮询 AMS 保护的一个 LRU 的过程 List,如果最近拜访的过程蕴含了 persistent 的过程,或者带有 BIND_TREAT_LIKE_ACTVITY 标签的过程,都增加到 firstPids 中。
  • extraPids:LRU 过程 List 中的其余过程,都会首先增加到 lastPids 中,而后 lastPids 会进一步被选出最近 CPU 使用率高的过程,进一步组成 extraPids;
  • nativePids:nativePids 最为简略,是一些固定的 native 的零碎过程,定义在 WatchDog.java 中。

拿到须要 dump 的所有过程的 pid 后,AMS 开始依照 firstPids、nativePids、extraPids 的程序 dump 这些过程的堆栈:

File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,

这里也是咱们须要重点剖析的中央,咱们持续看这里做了什么,跟到 AMS 外面,

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public static Pair<Long, Long> dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,

咱们首先关注到remainingTime,这是一个重要的变量,规定了咱们 dump 所有过程的最长工夫,因为 dump 过程所有线程的堆栈,自身就是一个重操作,何况是要 dump 许多过程,所以规定了产生 ANR 之后,dump 全副过程的总工夫不能超过 20 秒,如果超过了,马上返回,确保 ANR 弹窗能够及时的弹出(或者被 kill 掉)。咱们持续跟到dumpJavaTracesTombstoned

private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {

再一路追到 native 层负责 dump 堆栈的system/core/debuggerd/client/debuggerd_client.cpp

bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) {

来了来了!之前说的交互边界终于找到了!这里会通过 sigqueue 向须要 dump 堆栈的过程发送 SIGQUIT 信号,也就是 signal 3 信号,而产生 ANR 的过程是肯定会被 dump 的,也是第一个被 dump 的。这就意味着,只有咱们能监控到零碎发送的 SIGQUIT 信号,兴许就可能监控到产生了 ANR。

每一个利用过程都会有一个 SignalCatcher 线程,专门解决 SIGQUIT,来到art/runtime/signal_catcher.cc

void* SignalCatcher::Run(void* arg) {

WaitForSignal办法调用了 sigwait 办法,这是一个阻塞办法。这里的死循环,就会始终一直的期待监听 SIGQUIT 和 SIGUSR1 这两个信号的到来。

整顿一下 ANR 的过程:当利用产生 ANR 之后,零碎会收集许多过程,来 dump 堆栈,从而生成 ANR Trace 文件,收集的第一个,也是肯定会被收集到的过程,就是产生 ANR 的过程,接着零碎开始向这些利用过程发送 SIGQUIT 信号,利用过程收到 SIGQUIT 后开始 dump 堆栈。来简略画个示意图:

所以,事实上过程产生 ANR 的整个流程,也只有 dump 堆栈的行为会在产生 ANR 的过程中执行。这个过程从收到 SIGQUIT 开始(圈 1),到应用 socket 写 Trace(圈 2)完结,而后再持续回到 server 过程实现残余的 ANR 流程。咱们就在这两个边界上做做文章。

首先咱们必定会想到,咱们是否监听到 syste_server 发送给咱们的 SIGQUIT 信号呢?如果能够,咱们就胜利了一半。

二、监控 SIGQUIT 信号

Linux 零碎提供了两种监听信号的办法,一种是 SignalCatcher 线程应用的 sigwait 办法进行同步、阻塞地监听,另一种是应用 sigaction 办法注册 signal handler 进行异步监听,咱们都来试试。

2.1. sigwait

咱们首先尝试前一种办法,模拟 SignalCatcher 线程,做截然不同的事件,通过一个死循环sigwait,始终监听 SIGQUIT:

static void *mySigQuitCatcher(void* args) {

这个时候就有了两个不同的线程 sigwait 同一个 SIGQUIT,具体会走到哪个呢,咱们在 sigwait 的文档中找到了这样的形容(sigwait办法是由 sigwaitinfo 办法实现的):

原来 当有两个线程通过 sigwait 办法监听同一个信号时,具体是哪一个线程收到信号时不能确定的。不确定可不行,当然不满足咱们的需要。

3.2. Signal Handler

那咱们再试下另一种办法是否可行,咱们通过能够 sigaction 办法,建设一个 Signal Handler:

void signalHandler(int sig, siginfo_t* info, void* uc) {

建设了 Signal Handler 之后,咱们发现在同时有 sigwait 和 signal handler 的状况下,信号没有走到咱们的 signal handler 而是仍然被零碎的 Signal Catcher 线程捕捉到了,这是什么起因呢?

原来是 Android 默认把 SIGQUIT 设置成了 BLOCKED,所以只会响应 sigwait 而不会进入到咱们设置的 handler 办法中。咱们通过 pthread_sigmask 或者 sigprocmask 把 SIGQUIT 设置为 UNBLOCK,那么再次收到 SIGQUIT 时,就肯定会进入到咱们的 handler 办法中。须要这样设置:

sigset_t sigSet;

最初须要留神,咱们通过 Signal Handler 抢到了 SIGQUIT 后,本来的 Signal Catcher 线程中的 sigwait 就不再能收到 SIGQUIT 了,本来的 dump 堆栈的逻辑就无奈实现了,咱们为了 ANR 的整个逻辑和流程跟原来完全一致,须要在 Signal Handler 外面从新向 Signal Catcher 线程发送一个 SIGQUIT

int tid = getSignalCatcherThreadId(); // 遍历 /proc/[pid]目录,找到 SignalCatcher 线程的 tid

(如果短少了从新向 SignalCatcher 发送 SIGQUIT 的步骤,AMS 就始终等不到 ANR 过程写堆栈,直到 20 秒超时后,才会被迫中断,而持续之后的流程。间接的体现就是 ANR 弹窗十分慢(20 秒超时工夫),并且 /data/anr 目录下无奈失常生成残缺的 ANR Trace 文件。)

以上就失去了一个不扭转零碎行为的前提下,比较完善的监控 SIGQUIT 信号的机制,这也是咱们监控 ANR 的根底。

三、欠缺的 ANR 监控计划

监控到 SIGQUIT 信号并不等于就监控到了 ANR。

3.1. 误报

充沛非必要条件 1:产生 ANR 的过程肯定会收到 SIGQUIT 信号;然而收到 SIGQUIT 信号的过程并不一定产生了 ANR。

思考上面两种状况:

  • 其余过程的 ANR:下面提到过,产生 ANR 之后,产生 ANR 的过程并不是惟一须要 dump 堆栈的过程,零碎会收集许多其余的过程进行 dump,也就是说当一个利用产生 ANR 的时候,其余的利用也有可能收到 SIGQUIT 信号。进一步,咱们监控到 SIGQUIT 时,可能是监听到了其余过程产生的 ANR,从而产生误报。
  • 非 ANR 发送 SIGQUIT:发送 SIGQUIT 信号其实是很容易的一件事件,开发者和厂商都能够很容易的发送一个 SIGQUIT(java 层调用 android.os.Process.sendSignal 办法;Native 层调用 kill 或者 tgkill 办法),所以咱们可能会收到非 ANR 流程发送的 SIGQUIT 信号,从而产生误报。

怎么解决这些误报的问题呢,我从新回到 ANR 流程开始的中央:

void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,

在 ANR 弹窗前,会执行到 makeAppNotRespondingLocked 办法中,在这里会给产生 ANR 过程标记一个 NOT_RESPONDING 的 flag。而这个 flag 咱们能够通过 ActivityManager 来获取:

private static boolean checkErrorState() {

监控到 SIGQUIT 后,咱们在 20 秒内(20 秒是 ANR dump 的 timeout 工夫)一直轮询本人是否有 NOT_RESPONDING 对 flag,一旦发现有这个 flag,那么马上就能够认定产生了一次 ANR。

(你可能会想,有这么不便的办法,监控 SIGQUIT 信号不是多余的吗?间接一个死循环,一直轮训这个 flag 不就完事了?是的,实践上的确能这么做,然而这么做过于的低效、耗电和不环保外,更要害的是,上面漏报的问题仍然无奈解决)

另外,Signal Handler 回调的第二个参数 siginfo_t,也蕴含了一些有用的信息,该构造体的第三个字段 si_code 示意该信号被发送的办法,SI_USER 示意信号是通过 kill 发送的,SI_QUEUE 示意信号是通过 sigqueue 发送的。但在 Android 的 ANR 流程中,高版本应用的是 sigqueue 发送的信号,某些低版本应用的是 kill 发送的信号,并不对立。

而第五个字段(极少数机型上是第四个字段)si_pid 示意的是发送该信号的过程的 pid,这里实用简直所有 Android 版本和机型的一个条件是:如果发送信号的过程是本人的过程,那么肯定不是一个 ANR。能够通过这个条件排除本人发送 SIGQUIT,而导致误报的状况。

3.2. 漏报

充沛非必要条件 2:过程处于 NOT_RESPONDING 的状态能够确认该过程产生了 ANR。然而产生 ANR 的过程并不一定会被设置为 NOT_RESPONDING 状态。

思考上面两种状况:

  • 后盾 ANR(SilentAnr):之前剖析 ANR 流程咱们能够晓得,如果 ANR 被标记为了后盾 ANR(即 SilentAnr),那么杀死过程后就会间接 return,并不会走到产生过程谬误状态的逻辑。这就意味着,后盾 ANR 没方法捕捉到,而后盾 ANR 的量同样十分大,并且后盾 ANR 会间接杀死过程,对用户的体验也是十分负面的,这么大一部分 ANR 监控不到,当然是无奈承受的。
  • 闪退 ANR:除此之外,咱们还发现相当一部分机型(例如 OPPO、VIVO 两家的高 Android 版本的机型)批改了 ANR 的流程,即便是产生在前台的 ANR,也并不会弹窗,而是间接杀死过程,即闪退。这部分的机型笼罩的用户量也十分大。并且,确定两家今后的新设施会始终维持这个机制。

所以咱们须要一种办法,在收到 SIGQUIT 信号后,可能十分疾速的侦察出本人是不是已处于 ANR 的状态,进行疾速的 dump 和上报。很容易想到,咱们能够通过主线程是否处于卡顿状态来判断。那么怎么最疾速的晓得主线程是不是卡住了呢?上一篇文章中,剖析 Sync Barrier 透露问题时,咱们反射过主线程 Looper 的 mMessage 对象,该对象的 when 变量,示意的就是以后正在解决的音讯入队的工夫,咱们能够通过 when 变量减去以后工夫,失去的就是等待时间,如果等待时间过长,就阐明主线程是处于卡住的状态,这时候收到 SIGQUIT 信号基本上就能够认为确实产生了一次 ANR:

private static boolean isMainThreadStuck(){

咱们通过下面几种机制来综合判断收到 SIGQUIT 信号后,是否真的产生了一次 ANR,最大水平地缩小误报和漏报,才是一个比较完善的监控计划。

3.3. 额定播种:获取 ANR Trace

回到之前画的 ANR 流程示意图,Signal Catcher 线程写 Trace(圈 2)也是一个边界,并且是通过 socket 的 write 办法来写 Trace 的,如果咱们可能 hook 到这里的 write,咱们甚至就能够拿到零碎 dump 的 ANR Trace 内容 。这个内容十分全面,包含了所有线程的各种状态、锁和堆栈(包含 native 堆栈),对于咱们排查问题非常有用, 尤其是一些 native 问题和死锁等问题。Native Hook 咱们采纳 PLT Hook 计划,这种计划在微信上曾经被验证了其稳定性是可控的。

int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);

其中有几点须要留神:

  • 只 Hook ANR 流程:有些状况下,根底库中的 connect/open/write 办法可能调用的比拟频繁,咱们须要把 hook 的影响降到最低。所以咱们只会在接管到 SIGQUIT 信号后(从新发送 SIGQUIT 信号给 Signal Catcher 前)进行 hook,ANR 流程完结后再 unhook。
  • 只解决 Signal Catcher 线程 open/connect 后的第一次 write:除了 Signal Catcher 线程中的 dump trace 的流程,其余中央调用的 write 办法咱们并不关怀,并不需要解决。例如,dump trace 的流程会在在 write 办法前,零碎会先应用 connet 办法链接一个 path 为“/dev/socket/tombstoned_java_trace”的 socket,咱们能够 hook connect 办法,拿到这个 socket 的 name,咱们只解决 connect 这个 socket 后,雷同线程(即 Signal Catcher 线程)的第一次 write,这次 write 的内容才是咱们惟一关怀的。
  • Hook 点因 API Level 而不同:须要 hook 的 write 办法在不同的 Android 版本中,所在的 so 也不尽相同,不同 API Level 须要别离解决,hook 不同的 so 和办法。目前这个计划在 API 18 以上都测试过可行。

这个 Hook Trace 的计划,不仅仅能够用来查 ANR 问题,任何时候咱们都能够手动向本人发送一个 SIGQUIT 信号,从而 hook 到过后的 Trace。Trace 的内容对于咱们排查线程死锁,线程异样,耗电等问题都十分有帮忙

这样咱们就失去了一个欠缺的 ANR 监控计划,这套计划在微信上安稳运行了很长一段时间,给咱们评估和优化微信 Android 客户端的品质提供了十分重要依据和方向。

关注我,每天分享常识干货

正文完
 0