日常开发过程中,我们肯定有遇到app崩溃的情况,很多少时候,会有系统弹出提示:
,那么系统是怎么捕获到App崩溃并弹出提示框的呢?更进一步,如果我想在子线程crash时,app不被杀死,要怎么实现呢?带着这些疑问,我去探究了一下Android的crash处理原理。
背景知识
做Android开发的小伙伴都知道,Android是基于Java线程+消息队列来实现的。而Java的Crash处理机制也是和线程息息相关的。在Java的Thread类源码中可以看到如下接口:
public interface UncaughtExceptionHandler {
/**
* 当虚拟机收到某个线程产生的未捕获的异常时,该方法会被调用,在此方法中扔出的异常将不会被虚拟机忽略
*/
void uncaughtException(Thread t, Throwable e);
}
UncaughtExceptionHandler接口定义了应用层处理异常的行为,那么应用层应该怎么使用呢?直接看Thread源码
public class Thread implements Runnable {
// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;
private static volatile UncaughtExceptionHandler uncaughtExceptionPreHandler;
public static void setUncaughtExceptionPreHandler(UncaughtExceptionHandler eh) {
uncaughtExceptionPreHandler = eh;
}
/** @hide */
public static UncaughtExceptionHandler getUncaughtExceptionPreHandler() {
return uncaughtExceptionPreHandler;
}
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
return uncaughtExceptionHandler != null ?
uncaughtExceptionHandler : group;
}
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
checkAccess();
uncaughtExceptionHandler = eh;
}
public final void dispatchUncaughtException(Throwable e) {
// BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
Thread.UncaughtExceptionHandler initialUeh =
Thread.getUncaughtExceptionPreHandler();
if (initialUeh != null) {
try {
initialUeh.uncaughtException(this, e);
} catch (RuntimeException | Error ignored) {
// Throwables thrown by the initial handler are ignored
}
}
// END Android-added: uncaughtExceptionPreHandler for use by platform.
getUncaughtExceptionHandler().uncaughtException(this, e);
}
}
我精简了Thread中和Crash处理无关的代码,上述源码分为成员的设置和获取、异常处理,先来看成员相关:
- uncaughtExceptionHandler:对象成员,每个Thread对象对应一个
- defaultUncaughtExceptionHandler:类成员,Thread类仅一个,对于android来讲,就是一个应用只有一个,即应用级别
- uncaughtExceptionPreHandler:类成员,Thread类仅一个,对于android来讲,就是一个应用只有一个,即应用级别
接下来看下异常如何被分发和处理:
- 首先获取uncaughtExceptionPreHandler成员,将异常交给ta处理,和名字一样,果然是预处理。
- 获取uncaughtExceptionHandler,非空时,将异常交给uncaughtExceptionHandler处理。
- uncaughtExceptionHandler为空时,获取group,将异常交给group处理?那么这个group是什么呢
#Thread.ava
private ThreadGroup group;
#ThreadGroup.java
public class ThreadGroup implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
//如果父处理器非Null,交由父处理器处理
parent.uncaughtException(t, e);
} else {
//获取当前线程的默认处理器defaultUncaughtExceptionHandler,非Null则交由其处理。
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}}
通过上述源码,我们大概了解了Java线程在应用层的分发逻辑了,整理下可以得出如下结论:
- 异常处理是具有优先级的,首先是应用级别预处理器uncaughtExceptionPreHandler,其次是当前线程处理器uncaughtExceptionHandler,之后时不断向上递交,最终交给应用级别默认处理器defaultUncaughtExceptionHandler
- 异常处理具有线程隔离的能力,能将crash产生的线程捕获,不向其他线程或者进程扩散,前提是主业务能正常运行。
了解了Java中Thread线程异常的捕获机制,不禁让我疑惑,之前在开发时,也不曾看到有设置Thread异常处理对象啊?那么Android中的异常处理在哪里设置的呢??
Android中的异常处理
仔细想想,如果我们给Android中应用设置一个异常处理器,会在哪里设置呢?大概率会在进程启动的时候就设置,因为这样就不会遗漏捕获。其实Android的确就是这样干的。
#ZygoteInit.java
public static final Runnable zygoteInit(int targetSdkVersion, String[] argv,
ClassLoader classLoader) {
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
protected static final void commonInit() {
/*
* 设置默认异常处理器给虚拟机所有线程,允许替换默认处理器,但不允许更改预处理起
*/
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
}
上述是ZygoteInitl类的两个方法,我们知道,Android中每个应用是个进程,而且该进程由Zygote进程孵化而来,在Zygote创建新进程之后,便会调用上述zygoteInit对象zygoteInit方法来进程新进程的初始化,而我们关心的异常处理器则在其调用的commonInit方法中。看一看到其设置了预处理器LoggingHandler,又设置了默认处理器KillApplicationHandler。这里我们直接看KillApplicationHandler就行了。
# KillApplicationHandler.java
public void uncaughtException(Thread t, Throwable e) {
try {
//打印异常,内部主要是使用LoggingHandler
ensureLogging(t, e);
// 如果正在处理异常,则直接返回,这里是为了不重复上报异常
if (mCrashing) return;
mCrashing = true;
// 停止系统数据采集
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// 通知AMS,当前进程出现了Crash
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
} finally {
// 杀掉当前进程并退出
Process.killProcess(Process.myPid());
System.exit(10);
}
}
我们看到,系统设置的异常处理器主要就是做了三件事
- 打印日志
- 通知AMS,当前进程出现了crash,赶紧处理
- 杀掉当前进程
那么AMS会怎么处理呢?简单跟一下代码:
#ActivityManagerService.java
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
ApplicationErrorReport.CrashInfo crashInfo) {
//省略打印日志和收集日志到drop box
mAppErrors.crashApplication(r, crashInfo);
}
# AppErrors.java
void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
....
try {
crashApplicationInner(r, crashInfo, callingPid, callingUid);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
int callingPid, int callingUid) {
AppErrorDialog.Data data = new AppErrorDialog.Data();
data.result = result;
data.proc = r;
// 避免重复处理
if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace, data)) {
return;
}
final Message msg = Message.obtain();
msg.what = ActivityManagerService.SHOW_ERROR_UI_MSG;
taskId = data.taskId;
msg.obj = data;
//这里是想UIHandler发送了SHOW_ERROR_UI_MSG消息,该消息主要是展示UI的
mService.mUiHandler.sendMessage(msg);
}
// 后面是清理AMS进程内当前Crash进程的一些数据,有兴趣可以自己跟一下
}
#UIHandler.java
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_ERROR_UI_MSG: {
mAppErrors.handleShowAppErrorUi(msg);
ensureBootCompleted();
}
......
#AppErrors.java
void handleShowAppErrorUi(Message msg) {
AppErrorDialog.Data data = (AppErrorDialog.Data) msg.obj;
boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
AppErrorDialog dialogToShow = null;
final String packageName;
final int userId;
synchronized (mService) {
final ProcessRecord proc = data.proc;
final AppErrorResult res = data.result;
//...省略了一些拦截代码
// 展示Dialog提示App Crash,这里的Crash是包含ANR导致的Crash的
if (dialogToShow != null) {
dialogToShow.show();
}
}
上述思路大致可以看到,系统弹出了一个dialog去展示,就是文章开头的效果,当然,不同系统可能UI效果不一样的。到此,Android中的异常处理机制大致已经介绍一遍。那么加入我现在想实现一个功能,实现crash的拦截和包装上报呢,这个要怎么做呢?可以先想想,基础思路的 答案在文末。
public interface CrashHandleInterceptor {
/**
* @return false 为不捕获异常.
*/
boolean handleException(Thread thread, Throwable throwable);
}
public interface CrashFacadeInterceptor {
String facadeException(Throwable throwable);
}
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static CrashHandler crashHandler;
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
private boolean inited;
private CrashHandleInterceptor crashInterceptor;
private CrashFacadeInterceptor crashFacadeInterceptor;
static synchronized CrashHandler getInstance() {
CrashHandler myCrashHandler;
synchronized (CrashHandler.class) {
if (crashHandler == null) {
crashHandler = new CrashHandler();
}
myCrashHandler = crashHandler;
}
return myCrashHandler;
}
void register(CrashHandleInterceptor crashInterceptor) {
if (!inited) {
inited = true;
this.crashInterceptor = crashInterceptor;
try {
uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
} catch (Exception e) {
Log.d("CrashSDK", "Exception e = " + e.getMessage());
inited = false;
e.printStackTrace();
}
}
}
@Override public void uncaughtException(Thread thread, Throwable throwable) {
//拦截
if (crashInterceptor != null && crashInterceptor.handleException(thread, throwable)) {
Log.d(CrashSDK.TAG, "crashInterceptor.handleException");
return;
}
Throwable facadeThrowable = throwable;
if (crashFacadeInterceptor != null) {
String errorMsg = crashFacadeInterceptor.facadeException(throwable);
facadeThrowable = CrashUtil.facadeThrowable(throwable, errorMsg);
}
uncaughtExceptionHandler.uncaughtException(thread, facadeThrowable);
}
void setCrashFacadeInterceptor(CrashFacadeInterceptor crashFacadeInterceptor) {
this.crashFacadeInterceptor = crashFacadeInterceptor;
}
boolean isInited() {
return inited;
}
}
提几个问题
- 默认情况下,如果子线程出现了未捕获的Exception,主线程会crash吗
- 如何让指定子线程出现了未捕获异常时,主线程不受影响
- 如果覆盖了KillApplicationHandler,当异常产生时,不做任何处理,此时会出现什么情况?
对应答案
- 主线程会crash,因为子线程没有设置未处理异常的处理器,所以会委托给Threadgroup的uncatchExceptionHandle去处理,该方法会调用KillApplicationHandler处理,默认会crash。
- 可以调用子线程的setUncaughtExceptionHandler方法给子线程设置处理器,拦截掉异常
- 程序会没有反应,如果用户进行交互,会产生ANR