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”);
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机制,有需要的可以看下我这篇已经授权给 鸿洋 的博客,那真叫一个粗暴,保证你一会就搞明白整个流程。
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 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的启动流程这一篇够了
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
尾声
如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
这里,笔者分享一份从架构哲学的层面来剖析的视频及资料给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2019-2021字节跳动Android面试历年真题解析》
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
[外链图片转存中…(img-JPM7d7GQ-1713294695987)]
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-JZKD76ZV-1713294695988)]
《2019-2021字节跳动Android面试历年真题解析》
[外链图片转存中…(img-cn9lA6cO-1713294695989)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!