一、造成系统崩溃(crash)的原因是什么?
(1)未捕获的异常
有Java
层面,和Native
层面 。
在我们平常操作中,如果有未捕获的异常,会导致系统崩溃,这个崩溃本质上是人为操作的
,人为操作系统主动退出
(后面讲源码的地方可以看到)。
(2)ANR
系统因为线程阻塞问题,导致的无响应。
(3)WTF(What a Terrible Failure)
一般指,系统中自己编码没有按照android要求进行,例如发送未受保护的广播、启动的Activity未在注册文件里注册等等;这个一般不用太关注,主要写代码按标准来
就行。
二、java层未捕获的异常是如何导致崩溃的
源码分析步骤1
首先我们先明确一个目的,我们的main
函数的启用本身作为主线程存在,那么在我们想要探索系统到底如何处理异常,需要去关注到一个类Thread
;
在我们的程序代码中,如果存在一个异常,任何地方都没有去捕获处理它的话,它就会一路往上抛,最终来到main
函数,如果main
函数也没有处理这个异常,就会给到JVM
来处理,JVM
会给到当前的线程Thread
来处理。
源码分析步骤2
在Thread
类中,看到一段这样的函数dispatchUncaughtException()
;
注释翻译为:向处理程序发送未捕获的异常。此方法旨在仅由JVM
调用; 可以理解为,未处理的异常
会走到这里来:
在上图我们可以看到官方明确告知,
JVM在处理未经捕获的异常时
,会调用当前dispatchUncaughtException
函数进行处理,这个里面我们能看到一个类型为UncaughtExceptionHandler
的类。
在上图的逻辑中我们可以看到如果没有设置
uncaughtExceptionHandler
,将使用线程所在的线程组(ThreadGroup )
来处理这个未捕获异常。线程组ThreadGroup
实现了UncaughtExceptionHandler
,所以可以用来处理未捕获异常。
源码分析步骤3
所以,我们重点来看ThreadGroup
中,是如何来处理未捕获异常的:在Thread
类的dispatchUncaughtException
函数中,最后调用了getUncaughtExceptionHandler().uncaughtException(this, e);
我们知道这个getUncaughtExceptionHandler()
返回的是ThreadGroup
,所以我们来看ThreadGroup
中的uncaughtException
方法:
默认情况下,
ThreadGroup
处理未捕获异常的逻辑是:
-
首先将异常消息通知给
父线程组(如果parent不为空的话)
; -
然后尝试利用一个默认的
defaultUncaughtExceptionHandler
来处理异常; -
如果没有默认的异常处理器则将
错误信息输出打印到System.err
。
这里可以思考下,我们可以自定义一个异常处理类,继承下 Thread.UncaughtExceptionHandler
,然后去处理未捕获的异常。记得需要手动去调用Thread.setUncaughtExceptionPreHandler()
方法设置下,有了这个自定义异常处理类,就可以做相应的崩溃优化。
源码分析步骤4
回到Thread
中,思考下:既然他是通过getDefaultUncaughtExceptionHandler
来处理,现在我们并没有看到有相关的设置,但是在Thread
中我们看到了他对外提供了对应的设置函数:Thread.setUncaughtExceptionPreHandler()
。
源码分析步骤5
思考下:系统是否会有地方默认给我们设置了uncaughtExceptionHandler
?
因为从上面的源码看来,我们并没有看到有让系统直接崩溃掉的情况,因为默认是ThreadGroup
去处理,他只不过是做了一个日志信息的记录,不会有退出的情况,那么肯定是有哪个地方默认设置了uncaughtExceptionHandler
,让系统退出的。
源码分析步骤6
来看下RuntimeInit
这个类,zygote
负责启动RuntimeInit
进程(作用:app运行时环境初始化,用来初始化运行时的一系列信息,其中包含异常处理),它里面有个main
方法:
这里设置了默认的异常处理:
KillApplicationHandler
。
源码分析步骤7
我们来看下KillApplicationHandler
重点来看
uncaughtException(Thread t, Throwable e)
这个方法:
看到这里就知道了,默认的异常处理(
杀进程
)是在RuntimeInit
进程(作用:app运行时环境初始化,用来初始化运行时的一系列信息,其中包含异常处理)的main()
方法里设置的。
小总结
三、AMS如何承接应用的异常信息上报?
在上面的源码分析步骤7我们知道了,在KillApplicationHandler的uncaughtException()
方法里,最终异常信息有一个AMS
上报过程:
来看下
ActivityManagerService.handleApplicationCrash()
方法:
从上面可以看出,若传入
app
为null
时,processName
就设置为system_server
,意思是:如果没有来源默认判定是系统进程自己。接着看handleApplicationCrashInner(String eventType......)
方法:
参数eventType
是指事件类型,具体如下:
Java
层未捕捉的异常:crash
ANR
:anr
native
层的异常:native_crash
现在我们看的是java
的异常,所以这个类型传的是crash
。
接着看handleApplicationCrashInner()
函数:
中间的可以先不管他们,这个可以理解为在进行系统日志输出,具体的处理是在addErrorToDropBox()
函数中。
重点注意:无论是java crash
、native_crash
、ANR
或是wtf
,最终都是来到这里,交由addErrorToDropBox()
函数去处理。
四、DropBoxManager
addErrorToDropBox()
函数和DropBoxManager
有关,Android Dropbox
是 Android 在 Froyo(API level 8) 引入的用来持续化存储系统数据的机制。主要用于记录 Android 运行过程中, 内核, 系统进程, 用户进程等出现严重问题时的 log, 可以认为这是一个可持续存储的系统级别的 logcat。
记录位置:在data/system/dropbox
中:
也就是说,我们想要看系统的崩溃日志,可以在这个文件路径下找。