我日常用的 Android 调试工具清单
我日常用的 Android 调试工具清单
Android 开发的调试工具生态在过去几年发生了不小的变化。Google 官方工具链在持续迭代,第三方工具也在特定场景下找到了自己的位置。这篇文章想聊的不是那种"十大必备工具"式的罗列,而是我实际工作中高频使用、踩过坑、也经历过"原来还能这样"时刻的那些工具。有些你可能已经很熟悉,有些或许能补上一个你之前没注意到的盲区。
adb 的隐藏深度:不只是安装和 logcat
大部分人接触 adb 是从 adb install 和 adb logcat 开始的,但 adb 的现代版本(platform-tools 34.0.0 之后)藏了不少被低估的能力。我个人最常用的是 adb shell cmd 系列命令,它直接调用系统服务的 binder 接口,绕过了很多权限限制。
比如调试应用 standby 状态(App Standby Buckets)时,adb shell am set-standby-bucket com.example.app active 这种命令在测试 Doze 模式行为时几乎是必需的。更隐蔽一点的是 adb shell dumpsys jobscheduler,它能打印出 JobScheduler 的完整内部状态,包括每个任务的延迟原因——是充电限制?是网络不满足?还是 app standby 桶的限制?这些信息在 logcat 里通常被淹没在噪声中。
adb 的无线调试在 Android 11 之后稳定了很多,但我仍然遇到过一个坑:某些厂商(特别是早期 MIUI 和 ColorOS)的无线 adb 配对码会在系统深色模式下显示为黑色文字,根本看不见。解决方案是用 adb pair ip:port 命令行直接输入配对码,绕过系统 UI 的显示 bug。这个 issue 在 Xiaomi 的社区论坛里被反复提及,但官方修复得很慢。
adb shell settings 命令是另一个调试利器。settings put global hidden_api_policy 1 可以临时关闭 hidden API 限制,这在分析系统框架行为时很有用,但记住这个设置会在系统更新后被重置。我还用 settings put system screen_off_timeout 1800000 把屏幕超时调到 30 分钟,避免长时间抓 log 时屏幕熄灭打断测试。
adb 的局限也很明显:它对系统级行为的模拟是"近似"的。比如 adb shell dumpsys battery set level 15 可以模拟低电量,但某些厂商的自定义省电逻辑(如华为的"超级省电")并不会被触发,因为它们依赖额外的系统服务状态。这时候就必须上真机手动测试,adb 帮不了忙。
logcat 的过滤艺术:从 grep 到 pidcat
原始 adb logcat 的输出密度对大型应用几乎是不可读的。我早期用 grep 和 awk 组合过滤,但进程 ID 变化、TAG 不一致、多行堆栈断裂这些问题让脚本维护成本很高。
pidcat(https://github.com/JakeWharton/pidcat)是我用了多年的替代方案。它是 Jake Wharton 写的 Python 脚本,核心改进是按进程 ID 过滤并彩色化输出,同时正确处理 logcat 的多行消息(比如异常堆栈)。安装很简单:pip install pidcat,用法是 pidcat com.example.app。
pidcat 的一个细节设计很贴心:它会自动追踪进程重启。Android 应用被系统杀死后重新启动时,进程 ID 会变,pidcat 能通过包名重新锁定新进程,不需要手动重跑命令。这在调试崩溃后自动恢复的场景时省了很多事。
但 pidcat 的维护状态需要留意。Jake Wharton 在 2022 年之后基本停止了更新,Python 3.10+ 的兼容性问题社区里有几个 PR 但没合并。我本地维护了一个 fork 修了几个 regex 的 warning,如果你也遇到类似问题,可以直接用 adb logcat -v color 作为备选——这是 adb 原生支持的彩色输出,虽然没有 pidcat 的进程追踪智能,但兼容性更好。
对于需要持久化分析的日志,我倾向于用 adb logcat -v threadtime -b all -d > full_log.txt 抓完整缓冲区,然后用本地工具分析。-b all 会包含 crash、events、main、system 等所有 buffer,很多 ANR 的根因其实藏在 system buffer 的事件顺序里,只看 main buffer 会漏掉关键上下文。
Layout Inspector 的进化与性能陷阱
Android Studio 自带的 Layout Inspector 在 Electric Eel 版本(2022.1.1)之后经历了重大重构,从基于屏幕截图的叠加分析改成了直接读取 Compose 的语义树和 View 层级。这个改动对 Compose 开发者是福音,但对纯 View 系统应用的性能开销变大了。
我实际测试过一个中等复杂度的 RecyclerView 页面:旧版 Layout Inspector 连接耗时约 3 秒,新版需要 8-12 秒,且抓取过程中应用帧率会从 60fps 掉到 20fps 左右。这是因为新版为了支持 Compose 的重组追踪,注入了更重的检测代码。如果你只是快速检查 View 的 measure/layout 参数,旧版的 "Legacy Layout Inspector" 反而更快——在 Android Studio 的设置里搜索 "legacy" 可以切换回来。
Compose 的重组计数器(Recomposition Counts)是新版的杀手级功能。在 Layout Inspector 里开启后,能看到每个 Composable 的重组次数和跳过次数。但这个功能有个隐蔽的坑:它依赖编译期的 Compose 编译器插件插桩,如果某个模块的 Compose 版本和编译器插件版本不匹配(比如 compose-ui 1.4.0 配 compiler 1.3.2),重组计数会完全静默失效,不会报错,只是显示为零。我花了半小时排查过一次"为什么我的重组优化没效果",最后发现是版本对齐问题。
对于需要量化布局性能的场景,我更信任 Systrace/Perfetto,而不是 Layout Inspector 的实时渲染。Layout Inspector 的"3D 层级视图"看起来很酷,但复杂页面的 GPU 渲染开销会让它卡顿到几乎不可用,这个功能更适合截图做演示,不适合日常调试。
Profiler:内存分析的误读与修正
Android Studio Profiler 的内存堆转储(Heap Dump)是我定位内存泄漏的主要工具,但它的默认视图有误导性。默认按 "Class Name" 排序时,大量的 byte[] 和 String 条目会让人无从下手。我习惯切换到 "Arrange by package" 视图,然后直接定位到应用包名和已知第三方 SDK 的包名。
一个具体的技巧:LeakCanary(后面会专门讲)报告了 MainActivity$2 的泄漏,但 Heap Dump 里找不到这个匿名类。因为 ART 在编译时可能把匿名类优化成了 MainActivity$$ExternalSyntheticLambda2 之类的合成类名。这时候需要用 "Find Object" 搜索 MainActivity 的所有实例,然后检查它们的引用链(Reference Chain)里的 this$0 字段,通常指向外部 Activity 实例。
Profiler 的 Native Memory Profiler 在 Android 12+ 设备上可用,但采样频率和精度受限于设备的 perf_event_paranoid 设置。部分厂商(三星、OPPO)的系统会把这个值设得很高,导致 Native 内存数据几乎为空。验证方法是 adb shell cat /proc/sys/kernel/perf_event_paranoid,如果返回值大于 1,就需要 root 权限才能获取完整的 native 内存采样。这在调试 C/C++ 库(如 FFmpeg、OpenCV)的内存问题时是个硬门槛。
CPU Profiler 的 "Trace Java Methods" 模式开销极大,我很少用。更实际的做法是用 "Sample Java Methods" 配合自定义的 Trace.beginSection/Trace.endSection 标记关键路径。这些标记在 Systrace/Perfetto 里会显示为自定义的线程轨道,定位问题非常直接。
LeakCanary:从集成到解读
Square 的 LeakCanary(https://github.com/square/leakcanary)几乎是 Android 内存泄漏检测的事实标准。我现在的集成方式有所变化:不再在 release build 里完全排除,而是用 leakcanary-android-release 这个 artifact 做轻量化的监控,只记录不弹通知。
LeakCanary 2.x 的重大改进是用了 Shark 这个自研的堆分析引擎,替代了之前的 perflib。Shark 的分析速度快了一个数量级,而且支持 streaming 分析大堆文件。我实测过一个 400MB 的 hprof 文件,LeakCanary 1.6 需要 90 秒,2.10 版本只要 6 秒。
但 Shark 的泄漏判定逻辑也有误报。比如 ViewModel 的 onCleared() 调用是异步的,如果 Activity 销毁后立即旋转屏幕,新 Activity 创建时旧 ViewModel 可能还没走完清理流程,LeakCanary 会报"ViewModel 持有已销毁 Activity 的引用"。这需要结合业务逻辑判断,不能无脑修。我的做法是给这类已知 benign 的泄漏加 LeakCanary.config.leakingObjectFinder 的自定义过滤器,避免噪音淹没真正的泄漏。
LeakCanary 的 heapDumpPolicy 配置值得细调。默认是在 retained 对象达到 5 个时触发堆转储,但在低内存设备(2GB RAM 的 Android Go 设备)上,堆转储本身可能导致应用被系统杀死。我把阈值调到 10,同时缩短了 retainedVisibleThreshold 的等待时间,让泄漏检测更快触发,减少同时积累的 retained 对象数。
Perfetto 与 Systrace:从命令行到 UI
Google 在 2019 年用 Perfetto 替代了传统的 Systrace,但 systrace.py 脚本仍然可用,只是底层换成了 Perfetto 的采集器。我现在的 workflow 是直接用 perfetto 命令行抓 trace,然后在 https://ui.perfetto.dev 分析,不依赖 Android Studio 的集成。
一个具体的命令模板:
adb shell perfetto -o /data/misc/perfetto-traces/trace_file.perfetto-trace -t 10s \
sched freq idle am wm gfx view binder_driver hal dalvik camera input res memory这个命令抓了 10 秒,包含调度、频率、空闲状态、ActivityManager、WindowManager、图形、View、Binder、HAL、虚拟机、相机、输入、资源和内存的全量数据。文件生成在设备上,再用 adb pull 拉下来。
Perfetto UI 的 query 功能非常强大,但学习曲线陡峭。我常用的一个 SQL 查询是找主线程的卡顿帧:
SELECT ts, dur, name
FROM thread_state
WHERE utid = (
SELECT utid FROM thread WHERE name = 'android.ui'
)
AND dur > 16000000
ORDER BY ts这个查询找的是 "android.ui" 线程(主线程)上持续时间超过 16ms 的状态片段。dur 的单位是纳秒,所以 16000000 是 16ms。结果能直接定位到卡顿发生的精确时间戳,再关联到同期的其他线程事件。
Perfetto 的局限是文件体积。上面那个 10 秒全量抓取的 trace 通常有 50-100MB,通过 adb pull 传大文件时,USB 2.0 连接会很慢。我遇到过 adb pull 到 90% 中断的情况,后来改用 adb shell gzip /data/misc/perfetto-traces/trace_file.perfetto-trace 先压缩,再 pull,体积能降到 5-10MB。
Flipper:Facebook 的调试平台与它的边界
Flipper(https://github.com/facebook/flipper)是我调试网络请求和数据库的首选桌面工具。它的 Network 插件能拦截 OkHttp 的所有请求,展示完整的请求头、响应体和耗时,比 Charles Proxy 配置简单得多,而且支持 WebSocket 的帧级 inspection。
集成 Flipper 需要应用层改动:
val client = AndroidFlipperClient.getInstance(context)
client.addPlugin(NetworkFlipperPlugin())
client.start()这个初始化代码需要放在 debug build 里,通常配合 BuildConfig.DEBUG 或自定义的 BuildType 条件。我踩过一个坑:在 multi-module 项目里,如果 library module 也依赖了 Flipper 的 runtime,但 application module 没调用 client.start(),library 里的插件会静默失效,不会报错,只是数据不显示。这导致我一度怀疑是 Flipper 的版本 bug,其实是初始化顺序问题。
Flipper 的 Layout 插件对 Compose 的支持还在追赶中。截至 0.250.0 版本,它能显示 Compose 的语义树,但重组状态、参数值等关键调试信息还没有。对于纯 Compose 项目,Layout Inspector 仍然是更好的选择。Flipper 的优势在于它的插件生态——Crash Reporter、Shared Preferences Viewer、Navigation 插件这些在混合开发(View + Compose 共存)的项目里仍然很有价值。
Flipper 的一个设计缺陷是它对桌面端的强依赖。所有数据处理都在桌面应用里完成,这意味着断网或远程调试时(比如测试机在公司,人在家),Flipper 完全不可用。相比之下,Android Studio Profiler 的数据处理在 IDE 本地,adb 连接可以走 SSH 隧道,灵活性更好。
Charles Proxy 与网络调试的残余场景
Flipper 覆盖了大部分 OkHttp 的调试需求,但 Charles Proxy(https://www.charlesproxy.com,$50 个人授权)仍然有不可替代的场景:调试非 OkHttp 的网络栈(如 Cronet、原生 WebSocket 库)、模拟弱网环境、以及修改请求响应做边界测试。
Charles 的 Map Local 功能让我能不用改后端代码就模拟各种异常响应。比如测试客户端对 500 错误码的处理,或者返回一个超大的 JSON 触发解析性能问题。Flipper 的 Mock 插件也能做类似的事,但 Charles 的操作更直觉,不需要在应用代码里预埋 mock 逻辑。
SSL 代理的配置是 Charles 的入门门槛。Android 7.0+ 的应用默认不信任用户安装的 CA 证书,需要把 Charles 的证书放到应用的 res/raw/ 里,并在 NetworkSecurityConfig 中配置 <certificates src="@raw/charles"/>。这个改动不能进 release 包,我通常用 productFlavor 隔离,或者更简单地,用 debug 版本的 android:usesCleartextTraffic="true" 绕过 HTTPS(仅限内网测试环境)。
Charles 的弱网模拟(Throttle)比 Android 模拟器的原生功能更精细。可以按域名单独限速,模拟 4G/3G/Edge 的不同延迟和丢包率。我常用它来测试视频播放的缓冲策略,以及大图加载的渐进式降级。
Stetho:被低估的轻量选项
Facebook 的 Stetho(https://github.com/facebook/stetho)已经停止维护,但我在维护一些老项目时仍然用它。它的独特价值是零桌面端依赖——直接复用 Chrome DevTools 的 Remote Debugging 协议,打开 chrome://inspect 就能用。
Stetho 的 Database 插件能直接执行 SQL 查询,这比 Flipper 的只读查看更方便做数据验证。Network 插件的展示不如 Flipper 现代,但功能足够。它的主要问题是只支持 Chrome,Edge 和 Firefox 的 DevTools 兼容性有问题,而且随着 Chrome 升级,某些面板会显示异常。
新项目我不会推荐 Stetho,但如果你在维护 2018-2020 年左右启动的项目,且已经集成了 Stetho,没必要为了追新强行迁移到 Flipper。Stetho 的代码量很小,自己 fork 修几个兼容性问题成本不高。
模拟器与真机的选择:不是非此即彼
Android Emulator 在 Apple Silicon 上的性能提升很大,但真机调试仍然是不可替代的。我日常保持 3-4 台测试机:一台 Pixel(最新系统版本,用于验证 Google 原生行为)、一台三星(One UI 的自定义逻辑差异极大)、一台低端机(2GB RAM,测试内存和性能边界)、一台特定厂商的测试机(根据项目目标用户调整)。
Emulator 的 Snapshots 功能节省了大量重复配置时间。我通常会 snapshot 一个已经登录测试账号、装好辅助工具(如测试用的输入法、文件管理器)的干净状态,每次测试前恢复快照。但注意:如果 snapshot 时开启了 "Quick Boot",某些依赖硬件指纹(如 SafetyNet)的测试会失败,因为 snapshot 里的硬件状态是虚拟化的。
Emulator 的 Extended Controls(侧边栏的 ... 按钮)里有 Sensors 模拟,可以模拟指纹、旋转、位置变化。但 GPS 路径模拟(Route)的精度很差,做地图类应用的连续轨迹测试时,真机 + mock location app 更可靠。我常用的 mock location 工具是 "Fake GPS Location"(Google Play 上有很多同名,我用的包名是 com.blogspot.newapphorizons.fakegps),它支持 GPX 文件导入,能复现真实的用户轨迹。
命令行工具箱:beyond adb
除了 adb,我日常还依赖几个命令行工具:
apkanalyzer 是 Android SDK 自带的 APK 分析工具。apkanalyzer apk summary app.apk 能快速看文件大小分布,apkanalyzer dex list app.apk 列出所有类和方法数。在接近 64K 方法数限制时,这比 Android Studio 的 Build Analyzer 更快定位哪个依赖库在膨胀。
bundletool 是处理 App Bundle 的必需工具。Google Play 的 Dynamic Delivery 本地验证全靠它:bundletool build-apks --bundle=app.aab --output=app.apks 生成拆分 APK 集合,再 bundletool install-apks --apks=app.apks 模拟 Play 商店的安装行为。我遇到过 aab 在本地安装正常,但用户通过 Play 下载后崩溃的问题,最后发现是 bundletool 版本过旧,没有模拟 Play 的最新拆分策略。
retrace 用于还原 ProGuard/R8 混淆后的堆栈。Android SDK 里的 retrace.bat/retrace.sh 是基础工具,但我更习惯用 Firebase Crashlytics 或 Bugsnag 的自动还原。只有在分析离线日志时才会手动跑 retrace,命令是 retrace mapping.txt obfuscated_trace.txt。
sqlite3 在设备上直接查数据库。adb shell sqlite3 /data/data/com.example.app/databases/app.db ".tables" 看表结构,.schema table_name 看建表语句。这个工具在数据库文件损坏、需要紧急导出数据时很有用,但注意 Android 10+ 的 Scoped Storage 限制,应用私有目录的访问需要 run-as 或 root。
版本管理与环境隔离:sdkmanager 和 Docker
Android SDK 的版本碎片化是调试工具链的一大痛点。我同时维护的项目可能分别要求 compileSdk 33、34、35,对应的 build-tools、platform-tools 版本各不相同。
sdkmanager 是命令行管理 SDK 组件的工具,但 Google 近年更推荐用 Android Studio 的 GUI 管理。我个人仍然保留 sdkmanager 的脚本化安装,因为 CI 环境和团队新成员的环境搭建需要可复现。一个典型的初始化脚本:
sdkmanager "platforms;android-34" "build-tools;34.0.0" "platform-tools" "emulator"对于更严格的隔离,我用 Docker 跑 Android 构建。mingchen/android-build-box 这个镜像包含了完整的 SDK 和 NDK,但体积很大(>5GB)。更轻量的选择是 circleci/android:api-34-node,基于 CircleCI 的优化镜像,去掉了不常用的模拟器系统镜像。
Docker 里的 adb 连接需要额外处理。我通常用 --privileged -v /dev/bus/usb:/dev/bus/usb 挂载 USB 设备,然后在容器内重新启动 adb server。这个方案在 macOS 上因为 Docker Desktop 的虚拟机隔离,USB 直通有问题,所以 macOS 下我还是用本地 adb,只把编译放在 Docker 里。
最后几个零散但实用的工具
scrcpy(https://github.com/Genymobile/scrcpy)是我演示和录屏的首选。它通过 adb 传输屏幕编码流,延迟低到可以玩游戏。scrcpy --record file.mp4 直接录屏,比 Android 系统录屏的兼容性更好(某些 DRM 内容系统录屏黑屏,scrcpy 有时能绕过)。scrcpy --crop 1224:2208:0:0 可以只录屏幕的一部分,做文档配图时很有用。
adb-join-wifi 这个脚本(https://github.com/steinwurf/adb-join-wifi)批量配置测试机的 WiFi 连接,避免在每台设备上手动输入密码。测试机多了之后,这个节省的时间很可观。
Android Debug Bridge 的 sync 命令很少有人用:adb sync 能把 PC 上的目录增量同步到设备的 /system 分区,需要 root,但在调试系统应用修改时比反复 adb push 快很多。
这些工具构成了我日常调试的基础设施。没有一个是完美的,每个都有特定的适用场景和已知缺陷。工具的价值不在于功能列表的长短,而在于你知道它的边界在哪里,在什么时候该换另一个。Android 开发的复杂性很大程度上来自生态的碎片化,工具链的选择本身也是一种对复杂性的管理策略。这篇文章提到的工具,有些我可能明年就不再用了,有些已经用了五年以上——变化是常态,但调试的核心逻辑(观察、假设、验证、量化)基本不变。