写点什么

App 怎么做才能永不崩溃

用户头像
Android架构
关注
发布于: 刚刚

在未知异常的情况下,有办法让程序不崩溃么?


  • 嗯...应该可以吧...


好了,回去等通知吧.




以上是一段加过戏的面试场景,考察的是对异常处理,以及 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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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 机制,有需要的可以看下我这篇已经授权给 鸿洋 的博客,那真叫一个粗暴,保证你一会就搞明白整个流程。


5分钟了解Handler机制,Handler的错误使用场景


OK,我们回来继续扯为啥进程存续,却无法响应用户的事件呢?其实刚刚描述 Handler 的时候已经说到了。 就是出现了异常,导致主线程的Looper已经退出循环了,都退出循环了还怎么响应你。


以上 2 种情况分析清楚了,那我们着重说下怎么解决这两种问题,先整第一种。


出现异常,怎么防止进程退出? 上述已经说到,进程退出,实际是默认的KillApplicationHandler.uncaughtException()调用了Process.killProcess(Process.myPid()); System.exit(10)。 防止退出,不让调用KillApplicationHandler.uncaughtException()不就可以了?


做法跟我们本文开头描述的一样,我们只需要自己实现一个 Thread.UncaughtExceptionHandler 类,并在 Application 初始化就可以了

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
App怎么做才能永不崩溃