

## 1 Android 崩溃

崩溃的分类：

* Java 崩溃：Java代码中，出现了未捕获异常，导致程序异常退出。
* Native 崩溃： Native 代码中**访问非法地址**、**地址对齐**出现了问题、**程序主动 abort**，这些都会产生相应的 signal 信号，导致程序异常退出。

在应用启动的时候设置一个标志，若发生了异常退出，则更新这个标志，下次启动时检查这个标志即可知道上次是否发生了异常退出。每次启动都把 flag 置1，后面启动成功就把 flag 置 0。如果下次启动发现 flag 是 1，那就是启动异常。这样检测可反映 ANR、low memory killer、被系统杀死、死机、断电等情况，不过从任务管理器划掉这种情况也会被统计进去。

应用稳定性，可以使用异常率的指标来衡量:

```java
UV 异常率 = 发生异常退出或崩溃的 UV / 登录 UV
//UV 独立访客，0 - 24小时内的同一终端只计算一次
```

## 3 采集崩溃信息

- 基本的崩溃信息

  - 进程名、线程名，前台还是后台线程，是否发生在UI线程。
  - 崩溃堆栈和类型，Java、Native、ANR，重点关注栈顶，发生在系统代码还是业务代码。

- 系统信息

  - Logcat：应用、系统的运行日志。
  - 机型、系统、厂商、CPU、ABI、Linux 版本等。
  - 设备状态：是否 root、是否是模拟器。

> 获取logcat方法:
>
> logcat日志流程：应用层 --> liblog.so --> logd，底层使用 ring buffer*来存储数据。获取的方式有以下三种：
>
> * 通过logcat命令获取
>
>   - 优点：非常简单，兼容性好。
>
>   - 缺点：整个链路比较长，可控性差，失败率高，特别是堆破坏或者堆内存不足时，基本会失败
>
> * hook liblog.so实现，通过hook liblog.so 中的  __android_log_buf_write 方法，将内容重定向到自己的buffer中。
>   * 优点：简单，兼容性相对还好。
>   * 缺点：要一直打开。
>
> * 自定义获取代码。通过移植底层获取logcat的实现，通过socket直接跟logd交互。
>
>   - 优点：比较灵活，预先分配好资源，成功率也比较高。
>   - 缺点：实现非常复杂
>
> 获取 Java 堆栈：
>
> native崩溃时，通过unwind只能拿到Native堆栈。我们希望可以拿到当时各个线程的Java堆栈
> * Thread.getAllStackTraces()。
>   * 优点：简单，兼容性好。
>   * 缺点：
>     a. 成功率不高，依靠系统接口在极端情况也会失败。
>     b. 7.0之后这个接口是没有主线程堆栈。
>     c. 使用Java层的接口需要暂停线程
>
> * hook libart.so。通过hook ThreadList 和 Thread 的函数，获得跟 ANR 一样的堆栈。为了稳定性，我们会在fork子进程执行。
>   * 优点：信息很全，基本跟ANR的日志一样，有native线程状态，锁信息等等。
>   * 缺点：黑科技的兼容性问题，失败时可以用Thread.getAllStackTraces()兜底


- 内存信息
  - 系统剩余内存，读取文件 /proc/meminfo获取内存状态。
  - 应用使用内存，Java内存、RSS、PSS，得出应用本身占用内存大小和分布。PSS 和 RSS 通过 /proc/self/smap 计算，可以进一步得到例如 apk、dex、so 等更加详细的分类统计。
  - 虚拟内存，通过 /proc/self/status 得到，通过 /proc/self/maps 文件可以得到具体的分布情况。
- 资源信息
  可能跟内存泄漏有关系。
  - 文件句柄，通过 /proc/self/limits 获得文件句柄限制，单个进程一般为1024，超过800则把所有fd以及对应文件名输出到日志，排查文件或线程泄漏。
  - 线程数，也是通过/proc/self/status得到，过多的线程会对虚拟内存和文件句柄带来压力，超过400个则要把所有线程id及线程名输出到日志中，排查线程相关问题。
  - JNI，通过DumpReferenceTable统计JNI引用表，分析JNI泄漏。
- 应用信息
  - 崩溃场景，发生在哪个activity、fragment，对应的模块。
  - 关键操作路径，记录用户的关键操作路径。
  - 其它自定义信息，如运行时间、是否加载补丁等。



## 5 Java Crash 异常

出现未捕获异常，导致出现异常退出。

```java
//自定义的 UncaughtExceptionHandler，崩溃发生的时候获取到现场信息。
Thread.setDefaultUncaughtExceptionHandler();

//获取主线程的堆栈信息
Looper.getMainLooper().getThread().getStackTrace();

//获取当前线程的堆栈信息
Thread.currentThread().getStackTrace();

//获取全部线程的堆栈信息
Thread.getAllStackTraces();
```

第三方Crash监控工具如 **Fabric、腾讯Bugly**，都是以字符串拼接的方式将数组StackTraceElement[]转换成字符串形式，进行保存、上报或者展示。

## 6 Java Crash处理流程

![](../../../asset/javacrash.jfif) 

1、首先发生crash所在进程，在创建之初便准备好了defaultUncaughtHandler，用来处理Uncaught Exception，并输出当前crash的基本信息；

2、调用当前进程中的AMP.handleApplicationCrash；经过binder ipc机制，传递到system_server进程；

3、接下来，进入system_server进程，调用binder服务端执行AMS.handleApplicationCrash；

4、从mProcessNames查找到目标进程的ProcessRecord对象；并将进程crash信息输出到目录/data/system/dropbox；

5、执行makeAppCrashingLocked：

- 创建当前用户下的crash应用的error receiver，并忽略当前应用的广播；
- 停止当前进程中所有activity中的WMS的冻结屏幕消息，并执行相关一些屏幕相关操作；

6、再执行handleAppCrashLocked方法：

- 当1分钟内同一进程未发生连续crash两次时，则执行结束栈顶正在运行activity的流程；
- 当1分钟内同一进程连续crash两次时，且非persistent进程，则直接结束该应用所有activity，并杀死该进程以及同一个进程组下的所有进程。然后再恢复栈顶第一个非finishing状态的activity;
- 当1分钟内同一进程连续crash两次时，且persistent进程，则只执行恢复栈顶第一个非finishing状态的activity。

7、通过mUiHandler发送消息SHOW_ERROR_MSG，弹出crash对话框；

8、到此，system_server进程执行完成。回到crash进程开始执行杀掉当前进程的操作；

9、当crash进程被杀，通过binder死亡通知，告知system_server进程来执行appDiedLocked()；

10、最后，执行清理应用相关的四大组件信息。

## 7 Native Crash 异常

Native崩溃的捕获流程：

- 编译端：编译C/C++代码时，将带符号信息的文件保存下来。
- 客户端：捕获崩溃时，尽可能收集有用信息写入日志文件，在合适的时机上传服务器。
- 服务端：读取客户端上报的日志文件，寻找合适的符号文件，生成可读的C/C++调用栈。

如果 Native开发：可以使用 Chromium 的 Breakpad 来捕获崩溃，生成 minidump文件，进行解析定位到问题源码处。

## 2 检测异常

## 7 分析崩溃

采集到有用信息后需要进行分析，分为以下三步：

- step1：确定重点，找到日志中的关键信息，做一个大致判断
  - 确定严重程度，优先解决Top崩溃或对业务有重大影响的崩溃，如启动崩溃。
  - 崩溃基本信息，确定崩溃的类型和异常描述。如Java崩溃NullPointerException、OutOfMemoryError等关键信息；Native崩溃则观察signal、code、fault addr等内容，获取Java堆栈；ANR则先看主线程的堆栈，看是否是因为锁等待导致，接着看 ANR 日志中 iowait、CPU、GC、system server 等信息，进一步确定是 I/O 问题，或是 CPU 竞争问题，还是由于大量 GC 导致卡死。
  - Logcat，关注warning和error级别。ANR->“am_anr”、被系统杀死->"am_kill。
  - 各个资源情况，结合基本信息，看是跟内存信息有关或是跟资源信息有关。

- step2：查找共性，即查找这类崩溃的共性，如机型、系统版本等。

- step3：**尝试复现**，如根据收集到的用户操作路径来复现。

## 8 ANR 优化

ANR 全名 **Application Not Responding**，也就是"应用无响应"，当操作在一段时间内系统无法处理时, 系统层面会弹出 ANR 对话框。

在Android里，App的响应能力是由 Activity Manager 和 Window Manager 系统服务来监控的. 通常在如下两种情况下会弹出ANR对话框:

* 5s 内无法响应用户输入事件(例如键盘输入, 触摸屏幕等).
* 不同的组件发生 ANR 的时间不一样，Activity 5s，BroadCastReceiver 10s，Service 20s（均为前台）。

说白了，就是**在主线程(UI线程)里面做了太多的阻塞耗时操作**, 例如文件读写，数据库读写，网络查询等等。

### 8.1 如何避免ANR

不要在主线程(UI线程)里面做繁重的操作；

不要在主线程(UI线程)里面做繁重的操作；

不要在主线程(UI线程)里面做繁重的操作;

### 8.2 ANR分析

```java
//1 Log获取

//1.1 抓取bugreport
adb shell bugreport > bugreport.txt

//1.2 直接导出/data/anr/traces.txt文件
adb pull /data/anr/traces.txt trace.txt
```