1. 先从 Thread.setDefaultUncaughtExceptionHandler 说起
我们先定个小目标,把 App 里所有的 Crash catch 住,防止 app crash.
1.1 简单实现 Thread.UncaughtExceptionHandler 接口看看效果
//定义CrashHandler
class CrashHandler private constructor(): Thread.UncaughtExceptionHandler {
private var context: Context? = null
fun init(context: Context?) {
this.context = context
Thread.setDefaultUncaughtExceptionHandler(this)
}
override fun uncaughtException(t: Thread, e: Throwable) {}
companion object {
val instance: CrashHandler by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
CrashHandler() }
}
}
//Application中初始化
class MyApplication : Application(){
override fun onCreate() {
super.onCreate()
CrashHandler.instance.init(this)
}
}
//Activity中触发异常
class ExceptionActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_exception)
btn.setOnClickListener {
throw RuntimeException("主线程异常")
}
btn2.setOnClickListener {
Thread {
throw NumberFormatException("子线程异常")
}.start()
}
}
}
我们写了两种触发异常的情况:子线程崩溃和主线程崩溃。
-
运行,点击按钮2,触发子线程异常崩溃:
- 程序能继续正常运行
-
然后点击按钮1,触发主线程异常崩溃:
- 卡住了,再点几下,直接ANR
1.2 为什么catch异常之后 主线程 App 会 ANR 呢?
首先科普下java中的异常,包括 运行时异常和非运行时异常:
-
运行时异常
- 是RuntimeException类及其子类的异常,是非受检异常,比如系统异常或者是程序逻辑异常,我们常遇到的有NullPointerException、IndexOutOfBoundsException等。遇到这种异常,Java Runtime会停止线程,打印异常,并且会停止程序运行,也就是我们常说的程序崩溃。
-
非运行时异常
- 是属于Exception类及其子类,是受检异常,RuntimeException以外的异常。这类异常在程序中必须进行处理,如果不处理程序都无法正常编译,比如NoSuchFieldException,IllegalAccessException这种。
也就是说我们抛出一个RuntimeException异常之后,所在的线程会被停止。如果主线程中抛出这个异常,那么主线程就会被停止,所以APP就会卡住无法正常操作,时间久了就会ANR。
而子线程崩溃了并不会影响主线程也就是UI线程的操作,所以用户还能正常使用。
这样好像就说的通了。
那为什么遇到 setDefaultUncaughtExceptionHandler
就不会崩溃了呢?
1.3 异常源码分析
一般情况下,一个应用中所使用的线程都是在同一个线程组,而在这个线程组里只要有一个线程出现未被捕获异常的时候,JAVA 虚拟机就会调用当前线程所在线程组中的 uncaughtException():
// ThreadGroup.java
private final ThreadGroup parent;
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
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);
}
}
}
parent
表示当前线程组的父级线程组,所以最后还是会调用到这个方法中。接着看后面的代码,通过 getDefaultUncaughtExceptionHandler
获取到了系统默认的异常处理器,然后调用了 uncaughtException
方法。那么我们就去找找本来系统中的这个异常处理器——UncaughtExceptionHandler
。
这就要从APP的启动流程说起了,之前也说过,所有的Android进程都是由 zygote
进程 fork
而来的,在一个新进程被启动的时候就会调用 zygote.Init
方法,这个方法里会进行一些应用的初始化工作:
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();
// zygote初始化
ZygoteInit.nativeZygoteInit();
//应用相关初始化
return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
而关于异常处理器,就在这个通用的配置初始化方法 RuntimeInit.commonInit()
当中:
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
//设置异常处理器
LoggingHandler loggingHandler = new LoggingHandler();
Thread.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
//设置时区
TimezoneGetter.setInstance(new TimezoneGetter() {
@Override
public String getId() {
return SystemProperties.get("persist.sys.timezone");
}
});
TimeZone.setDefault(null);
//log配置
LogManager.getLogManager().reset();
//***
initialized = true;
}
这就是我们要找的应用默认的异常处理器—— KillApplicationHandler
private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
private final LoggingHandler mLoggingHandler;
public KillApplicationHandler(LoggingHandler loggingHandler) {
this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
//...
// Bring up crash dialog, wait for it to be dismissed
ActivityManager.getService().handleApplicationCrash(
mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
} catch (Throwable t2) {
if (t2 instanceof DeadObjectException) {
// System process is dead; ignore
} else {
try {
Clog_e(TAG, "Error reporting crash", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
}
} finally {
// Try everything to make sure this process goes away.
Process.killProcess(Process.myPid());
System.exit(10);
}
}
private void ensureLogging(Thread t, Throwable e) {
if (!mLoggingHandler.mTriggered) {
try {
mLoggingHandler.uncaughtException(t, e);
} catch (Throwable loggingThrowable) {
// Ignored.
}
}
}
在 uncaughtException
回调方法中,会执行一个 handleApplicationCrash
方法进行异常处理,并且最后都会走到 finally
中进行进程销毁,Try everything to make sure this process goes away
。所以程序就崩溃了。
关于我们平时在手机上看到的崩溃提示弹窗,就是在这个 handleApplicationCrash
方法中弹出来的。不仅仅是 java
崩溃,还有我们平时遇到的 native_crash
、ANR
等异常都会最后走到handleApplicationCrash
方法中进行崩溃处理。
另外有的朋友可能发现了构造方法中,传入了一个 LoggingHandler
,并且在 uncaughtException
回调方法中还调用了这个 LoggingHandler
的 uncaughtException
方法,难道这个 LoggingHandler
就是我们平时遇到崩溃问题,所看到的崩溃日志?进去瞅瞅:
private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
public volatile boolean mTriggered = false;
@Override
public void uncaughtException(Thread t, Throwable e) {
mTriggered = true;
if (mCrashing) return;
if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
} else {
StringBuilder message = new StringBuilder();
message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
final String processName = ActivityThread.currentProcessName();
if (processName != null) {
message.append("Process: ").append(processName).append(", ");
}
message.append("PID: ").append(Process.myPid());
Clog_e(TAG, message.toString(), e);
}
}
}
private static int Clog_e(String tag, String msg, Throwable tr) {
return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
}
这可不就是吗?将崩溃的一些信息——比如线程,进程,进程id,崩溃原因等等通过Log打印出来了。来张崩溃日志图给大家对对看:
1.4 我们自己实现的 UncaughtExceptionHandler 怎么起作用的呢
我们通过 setDefaultUncaughtExceptionHandler
方法设置了我们自己的崩溃处理器,就把之前应用设置的这个崩溃处理器给顶掉了,然后我们又没有做任何处理,自然程序就不会崩溃了,来张总结图。
2. 虽然我们可以 catch 异常,但是 App 还是 ANR,怎么破?
加入以下代码到 Application onCreate:
Handler(Looper.getMainLooper()).post {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
}
}
}
神奇的发现,主线程 Crash 之后还是可以正常运行我们的 App, NB.
2.1 为什么加了个 Looper.loop() 就可以了呢?
原理很简单,就是通过Handler往主线程的queue中添加一个Runnable,当主线程执行到该Runnable时,会进入我们的while死循环,如果while内部是空的就会导致代码卡在这里,最终导致ANR,但我们在while死循环中又调用了Looper.loop(),这就导致主线程又开始不断的读取queue中的Message并执行,这样就可以保证以后主线程的所有异常都会从我们手动调用的 Looper.loop()
处抛出,一旦抛出就会被 try{}catch
捕获,这样主线程就不会 crash
了,
如果没有这个while的话那么主线程下次抛出异常时我们就又捕获不到了,这样APP就又crash了,所以我们要通过while让每次crash发生后都再次进入消息循环,while的作用仅限于每次主线程抛出异常后迫使主线程再次进入消息循环。
具体参考下面链接