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)
}
相关推荐
顾林海17 分钟前
探秘Android JVM TI:虚拟机背后的"隐形管家"
android·面试·性能优化
刘大国2 小时前
<android>反编译魔改安卓系统应用并替换
android
恋猫de小郭2 小时前
Flutter Riverpod 3.0 发布,大规模重构下的全新状态管理框架
android·前端·flutter
纤瘦的鲸鱼3 小时前
MySQL慢查询
android·adb
郭庆汝3 小时前
模型部署:(三)安卓端部署Yolov8-v8.2.99目标检测项目全流程记录
android·yolo·目标检测·yolov8
fatiaozhang95273 小时前
中国移动云电脑一体机-创维LB2004_瑞芯微RK3566_2G+32G_开启ADB ROOT安卓固件-方法3
android·xml·adb·电脑·电视盒子·刷机固件
柯南二号3 小时前
【Android】设置让输入框只能输入数字
android
CV资深专家3 小时前
Android 编译系统lunch配置总结
android
撩得Android一次心动3 小时前
Android 项目:画图白板APP开发(五)——橡皮擦(全面)
android·绘图·自定义视图
2501_915106323 小时前
App Store 软件上架全流程详解,iOS 应用发布步骤、uni-app 打包上传与审核要点完整指南
android·ios·小程序·https·uni-app·iphone·webview