在Android/Java开发中经常遇到的就是程序突然崩溃了,这中崩溃是如何被捕获并且打印到控制面板的呢。抛出异常之后app就崩溃了,而不是整个系统崩溃是为什么?今天就从Android的角度来看看一个Android系统是如何崩溃的。
从Android系统启动流程知道,systemserver和每个app进程的启动都是通过Zygote进程孵化的。从Launcher桌面点击启动一个未启动过的app时,就会触发zygote孵化一个新的进程,从这里开始扯出系统对crash的处理。对于进程的创建和android系统的启动不熟悉的可以自行了解。
public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
RuntimeInit.redirectLogStreams();
RuntimeInit.commonInit();// 1
ZygoteInit.nativeZygoteInit(); // 2
return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);//3
}
一个app进程被fork出来之后,就会走到这个方法去初始化当前进程运行需要的条件环境,注释1初始化日志、crash监控等。注释2初始化binder线程池,表明当前进程就可以使用binder来通信。注释3利用反射调用 ActivityThread 的main()方法,这里是我们熟悉的主线程。这里主要关注注释1处。其他功能不在本次讨论范围。
protected static final void commonInit() {
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
Thread.setUncaughtExceptionPreHandler(new LoggingHandler());//1
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());//2
}
注释1主要是打印log 崩溃信息,但是和注释2是互斥的,如果注释2走了就不会走注释1的方法,因为有个boolean标识符做限制,这里不过多解释,主要看注释2的处理,因为这里涉及到了 kill application 的操作。都是传给Thread进行处理的,接下来会去看Thread的实现。
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
public void uncaughtException(Thread t, Throwable e) {
try {
//防止重复打印崩溃日志,这就是上面说的和LoggingHandler互斥
if (mCrashing) return;
mCrashing = true;
// 弹出崩溃dialog,触发AMS处理崩溃信息
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} finally {
// 杀死当前进程
Process.killProcess(Process.myPid());
System.exit(10);
}
}
}
KillApplicationHandler等都是实现Thread中的 Thread.UncaughtExceptionHandler 接口,接下来转到Thread中
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
defaultUncaughtExceptionHandler = eh;
}
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
return defaultUncaughtExceptionHandler;
}
Thread中处理很简单只是把他设置成全局静态变量。为什么是static的呢?也就是说这个崩溃并不是当前 Thread 的,而是Thread类的属性。跟当前对象没关系。不管是那一个线程崩溃都会触发,当然Thread中也保存了当前线程独有的UncaughtExceptionHandler对象,只是这个对象一般为空。因为对于android来说一个app代表一个进程,每个app进程都有独立的JVM虚拟机,所以进程中的所有线程都是运行在当前app的虚拟机内的。不管是任何线程触发了崩溃的信息,都会导致整个JVM的崩溃,所以每当崩溃时我们的app程序就退出运行。
到这里set之后我们的当前进程已经把callback注册到了Thread类中,那这个callback何时会被调用呢?
在上文了解到了,异常监控接口注册到了Thread类中,那产生异常时候,在哪里被出发的呢?
既然崩溃是通过监听线程类触发,不论是那个线程只要出现异常就会触发崩溃。我们知道一个进程对应一个虚拟机,对于一个进程中的所有线程,也通过JVM虚拟机来管理的。所以当出现异常信息时,jvm 会去搜是那个线程出了问题,然后调用线程类的
public final void dispatchUncaughtException(Throwable e) {
Thread.UncaughtExceptionHandler initialUeh =
Thread.getUncaughtExceptionPreHandler(); // 1
if (initialUeh != null) {
try {
initialUeh.uncaughtException(this, e);
} catch (RuntimeException | Error ignored) {
// Throwables thrown by the initial handler are ignored
}
}
// 2
getUncaughtExceptionHandler().uncaughtException(this, e);
}
当程序出现异常时JVM虚拟机就会调用 dispatchUncaughtException()方法,注释1就会回调 LoggingHandler 的方法uncaughtException()。主要看注释2
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
上述方法中 uncaughtExceptionHandler 是线程独立的异常信息监控,一般为空。所以注释2中获取到的都是ThreadGroup,ThreadGroup也是 Thread.UncaughtExceptionHandler 类型。在ThreadGroup中实现
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler(); // 1
ueh.uncaughtException(t, e);
}
}
ThreadGroup 是通过获取父线程的ThreadGroup对象。注释1 获取到的对象其实就是KillApplicationHandler对象
protected static final void commonInit() {
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
Thread.setUncaughtExceptionPreHandler(new LoggingHandler());//1
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());//2
}
所以在ThreadGroup中回调就会调到
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
然后转到AMS中去。
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
// 1
addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
// 2
mAppErrors.crashApplication(r, crashInfo);
}
注释1中通过跨进程把日志打印到具体的路径下面。注释2 主要是提示错误信息,然后弹出 crash dialog。主要看注释 1代码.
public void addErrorToDropBox(String eventType,
ProcessRecord process, String processName, ActivityRecord activity,
ActivityRecord parent, String subject,
final String report, final File dataFile,
final ApplicationErrorReport.CrashInfo crashInfo) {
final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class); // 1
if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
dbox.addText(dropboxTag, sb.toString());
}
};
}
注释1获取系统服务,这个服务是在SystemServer中启动的,主要就是保持系统和application的崩溃日志,然后保持到 (“data/system/dropbox/”)下面。
这个服务在SystemServer中启动
mSystemServiceManager.startService(DropBoxManagerService.class);
调用addText() 方法通过流保存到文件夹下。