写点什么

App 怎么做才能永不崩溃,一文详解

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

}


此时可以在 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");


RuntimeInit.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 初始化就可以了


class MyCrashHandler : Thread.UncaughtExceptionHandler {


override fun uncaughtException(t: Thread, e: Throwable) {


Log.e("e", "Exception:" + e.message);


}


fun init() {


Thread.setDefaultUncaughtExceptionHandler(this)


}


}


以上逻辑设置了 Thread 默认的 UncaughtExceptionHandler,所以再出现崩溃的时候会调用到 ThreadGroup.uncaughtException(),再处理异常就会到我们自己实现的 MyCrashHandler 了,所以也就不会退出进程了。


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);


}


}


}


以上逻辑同时就触发了第二种停止运行,也就是虽然进程没有退出,但是用户点击无响应。 既然用户无响应是 Looper 退出循环导致的,那我们启动循环不就解决了么,只需要通过以下方式,在 Application


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


onCreate()调用


Handler(mainLooper).post {


while (true) {


try {


Looper.loop()


} catch (e: Throwable) {


}


}


}


这是什么意思?我们通过 Handler 往 Message 队列 post 一个消息,这个消息是一个死循环。 每次 loop()出现了异常,都会重新启动 loop()也就解决了无响应的问题。但是这里一定要控制好异常处理逻辑,虽然无限重启 loop(),但是如果一直异常也不是长久之计,这个 try 相当于 try 住了整个 App 的运行逻辑。


开头我们也说明了 try 的作用域尽可能小,这种做法岂不是把 try 的作用域整到了最大??? 其实我们要努力的主要还是提高代码质量,降低异常出现的概率,这种做法只是补救,用效率换取了用户体验。


总结一下,其实异常处理本质考察的就是 Handler,Looper 机制,Application 启动的时机等逻辑的相互关系,只要知道对应关系也就彻底整掌握了异常处理的手法,还是推荐大家多看 Android 源码。 比如这一篇 Activity的启动流程这一篇够了



留一个小问题,在没有循环启动 Looper.loop()的情况下,以下代码会导致崩溃么?为什么?

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
App怎么做才能永不崩溃,一文详解