1ANRSystmHandle

ANR 设计原理

组件超时分类

Broadcast 超时原理举例

ANR Trace Dump流程

图解

sequenceDiagram

SystemServer->>SystemServer: AppNotResponding.run
activate SystemServer

Note right of SystemServer: 1.非异常场景下,统计进程信息,包括:
Note right of SystemServer: 发生ANR的进程,SystemServer进程,SurfaceFligner进程,CPU占用高的5个进程

Note right of SystemServer: 2.打印ANR顶部信息

被统计的进程->>被统计的进程: 虚拟机启动过程在SignalCatcher进行信号注册和监听(SIGQUIT)

SystemServer->>被统计的进程: 3.逐个向统计的进程发送SIGQUIT信号,通知dump进程信息

被统计的进程->>被统计的进程: TheadList::DumpForSigQuit
activate 被统计的进程
Note right of 被统计的进程: 遍历所有的Java线程和虚拟机线程,设置checkpoint,请求挂起suspend
deactivate 被统计的进程
被统计的进程->>被统计的进程: 所有线程挂起后,SignalCatcher线程开始遍历Dump各线程的堆栈和线程数据,结束之后再唤醒线程

SystemServer->>SystemServer: 监听管道并读取管道内容,写入Trace文件

deactivate SystemServer

参考signalcatcher获取各线程信息的工作过程

Capture ANR

capture anr

ANR 信息写入

继续以广播接收为例,在上面介绍到当判定超时后,会调用系统服务 AMS 接口,搜集本次 ANR 相关信息并存档(data/anr/trace,data/system/dropbox),入口如下。

进入系统服务 AMS 之后,AppError 先进行场景判断,以过滤当前进程是不是已经发生并正在执行 Dump 流程,或者已经发生 Crash,或者已经被系统 Kill 之类的情况。并且还考虑了系统是否正在关机等场景,如果都不符合上述条件,则认为当前进程真的发生 ANR。

顶部信息

接下来系统再判断当前 ANR 进程对用户是否可感知,如后台低优先级进程(没有重要服务或者 Activity 界面)。

然后开始统计与该进程有关联的进程,或系统核心服务进程的信息;例如与应用进程经常交互的 SurfaceFligner,SystemServer 等系统进程,如果这些系统服务进程在响应时被阻塞,那么将导致应用进程 IPC 通信过程被卡死。

首先把自身进程(系统服务 SystemServer)加进来,逻辑如下:

接着获取其它系统核心进程,因为这些服务进程是 Init 进程直接创建的,并不在 SystemServer 或 Zygote 进程管理范围。

在搜集完第一步信息之后,接下来便开始统计各进程本地的更多信息,如虚拟机相关信息、Java 线程状态及堆栈。以便于知道此刻这些进程乃至系统都发生了什么情况

Dump Trace 流程

系统为何要收集其它进程信息呢?因为从性能角度来说,任何进程出现高 CPU 或高 IO 情况,都会抢占系统资源,进而影响其它进程调度不及时的现象。下面从代码角度看看系统 dump 流程:

private static void dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids, ArrayList<Integer> nativePids, ArrayList<Integer> extraPids,
            boolean useTombstonedForJavaTraces) {
        ......
        ......
        //考虑到性能影响,一次dump最多持续20S,否则放弃后续进程直接结束
        remainingTime = 20 * 1000;
        try {
                ......
                //按照优先级依次获取各个进程trace日志
                int num = firstPids.size();
                for (int i = 0; i < num; i++) {
                    final long timeTaken;
                    if (useTombstonedForJavaTraces) {
                        timeTaken = dumpJavaTracesTombstoned(firstPids.get(i), tracesFile, remainingTime);
                    } else {
                        timeTaken = observer.dumpWithTimeout(firstPids.get(i), remainingTime);
                    }

                    remainingTime -= timeTaken;
                    if (remainingTime <= 0) {
                        //已经超时,则不再进行后续进程的dump操作
                        return;
                    }
                    }
                }
            }
            //按照优先级依次获取各个进程trace日志
                for (int pid : nativePids) {
                    final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);

                    final long start = SystemClock.elapsedRealtime();
                    Debug.dumpNativeBacktraceToFileTimeout(
                            pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
                    final long timeTaken = SystemClock.elapsedRealtime() - start;

                    remainingTime -= timeTaken;
                    if (remainingTime <= 0) {
                                              //已经超时,则不再进行后续进程的dump操作
                        return;
                    }
                }
            }
            //按照优先级依次获取各个进程trace日志
                for (int pid : extraPids) {
                    final long timeTaken;
                    if (useTombstonedForJavaTraces) {
                        timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
                    } else {
                        timeTaken = observer.dumpWithTimeout(pid, remainingTime);
                    }

                    remainingTime -= timeTaken;
                    if (remainingTime <= 0) {
                        //已经超时,则不再进行后续进程的dump操作
                        return;
                    }
                }
            }
        }
        ......
    }

出于安全考虑,进程之间是相互隔离的,即使是系统进程也无法直接获取其它进程相关信息。因此需要借助 IPC 通信的方式,将指令发送到目标进程,目标进程接收到信号后,协助完成自身进程 Dump 信息并发送给系统进程。以 AndroidP 系统为例,大致流程图如下:

关于应用进程接收信号和响应能力,是在虚拟机内部实现的,在虚拟机启动过程中进行信号注册和监听(SIGQUIT),初始化逻辑如下:

SignalCatcher 线程接收到信号后,首先 Dump 当前虚拟机有关信息,如内存状态,对象,加载 Class,GC 等等,接下来设置各线程标记位(check_point),以请求线程起态(suspend)。其它线程运行过程进行上下文切换时,会检查该标记,如果发现有挂起请求,会主动将自己挂起。等到所有线程挂起后,SignalCatcher 线程开始遍历 Dump 各线程的堆栈和线程数据,结束之后再唤醒线程。期间如果某些线程一直无法挂起直到超时,那么本次 Dump 流程则失败,并主动抛出超时异常。

SignalCatcher获取各线程信息的工作过程

根据上面梳理的流程,SignalCatcher 获取各线程信息的工作过程,示意图如下:

到这里,基本介绍完了系统设计原理,并以广播发送为例说明系统是如何判定 ANR 的,以及发生 ANR 后,系统是如何获取系统信息和进程信息,以及其他进程是如何协助系统进程完成日志收集的。

整体来看链路比较长,而且涉及到与很多进程交互,同时为了进一步降低对应用乃至系统的影响,系统在很多环节都设置大量超时检测。而且从上面流程可以看到发生 ANR 时,系统进程除了发送信号给其它进程之外,自身也 Dump Trace,并获取系统整体及各进程 CPU 使用情况,且将其它进程 Dump 发送的数据写到文件中。因此这些开销将会导致系统进程在 ANR 过程承担很大的负载,这是为什么我们经常在 ANR Trace 中看到 SystemServer 进程 CPU 占比普遍较高的主要原因。

参考

Android ANR Trace 详解

How to read Dalvik SIGQUIT output

https://codezjx.com/2017/08/06/anr-trace-analytics/

ActivityManagerService

  1. 在产生ANR的时候,会回调到AMS的appNotResponding()方法,以下为关键代码,中文注释为相关代码的解读:
  2. 我们来详细看下dumpStackTraces()这个方法,此处将dump出firstPids与lastPids进程的相关线程堆栈信息至traces.txt,中文注释为相关代码的解读。

http://zhijianz.me/2017/07/11/ANR%E5%88%86%E6%9E%90-trace%E6%96%87%E4%BB%B6%E5%88%86%E6%9E%90/

Android ANR Trace 详解

https://github.com/iqiyi/xCrash

今日头条 anr 优化实践系列 - 设计原理及影响因素

今日头条 anr 优化实践系列 - 监控工具与分析思路

今日头条 ANR 优化实践系列分享 - 实例剖析集锦