作为一名Android开发者,经常都会遇到关于『崩溃』的一些问题。今天我们首先在第一个小节里来阐述下Android中的崩溃,然后在第二个小节中阐述Android中崩溃又如何去优化呢?
Android中两种崩溃(Java 崩溃和 Native 崩溃)
Java 崩溃
java崩溃就是在 Java 代码中,出现了未捕获异常,导致程序异常退出。
Java崩溃很好理解,就是程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,具体可参考如下文章。(https://blog.csdn.net/sugar_no1/article/details/88593255)
Native崩溃
一般都是因为在 Native 代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动 abort,这些都会产生相应的 signal 信号,导致程序异常退出。
因为Native crash具有上下文不全、出错信息模糊、难以捕捉等特点。所以Native cash的捕获要比Java crash难的多。在这里我们先来了解下Native 代码的崩溃捕获机制及实现(https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w)。
大致会经过如下一个流程:
- 编译端。编译 C/C++ 代码时,需要将带符号信息的文件保留下来。
- 客户端。捕获到崩溃时候,将收集到尽可能多的有用信息写入日志文件,然后选择合适的时机上传到服务器。
-
服务端。读取客户端上报的日志文件,寻找适合的符号文件,生成可读的 C/C++ 调用栈。
image.png
Native崩溃在捕获过程中也会遇到很多种情况:
- 文件句柄泄漏,导致创建日志文件失败,怎么办?
应对方式:我们需要提前申请文件句柄 fd 预留,防止出现这种情况。
- 因为栈溢出了,导致日志生成失败,怎么办?
应对方式:为了防止栈溢出导致进程没有空间创建调用栈执行处理函数,我们通常会使用
常见的 signalstack。在一些特殊情况,我们可能还需要直接替换当前栈,所以这里也需要在堆中预留部分空间。
- 整个堆的内存都耗尽了,导致日志生成失败,怎么办?
应对方式:这个时候我们无法安全地分配内存,也不敢使用 stl 或者 libc 的函数,
因为它们内部实现会分配堆内存。这个时候如果继续分配内存,
会导致出现堆破坏或者二次崩溃的情况。Breakpad 做的比较彻底,
重新封装了Linux Syscall Support,来避免直接调用 libc。
- 堆破坏或二次崩溃导致日志生成失败,怎么办?
应对方式:Breakpad 会从原进程 fork 出子进程去收集崩溃现场,
此外涉及与 Java 相关的,一般也会用子进程去操作。这样即使出现二次崩溃,
只是这部分的信息丢失,我们的父进程后面还可以继续获取其他的信息。
在一些特殊的情况,我们还可能需要从子进程 fork 出孙进程。
对于崩溃服务来说:
并不建议自己去实现一套如此复杂的系统,可以选择一些第三方的服务。目前各种平台也是百花齐放,包括腾讯的Bugly、阿里的啄木鸟平台、网易云捕、Google 的 Firebase 等等。
在平台的选择方面,我认为,从产品化跟社区维护来说,Bugly 在国内做的最好;从技术深度跟捕获能力来说,阿里 UC 浏览器内核团队打造的啄木鸟平台最佳。
目前很多公司都会提到一个词『崩溃率』,并且会拿到日常的KPI中,所以就有以下两位同学的做法:
同学A:
对所有线程、任务都封装了一层 try catch,“消化”掉了所有 Java 崩溃。至于程序是否会出现其他异常表现,这是上帝要管的事情,反正我是实现了“千分之一”的目标。
同学B:
认为 Native 崩溃太难解决,所以他想了一个“好方法”,就是不采集所有的 Native 崩溃,美滋滋地跟老板汇报“万分之一”的工作成果。
以上两位同学做法如有雷同,纯属巧合,我们都过于看重这个美好的数字。所有的目标就只有一个【良好的用户体验】。
对于一款稳定的APP,另外一个很关键的指标就是异常虑:
在讨论什么是异常退出之前,我们先看看都有哪些应用退出的情形。
- 主动自杀。Process.killProcess()、exit() 等。
- 崩溃。出现了 Java 或 Native 崩溃。
- 系统重启;系统出现异常、断电、用户主动重启等,我们可以通过比较应用开机运行时间是否比之前记录的值更小。
- 被系统杀死。被 low memory killer 杀掉、从系统的任务管理器中划掉等。
- ANR。
对于以上问题如何解决呢?
我们可以在应用启动的时候设定一个标志,在主动自杀或崩溃后更新标志,这样下次启动时通过检测这个标志就能确认运行期间是否发生过异常退出。对应上面的五种退出场景,我们排除掉主动自杀和崩溃(崩溃会单独的统计)这两种场景,希望可以监控到剩下三种的异常退出,理论上这个异常捕获机制是可以达到 100% 覆盖的。
但以上方法也可能产生误报,比如用户:用户手动从系统任务管理器中把APP给划掉。但依然是可以帮我们从中获取一些有效信息的。
总结
崩溃率不应该是我们最关心的一个点。不应该盲目追求崩溃率这一个数字,应该以用户体验为先,如果强行去掩盖一些问题往往更加适得其反。我们不应该随意使用 try catch 去隐藏真正的问题,要从源头入手,了解崩溃的本质原因,保证后面的运行流程。在解决崩溃的过程,也要做到由点到面,不能只针对这个崩溃去解决,而应该要考虑这一类崩溃怎么解决和预防。
所以从崩溃到优化是一条漫长的路,需要我们一步一步去了解本质,从最根本上来解决问题。
网友评论