Android 开发高手课 02 | 崩溃优化(下):应用崩溃了,你应该如何去分析?

本文微信公众号「安卓小煜」首发

1. 背景

最近重读了 Android 开发高手课,记录一下阅读的一些笔记。

包括有些之前课后作业完成不了的,这次也都一并完成了。

如果想看课后作业的,可以直接定位到最后一节,前面的都是原文的一个汇总。

2. 崩溃现场

你要分析一个东西,肯定需要一些相关信息,因此首先就需要采集崩溃的现场信息。

2.1 崩溃信息

  • 进程名、线程名:判断是前台进程还是后台进程,是否 UI 线程
  • 崩溃堆栈和类型:Java 崩溃?Native 崩溃?ANR?特别要看崩溃堆栈的栈顶

2.2 系统信息

  • Logcat :应用运行日志
    系统运行日志,系统 event logcat 在 /system/etc/event-log-tags 记录 App 运行的一些基本情况
  • 机型、系统、厂商、CPU、ABI、Linux 版本等
  • 设备状态:是否 root、是否是模拟器

2.3 内存信息

  • 系统剩余内存:系统内存状态,通过 /proc/meminfo 文件可以得到。当 可用内存 < MemTotal * 10%,各种问题容易出现

  • 应用使用内存 :Java 内存、RSS、PSS,可以得到应用本身内存的占用大小和分布。

    RSS(Resident Set Size)------ 实际占用的物理内存,包括共享库,但不包含在交换分区的空间

    PSS(Proportional Set Size)------ 与 RSS 的区别是,以平分方式计算共享库大小(共享库/进程个数)

    RSS、PSS 可以通过 /proc/self/smap 得到

  • 虚拟内存 :通过 /proc/self/status 得到,通过 /proc/self/maps 文件可以得到具体分布情况。

    对于 32 位进程,如果是 32 位 CPU,虚拟内存达到 3GB 就可能内存申请失败;64 位 CPU,虚拟内存一般在 3~4 GB 之间

    对于 64 位进程,虚拟内存不会成为问题

2.4 资源信息

根据资源信息判断是否存在资源泄漏

  • 文件句柄 fd :通过 /proc/self/limits 获得。
    一般单个进程允许打开的最大文件句柄个数为 1024,如果超过 800 个需要输出到日志排查。
  • 线程数 :通过 /proc/self/status 获得。
    超过 400 个需要输出到日志排查。
  • JNI:通过 DumpReferenceTables 统计 JNI 引用表

2.5 应用信息

  • 崩溃场景
  • 关键操作路径
  • 其他自定义信息

3. 崩溃分析

第一步:确定重点

  1. 确定严重程度:主要是关注性价比
  2. 崩溃基本信息
  • Java 崩溃
  • Native 崩溃:观察 signal、code、fault addr 等内容,以及崩溃时 Java 堆栈
    关于 signal 的介绍,可以查看 siginfo_t
    比较常见的:
    SIGSEGV:空指针、非法指针
    SIGABRT:ANR、调用 abort() 退出
  • ANR
    1. 先看主线程的堆栈,是否因为锁等待导致
    2. 看 ANR 日志中 iowait、CPU、GC、system server 等信息,进一步确定是 I/O 问题、CPU 竞争问题,还是由于大量 GC 导致卡死
  1. Logcat
    注意日志级别是 Warning、Error,还有一些关键 tag(比如 am_anr、am_kill)
  2. 各个资源情况
    在 Logcat 和各个资源情况的分析时,尤其要关注内存与线程相关信息

第二步:查找共性

  • 系统信息:机型、系统、ROM、厂商、ABI
  • 应用信息

第三步:尝试复现

疑难问题:系统崩溃

举一个系统崩溃的例子来说明上面崩溃分析三步曲。

  1. 查找可能的原因。通过共性归类,找到一些怀疑的点。
  2. 尝试规避。查看可疑的代码调用,是否可以更换其他实现方式规避。
  3. Hook 解决。使用 Java Hook 或者 Native Hook 解决。

4. 课后作业

仓库地址:Chapter02

  • 注意点一:模拟器版本号
    我使用 API 30 Android 11.0 测试是不通过的,但是使用 API 22 Android 5.1 是可以的
    代码里面也有对应注释:Android P API 28 以后不能反射 FinalizerWatchdogDaemon
  • 注意点二:用到了反射,建议搭配源码食用更佳
    我这里参考的源码是 Android 系统 Lollipop - 5.1.1_r6 ,具体位置为:FinalizerWatchdogDaemon

这边把关键代码的地方添加注释如下:

java 复制代码
// 获取 FinalizerWatchdogDaemon 类
final Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
// 获取 FinalizerWatchdogDaemon 的 INSTANCE 字段,通过源码发现该字段赋值为 FinalizerWatchdogDaemon 实例对象
final Field field = clazz.getDeclaredField("INSTANCE");
field.setAccessible(true);
// 获得 FinalizerWatchdogDaemon 实例对象,这里可以传 null 是因为实例对象是 static 修饰的
final Object watchdog = field.get(null);
// 获得父类 Daemon 的 thread 字段
final Field thread = clazz.getSuperclass().getDeclaredField("thread");
thread.setAccessible(true);
// 将 FinalizerWatchdogDaemon 的 thread 变量设置为 null
thread.set(watchdog, null);

将 thread 置为 null 之后, finalizerTimedOut(finalizedObject) 就不会调用到,也就不会出现 java.util.concurrent.TimeoutException: com.dodola.watchdogkiller.GhostObject.finalize() timed out after 10 seconds

scss 复制代码
private static void finalizerTimedOut(Object object) {
 // The current object has exceeded the finalization deadline; abort!
 String message = object.getClass().getName() + ".finalize() timed out after "
         + (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
 Exception syntheticException = new TimeoutException(message);
 // We use the stack from where finalize() was running to show where it was stuck.
 syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
 Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler();
 if (h == null) {
     // If we have no handler, log and exit.
     System.logE(message, syntheticException);
     System.exit(2);
 }
 // Otherwise call the handler to do crash reporting.
 // We don't just throw because we're not the thread that
 // timed out; we're the thread that detected it.
 h.uncaughtException(Thread.currentThread(), syntheticException)
}
相关推荐
Estar.Lee1 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯2 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey3 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!4 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟5 小时前
Android音频采集
android·音视频
小白也想学C6 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程7 小时前
初级数据结构——树
android·java·数据结构
乐闻x8 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
闲暇部落9 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
青云交10 小时前
大数据新视界 -- 大数据大厂之 Impala 性能优化:跨数据中心环境下的挑战与对策(上)(27 / 30)
大数据·性能优化·impala·案例分析·代码示例·跨数据中心·挑战对策