Android 开发中如何定位问题
本文旨在总结个人在 Android 开发中定位问题的经验与技巧。
概述
定位问题的三个步骤,1. 确认重点,2. 查找共性,3. 尝试复现。其中确认重点与查找共性的关键在于收集足够多的问题信息。
Logcat
LogCat 是 Android 开发中最便捷,最常用的信息收集工具,没有之一。LogCat 中建议输出如下信息:
Application
,Activity
与Fragment
中的create
,destroy
,start
,stop
四个生命周期方法- 网络请求: 包括
request
的url
,header
,params
, 以及response
的header
,body
,需要注意的是,输出response
信息,也需要添加url
,方便我们知道是哪一个请求。 - io 操作: 包括文件/文件夹的
create
,delete
,read
,write
操作 - 关键模块的业务逻辑信息: 用户登录的生命周期 等
- 其他业务逻辑信息: 自己在调试时临时添加的 log 日志,注意在使用后需要删除这部分日志,避免污染代码库
- 日志持久化缓存: 日志缓存到本地的好处有两个
- 一是适用于问题设备不在我们身边的情况,可以让设备主人将日志发送给我们,或在 app 后台添加一个逻辑,自动上传错误日志
- 二是对于某些无法复现的问题,日志忠实的记录下了发生问题的那一次的信息
Debug tools
Debug 工具是 Android Studio 自带的一套非常强大的调试工具,也是比较为大家熟知的,但大部分人对于 Debug 工具的使用仍停留在打个断点的程度,下面简单列举一下 Debug 工具能做到的事。
- 断点类型
- 普通断点
- 条件断点: 满足某些条件才生效的断点
- 日志断点: 经过断点时向
debug console
输出日志 - 异常断点: 但凡捕获到该类型的异常,即在异常发生的位置进入断点模式
- 断点执行方式
- 单步执行: 每次向下执行一行代码
- 进入方法: 进入当前断点行所执行的方法,对应的还有一个退出方法
- 执行到光标处: 代码执行到光标处进入断点模式
- 计算表达式: 允许你在当前断点处执行一些代码,比如检查某些量是否正常,或修改一些量以进行测试
- 方法调用栈: 断点界面的左下角有一块完整的显示了当前代码处的方法调用栈
更详细的教程可以阅读下面这篇文章,作者总结的很棒,几乎 100% 覆盖了 Debug Tools 的功能。
Profiler
Profiler 是 Android Studio 自带的一套性能监测工具,它可以监测 app(以及设备)的实时运行状态,提供如下几个类型的监测:
- CPU: 可以监测 Activity 生命周期事件,实时 CPU 使用率,线程活动。支持对一段时间内的数据进行追踪,分析一段时间内方法的 cpu 耗时等。
- 内存: 可以查看实时内存用量,及
Java
,Native
,Graphics
等的内存占比。查看一段时间内的内存中对象的信息,包括创建对象的方法调用栈等。支持手动 gc。 - 网络: 可查看实时的网络通信信息,如
request
,response
的详细信息,且支持按线程查看。 - 能耗: 可以查看实时的 Activity 生命周期事件,应用的估算耗电量,以及可能会影响耗电量的系统事件。
第三方库
- 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 bisect
是 git
提供的一套二分法查找问题的工具。有时我们在某个节点发现了错误,但之前已经提交了多个 commit,我们并不知道出错的的到底是哪一个,但我们知道在很早之前的一个 commit 是正常的。如果按照 commit 一个个的 check out
排查效率是很低的,这时我们就可以使用 git bisect
。
1 | $ git bisect start |