App 怎么做才能永不崩溃
在未知异常的情况下,有办法让程序不崩溃么?
嗯...应该可以吧...
好了,回去等通知吧.
以上是一段加过戏的面试场景,考察的是对异常处理,以及 Handler 对应原理的了解程度。 接下来我们一个一个分析问题。
try catch 会影响程序运行性能么?
首先,try catch 使用,要尽可能的缩小作用域,当 try catch 作用域内未抛出异常时,性能影响并不大,但是只要抛出了异常就对性能影响是成倍的。 具体我进行了简单的测试,分别针对了以下三种情况。
没有 try catch
有 try catch 但是没有异常
既有 try catch 又有异常。
fun test() {
val start = System.currentTimeMillis()
var a = 0
for (i in 0..1000) {
a++
}
Log.d("timeStatistics", "noTry:" + (System.currentTimeMillis() - start))
}
fun test2() {
val start = System.currentTimeMillis()
var a = 0
for (i in 0..1000) {
try {
a++
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
Log.d("timeStatistics", "tryNoCrash:" + (System.currentTimeMillis() - start))
}
fun test3() {
val start = System.currentTimeMillis()
var a = 0
for (i in 0..1000) {
try {
a++
throw java.lang.Exception()
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
}
Log.d("timeStatistics", "tryCrash:" + (System.currentTimeMillis() - start))
}
2021-02-04 17:10:27.823 22307-22307/com.ted.nocrash D/timeStatistics: noTry:0
2021-02-04 17:10:27.823 22307-22307/com.ted.nocrash D/timeStatistics: tryNoCrash:0
2021-02-04 17:10:28.112 22307-22307/com.ted.nocrash D/timeStatistics: tryCrash:289
通过日志可以非常明显的得出两个结论
无异常时,有 try 与无 try 影响不大,都是 0 毫秒。
有异常时候性能下降了 289 倍
当然,以上测试为极端情况,目的是放大问题,直面问题,所以以后 try catch 要尽可能的缩小作用域。
异常日志要怎么收集呢?
这个问题在本文开头已经给出了答案,可以通过继承 Thread.UncaughtExceptionHandler 并重写 uncaughtException()实现日志收集。 注意:需要在Application调用初始化
class MyCrashHandler : Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread, e: Throwable) {
Log.e("e", "Exception:" + e.message);
}
fun init() {
Thread.setDefaultUncaughtExceptionHandler(this)
}
}
此时可以在 uncaughtException()方法中做日志收集和上传工作。
为什么出现异常了,程序会停止运行呢?
这个问题需要了解下 Android 的异常处理机制,在我们未设置 Thread.UncaughtExceptionHandler 之前,系统会默认设置一个,具体我们参考下ZygoteInit.zygoteInit()
public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
String[] argv, ClassLoader classLoader) {
if (RuntimeInit.DEBUG) {
Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
RuntimeI
nit.redirectLogStreams();
RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
classLoader);
}
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
/*
set handlers; these apply to all threads in the VM. Apps can replace
the default handler, but not the pre handler.
*/
LoggingHandler loggingHandler = new LoggingHandler();
RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
...
}
可以看到在 ZygoteInit.zygoteInit()中已经设置了 setDefaultUncaughtExceptionHandler(),而 ZygoteInit 是进程初始化的过程。 Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
当程序出现异常会回调到KillApplicationHandler.uncaughtException(Thread t, Throwable e)
@Override
public void uncaughtException(Thread t, Throwable e) {
try {
ensureLogging(t, e);
// Don't re-enter -- avoid infinite loops if crash-reporting crashes.
if (mCrashing) return;
mCrashing = true;
// Try to end profiling. If a profiler is running at this point, and we kill the
// process (below), the in-memory buffer will be lost. So try to stop, which will
// flush the buffer. (This makes method trace profiling useful to debug crashes.)
if (ActivityThread.currentActivityThread() != null) {
ActivityThread.currentActivityThread().stopProfiling();
}
// 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);
}
}
直接观察finally
中,调用了 Process.killProcess(Process.myPid()); System.exit(10);
,触发了进程结束逻辑,也就导致了程序停止运行。
如果出现了异常,程序一定会停止运行么?
首先我们需要定一下停止运行的概念是啥,一般主要有两种情况。
程序进程退出(对标常说的闪退)
程序进程存续,但是点击无响应用户事件(对标 ANR)
第一个问题很好理解,就是我们上述过程的进程退出,我们主要研究第二种情况,进程存续但是无法响应用户事件。
这里我先要普及个小知识点,Android 系统为啥能响应来自各种(人为/非人为)的事件?
这里就要涉及 Handler 的概念了,其实整个操作系统的运行全部依赖 Handler Message Looper 这套机制,所有的行为全部会组装成一个个的 Message 消息,然后 Looper 开启一个 for 循环(死循环)取出一个个 Message 交给 Handler 处理,而 Hander 处理完成进行了响应,我们的行为也就得到了应答,影响的越快我们就会认为系统越流畅。
这里不过多描述 Handler 机制,有需要的可以看下我这篇已经授权给 鸿洋 的博客,那真叫一个粗暴,保证你一会就搞明白整个流程。
OK,我们回来继续扯为啥进程存续,却无法响应用户的事件呢?其实刚刚描述 Handler 的时候已经说到了。 就是出现了异常,导致主线程的Looper已经退出循环了
,都退出循环了还怎么响应你。
以上 2 种情况分析清楚了,那我们着重说下怎么解决这两种问题,先整第一种。
出现异常,怎么防止进程退出? 上述已经说到,进程退出,实际是默认的KillApplicationHandler.uncaughtException()
调用了Process.killProcess(Process.myPid()); System.exit(10)
。 防止退出,不让调用KillApplicationHandler.uncaughtException()
不就可以了?
做法跟我们本文开头描述的一样,我们只需要自己实现一个 Thread.UncaughtExceptionHandler 类,并在 Application 初始化就可以了
评论