之前的一篇文章,讲到了应用程序无响应(ANR)时Android的处理逻辑。这篇文章,就来分析下应用进程发生崩溃(Crash)时,Android是如何处理的?总的说来,Android主要有两大类Crash:
- Java(JVM)层: 应用程序发生运行时错误(如空指针,浮点运算错误,数据索引超出界限)或者系统进程崩溃(长时间无响应);
- Native层: native进程或者kernel发生运行时错误;
APP层Crash处理
对Java层,Android需要处理两种情况的Crash:
- 对于APP中未捕获的异常,捕捉到后,进行处理;
- 监控UI线程(主线程)、前台服务线程、IO线程、显示线程(IMS/WMS,DMS中使用)以及Binder进程通信线程是否挂起(10s内无响应则视为挂起);
主线程(main thread)是指应用启动时创建的进程中的第一个线程,而UI线程则是负责输入、绘制等前台交互,大部分情况下主线程就是UI线程,参考:
下面就从这两种情况来分析APP出现的Crash。
未捕获异常Crash
对于每个进程,在启动过程初始化运行时执行环境时,都会设置一个未捕获异常处理函数,用于捕获APP中未捕获到的异常情况:
1 |
|
捕获到应用Crash后,向AMS发送Crash信息,最后将进程杀死:
1 |
|
参考源码:/android/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
AMS接收到Crash后,找到应用的进程记录,做下一步处理:
1 |
|
AMS处理APP的Crash主要做了两件事情:
- 保存Crash的当时的现场到Dropbox;
- 如果发现该APP频繁Crash,记录信息,强制其停止,不提示用户;否则需要弹出APP出错的对话框告知用户,由用户自己选择处理的方式(重启或者强制停止);
1 |
|
系统挂起
在应用层, Android有一个专门的监控者Watchdog
线程来负责监控应用层是否出现系统挂起(在给定时间10s无响应则视为挂起),比如系统服务IMS(AMS,WMS)无响应,UI线程、IO线程无响应,Binder IPC线程无响应等各种异常情况:
1 |
|
Watchdog
线程是在system_server
进程启动时启动起来的:
1 |
|
Watchdog
线程启动后,开始监测各个线程有无响应(通过在线程消息队列的头部添加一个可执行对象,对于Binder线程则通过BlockUtilThreadAvailable
来检测是否有Binder线程可用),一旦发现系统某个线程无响应,则抓取相应APP的堆栈LOG,同时保存kernel的堆栈信息:
1 |
|
参考源码:/android/frameworks/base/services/core/java/com/android/server/Watchdog.java
接下来,就来看一看Android是如何处理native进程crash的。
Native层Crash处理
在native层,Android的Crash处理主要由三个部分组成:
- kernel捕捉到进程的异常信号(SIGABRT,SIGBUS,SIGFPE)时,调用信号处理函数;信号处理函数负责收集Crash进程的错误信息,并将错误信息通过socket发送给debugger守护进程;
- debugger守护进程接收到crash信息后,一方面告知AMS有进程发生Crash,一方面通过tomestone保存完整的现场信息;
- AMS收到Crash信息后,弹出对话框告知用户Crash信息,同时保存该crash进程的相关LOG;
接下来,我们分步骤来看下native层Crash的处理流程。
Crash信号处理函数
Native进程发生崩溃(Crash)时,kernel会向其发送一个信号(signal),此时当前进程被中断,kernel接着调用事先注册在系统中的信号处理函数(Signal Handler)。Android在启动时专门注册了一个signal handler用于处理Native进程由于运行错误而出现的信号。
那么,Android的signal handler是在哪里注册到系统中的了?在Android库bionic中有一个动态链接库linker
(文件目录/android/bionic/
),linker
启动时会初始化通过系统调用signal
向系统注册信号处理函数; Android主要处理SIGABRT
、SIGBUS
等几种Crash信号(linux signal):
SIGABRT
(6): abort, 异常终止;SIGBUS
(10): bus error, 总线错误;SIGFPE
(8): Floating Point Exception, 浮点运算异常;SIGILL
(4): Illegal Instruction, 非法指令;SIGSEGV
(11): Invalid memory Segmentation Fault, 无效内存访问;SIGSTKFLT
(16): stack fault, 堆栈错误;SIGTRAP
(5): Trace Trap, 跟踪陷阱;
debugger.cpp
1 |
|
注册完信号处理函数后,一旦kernel检测到有进程出现上述信号,则会调用debuggerd_signal_handler
函数进行处理:此时会通过socket接口android:debuggerd32
(对于64位系统,socket名为android:debuggerd
)发送错误信息,
1 |
|
调用send_debuggerd_packet
,首先连接debuggerd
的服务端socket;接着不断尝试向该socket写入数据,直至成功:
1 |
|
参考源码: /android/bionic/linker/debugger.cpp
debuggerd.cpp
在服务端进程debuggerd
启动时,会不断监听客户端的连接请求,一旦发现有连接,就会读取其中的数据进行处理:
1 |
|
现在debuggerd
与客户端进程已经建立好连接了,读取其中的数据并fork
一个新的进程处理之:
1 |
|
在子进程中处理Crash:首先尝试连接AMS,接着会保存Crash进程的DUMP LOG,最后通过socket发送消息告知AMS有Crash发生,
1 |
|
连接AMS用来监听native crash的socket, 从这里我们也可以看到,debuggerd
进程实际连接的是在NativeCrashListener.java
中创建的socket:
1 |
|
参考源码: /android/system/core/debuggerd/debuggerd.cpp
ActivityManagerService.java
进程debuggerd
跟AMS是通过一个叫/data/system/ndebugsocket
的socket进行通信的,该socket在NativeCrashListener.java
中创建的; NativeCrashListener
实际是一个线程,它是在AMS初始化时创建的,其作用是一直在监听来自debuggerd
进程的请求,如果有则处理:
1 |
|
读取socket中的Crash数据,并将其报告给AMS:
1 |
|
通过NativeCrashReporter
线程告知AMS有native crash发生:
1 |
|
之后的处理流程,跟在第一节关于未捕获异常的时候,就基本一样了。详细可以参考上节内容。