Android 开发中如何定位问题

本文旨在总结个人在 Android 开发中定位问题的经验与技巧。

概述

定位问题的三个步骤,1. 确认重点2. 查找共性3. 尝试复现。其中确认重点查找共性的关键在于收集足够多的问题信息


Logcat

LogCat 是 Android 开发中最便捷,最常用的信息收集工具,没有之一。LogCat 中建议输出如下信息:

  • Application, ActivityFragment 中的 create, destroy, start, stop 四个生命周期方法
  • 网络请求: 包括 requesturl, header, params, 以及 responseheader, body,需要注意的是,输出 response 信息,也需要添加 url,方便我们知道是哪一个请求。
  • io 操作: 包括文件/文件夹的 create, delete, read, write 操作
  • 关键模块的业务逻辑信息: 用户登录的生命周期 等
  • 其他业务逻辑信息: 自己在调试时临时添加的 log 日志,注意在使用后需要删除这部分日志,避免污染代码库
  • 日志持久化缓存: 日志缓存到本地的好处有两个
    • 一是适用于问题设备不在我们身边的情况,可以让设备主人将日志发送给我们,或在 app 后台添加一个逻辑,自动上传错误日志
    • 二是对于某些无法复现的问题,日志忠实的记录下了发生问题的那一次的信息

Debug tools

Debug 工具是 Android Studio 自带的一套非常强大的调试工具,也是比较为大家熟知的,但大部分人对于 Debug 工具的使用仍停留在打个断点的程度,下面简单列举一下 Debug 工具能做到的事。

  • 断点类型
    • 普通断点
    • 条件断点: 满足某些条件才生效的断点
    • 日志断点: 经过断点时向 debug console 输出日志
    • 异常断点: 但凡捕获到该类型的异常,即在异常发生的位置进入断点模式
  • 断点执行方式
    • 单步执行: 每次向下执行一行代码
    • 进入方法: 进入当前断点行所执行的方法,对应的还有一个退出方法
    • 执行到光标处: 代码执行到光标处进入断点模式
  • 计算表达式: 允许你在当前断点处执行一些代码,比如检查某些量是否正常,或修改一些量以进行测试
  • 方法调用栈: 断点界面的左下角有一块完整的显示了当前代码处的方法调用栈

更详细的教程可以阅读下面这篇文章,作者总结的很棒,几乎 100% 覆盖了 Debug Tools 的功能。

超详细:用图诠释 Android Studio 调试技巧 >Google 官方 debug 工具教程


Profiler

Profiler 是 Android Studio 自带的一套性能监测工具,它可以监测 app(以及设备)的实时运行状态,提供如下几个类型的监测:

  • CPU: 可以监测 Activity 生命周期事件,实时 CPU 使用率,线程活动。支持对一段时间内的数据进行追踪,分析一段时间内方法的 cpu 耗时等。
  • 内存: 可以查看实时内存用量,及 JavaNativeGraphics 等的内存占比。查看一段时间内的内存中对象的信息,包括创建对象的方法调用栈等。支持手动 gc。
  • 网络: 可查看实时的网络通信信息,如 request, response 的详细信息,且支持按线程查看。
  • 能耗: 可以查看实时的 Activity 生命周期事件,应用的估算耗电量,以及可能会影响耗电量的系统事件。

Android 官方 Profiler 教程


第三方库

  • stetho: facebook 出品的强大的调试工具,可以借助 chrome 开发者工具,方便的查看 Android app 的 ui 布局,网络请求,资源文件,assets 文件,数据库内容等
  • Android Debug Database: 通过浏览器访问设备,直接查看数据库内容
  • LeakCanary: 强大的内存泄露检测库
  • AndroidPerformanceMonitor (BlockCanary): 卡顿检测库(似乎已停止维护)

崩溃信息收集

崩溃信息的收集主要通过 UncaughtExceptionHandler 捕获,常见的第三方崩溃信息收集工具,如 bugly,友盟等,均通过该方法实现崩溃信息的手机。

对于一个崩溃,需要收集以下信息:

  • 方法调用栈
  • log
  • 设备信息: 设备型号,厂商,cpu 型号,内存大小,存储大小
  • 系统版本: Android OS 版本,厂商系统版本
  • 进程信息: 进程名,前台进程 or 后台进程
  • 线程信息: 所在线程,崩溃发生时其他线程执行状态,线程池状态
  • cpu: cpu 运行状态
  • memory: app 内存用量,系统内存用量
  • storage: app 存储用量,系统存储用量
  • 特殊环境检测: 是否 root,是否安装 Xposed

再啰嗦几句:

  • 线上发现崩溃问题,应通过邮件,im 等工具及时通知相关负责人
  • 对于 RxJava 流中发生的未捕获异常,可以使用 RxJavaPlugins.setErrorHandler 统一收集,与 UncaughtExceptionHandler 进行区分
  • 部分第三方的崩溃信息收集库(fabric - crashlytics)会自己对 UncaughtExceptionHandler 进行一层封装,以避免破坏已有的逻辑,但某些三方库(umeng)不会考虑的这么周到,这时可以注意三方库的初始化顺序,并自己实现一层封装

反编译

有时需要确认 apk 中是否包含我们所需要的资源/代码,而 apk 解压后的 AndroidManifest.xml 是二进制码,java 类也都是 smali 文件。这时我们可以通过一些成熟的反编译工具,直接查看 apk 中的明文内容。

  • jd-gui: 带有 gui 的 Android / java 反编译工具
  • Android Studio: 直接将 apk 文件拖拽入 Android Studio 中即可反编译查看 apk 内容

Android OS 开发者模式选项

Android 系统的开发者选项中,提供了许多方便我们调试以及定位问题的选项。

  • 显示布局边界
  • 显示过度绘制
  • GPU 呈现模式分析
  • 严格模式
  • 不保留活动
  • 限制后台进程数
  • 调整动画速度倍率

git bisect

git bisectgit 提供的一套二分法查找问题的工具。有时我们在某个节点发现了错误,但之前已经提交了多个 commit,我们并不知道出错的的到底是哪一个,但我们知道在很早之前的一个 commit 是正常的。如果按照 commit 一个个的 check out 排查效率是很低的,这时我们就可以使用 git bisect

1
2
3
4
5
6
7
8
9
10
11
12
$ git bisect start
$ git bisect bad commit_19 # commit_19 是有问题的
$ git bisect good commit_5 # commit_5 是正常的

## git 开始切换到 commit_19 与 commit_5 中间的节点,并要求你确认这个节点是否正常
## 同时告诉你,按照二分法,还有3次 git bisect 操作就能定位到有问题的节点了
Bisecting: 675 revisions left to test after this (roughly 3 steps)

## 在这个节点进行测试,并将测试结果告诉 git
git bisect good # 如果节点异常就是 git bisect bad

## 继续执行,直到定位到问题节点

git bisect 资料