mcpField
A
android-debugging

android-debugging

github.com/rcosteira79开发165175 次安装6 周前更新
代码仓库

关于 android-debugging

android-debugging 是用于排查 Android 问题的技能,强调先收集证据并查明根因,再进行修改。内容涵盖崩溃、ANR、内存泄漏、性能跟踪、混淆代码、构建失败和运行时 UI 检查。

主要功能

  1. 01分析 Logcat 崩溃与 ANR 跟踪
  2. 02排查泄漏并转储堆
  3. 03使用 Perfetto 测量性能
  4. 04解码混淆堆栈跟踪
  5. 05诊断 Gradle 构建失败
  6. 06检查运行时 UI 布局树

适用场景

  1. 01追查应用崩溃原因
  2. 02定位 ANR 背后的主线程阻塞
  3. 03将性能跟踪与基线对比
  4. 04检查 UI 状态或排查构建失败

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rcosteira79/android-skills --skill android-debugging

安装后在 Claude Code 中输入/android-debugging确认已加载。

详细介绍

Android 调试

概述

以根因优先为基础,采用 Android 专属的证据收集和调查技术。

根因优先的基础原则:

  • 找到根因之前不要修复。 先调查——复现问题、收集证据(记录观察到的值,而非假设值)、追踪原因。修补不了解的症状会引入两个错误。
  • 连续三次猜测失败 ⇒ 停下来审视架构,而不是继续猜测。

下面的 Android 专属工具可用于调查。*可选:*如果你使用专门的调试规范技能(例如 superpowers:systematic-debugging 或 ace:systematic-debugging),这些内容可作为补充;但并不依赖它们。

按问题类型收集证据

崩溃与异常
# Stream crash logs filtered by app package
adb logcat --pid=$(adb shell pidof -s com.example.app)

# Save full logcat to file for analysis
adb logcat -d > crash_log.txt

# Filter by tag
adb logcat -s "YourTag:E"

logcat 的关键日志级别:V(详细)D(调试)I(信息)W(警告)E(错误)F(致命)

阅读完整的堆栈跟踪——根因通常位于 Caused by: 链的底部,而不是顶层异常。

ANR(应用无响应)

ANR 表示主线程遭到阻塞。证据:

# Pull ANR trace from device
adb pull /data/anr/traces.txt ./anr_traces.txt

# Or stream while reproducing
adb logcat -s "ActivityManager:E" | grep -A 30 "ANR in"

查找:处于 MONITOR 状态的 main 线程(正在等待锁),或 main 线程上的阻塞 I/O。沿调用链向前追踪,找出持有该锁的对象。

**常见原因:**在主线程上进行数据库/网络调用、在主线程上使用 runBlocking、协程作用域之间发生死锁。

内存泄漏

将 LeakCanary 添加到 debugImplementation。它会在通知中自动显示泄漏跟踪信息。

从上到下阅读泄漏跟踪信息:第一行加粗的内容是泄漏对象,路径则显示是什么持有该引用。在适当的生命周期回调中清除该引用即可修复。

# Dump heap manually for Android Profiler analysis
adb shell am dumpheap com.example.app /data/local/tmp/heap.hprof
adb pull /data/local/tmp/heap.hprof ./heap.hprof
性能跟踪调查(Perfetto)

如果要调查 CPU、图形、I/O、IPC、内存或功耗方面的瓶颈,且 Logcat 和 ANR 跟踪信息无法满足需求,可捕获 Perfetto 跟踪并使用 SQL 查询。

修复前先测量。 对于性能回退,日志通常会误导人——在做任何改动之前,先捕获基线测量结果,然后与其进行二分对比。跟踪会告诉你时间花在了哪里;只有与已知正常的基线进行比较,才能确定实际发生回退的部分。

# Capture a trace (system-level, all categories)
adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/trace.pftrace \
  <<'EOF'
buffers { size_kb: 65536 }
data_sources { config { name: "linux.ftrace" } }
data_sources { config { name: "android.surfaceflinger.frame" } }
duration_ms: 10000
EOF

adb pull /data/misc/perfetto-traces/trace.pftrace ./

然后在 https://ui.perfetto.dev 打开跟踪并对其运行 SQL(例如,使用 SELECT name, dur FROM slice WHERE dur > 16e6 查找慢于 16ms 的帧,等等)。

如需 Agent 驱动的工作流,将调查意图(卡顿、启动缓慢、电量消耗)转化为合适的 Perfetto SQL,并在跟踪中反复迭代,请参阅 Google 的 perfetto-sql 和 perfetto-trace-analysis 技能(使用 android skills list 检查本地是否已安装;否则使用 android skills add perfetto-sql perfetto-trace-analysis)。它们提供领域提示(CPU/图形/I/O/IPC/内存/功耗)、强制使用便笺式证据链模式,以及 GLOB 优先于 LIKE 的查询规则。

R8 / ProGuard — 混淆堆栈跟踪

发布版本的崩溃堆栈跟踪经过了混淆。使用构建时生成的映射文件对其解码。

# retrace a crash (AGP 7+)
./gradlew :app:retrace --stacktrace-file crash.txt

# Or use the retrace CLI directly
java -jar retrace.jar mapping.txt crash.txt

映射文件位于 app/build/outputs/mapping/<variant>/mapping.txt。务必将其与发布构建产物一同归档。

如果某个类被意外移除或重命名,请在 proguard-rules.pro 中添加 -keep 规则,并使用以下命令验证:

./gradlew :app:assembleRelease
# Then inspect: app/build/outputs/mapping/release/usage.txt (removed) and seeds.txt (kept)

对于反向问题——阅读经过混淆的第三方代码,或解码映射文件不属于你的库的堆栈跟踪——retrace 不适用。请使用 jadx --deobf(在反编译输出中统一重命名),或使用 jadx --deobf-map(SDK 附带映射文件时)。android-reverse-engineering 插件涵盖完整工作流,其中包括通过字符串字面量和混淆后仍保留的框架类名作为锚点来浏览混淆代码的策略(请先检查本地是否已安装——它以 android-reverse-engineering:* skills 的形式提供)。

Gradle 构建失败

从底部向上阅读错误——Gradle 会将错误包装在多层信息中。

常见模式:

错误调查方法
Manifest merger failed检查 app/build/intermediates/merged_manifests/ 中合并后的输出;查找冲突的 android: 属性
Duplicate class运行 ./gradlew dependencies,查找多个传递依赖中是否包含同一个类;使用 exclude 或强制指定版本
Could not resolve检查仓库声明、VPN/代理,以及依赖版本是否存在
D8/R8: Type not present缺少 keep 规则或存在脱糖问题;检查 minSdk 与所用 API 是否匹配
KSP / KAPT error查找 Gradle 包装器消息上方处理器自身的错误
# Full dependency tree for a configuration
./gradlew :app:dependencies --configuration releaseRuntimeClasspath

# Run with stacktrace for deeper Gradle errors
./gradlew assembleDebug --stacktrace --info 2>&1 | grep -A 20 "FAILED"
运行时 UI 检查

如果是视觉问题(元素状态错误、内容缺失、元素重叠),请直接导出布局树,而不是根据截图推断:

# Full layout tree as JSON — search by class/text/bounds instead of parsing an image
android layout --pretty

# Only elements that changed since last call — useful for animations or transient state
android layout --diff --pretty

# Target a specific device, write to file
android layout --device=emulator-5554 -o layout.json

如果问题是“UI 状态是什么?”而不是“它看起来怎么样?”,请优先使用 android layout,而非 adb screencap。JSON 树可通过 grep 搜索,并且在多次调用间能通过 --diff 保留状态。

Compose 重组问题

如需深入分析 Compose 性能(稳定性、跳过重组、基线配置文件),请参阅 android-skills:compose → compose/references/performance.md。

状态错误或意外重新渲染:

  1. Layout Inspector(Android Studio)→ 启用“Show recomposition counts”以识别热点路径。对于无头/CLI 工作流,android layout --diff 会提供一个 JSON 树,显示帧间发生的变化。
  2. 临时添加 SideEffect { Log.d("Recompose", "MyComposable recomposed") } 进行确认
  3. 检查是否在组合内创建了 State 对象(请使用 remember)
  4. 检查状态数据类的 equals()——如果没有实现 equals,即使值相同,新对象也会触发重组

**注意:**从 Compose compiler 2.0+(Kotlin 2.0+)开始,默认启用强跳过模式,编译器会自动记忆捕获稳定引用的 lambda。在大多数情况下,不再需要手动使用 remember {{ }} 包装。如果 lambda 导致重组过多,请检查捕获的引用是否不稳定(可变集合、非数据类),而不是用 remember 包装。

(这里未涵盖标准 adb 命令——devices、install -r、am start、pm clear、ps、run-as … ls /data/data/<pkg>/;如上所述,对于视觉状态问题,请优先使用 android layout,而非 screencap。)

多组件证据模板

对于跨越多个层的 issue(例如:Repository → ViewModel → UI):

// Temporarily instrument each boundary with a UNIQUE run-specific tag
// (pick a fresh suffix per session, e.g. DEBUG-a4f2) so the SAME tag both
// filters logcat at runtime and greps cleanly at teardown.
class UserRepository(...) {
    suspend fun fetchUser(id: String): User {
        Log.d("DEBUG-a4f2", "Repository: fetching user $id")
        val result = api.getUser(id)
        Log.d("DEBUG-a4f2", "Repository: received ${result}")
        return result
    }
}

运行一次,以确定是哪个层产生了错误值。在运行时使用 adb logcat -s "DEBUG-a4f2" 过滤日志,然后用一条 grep -rl "DEBUG-a4f2" 命令移除所有插桩。使用每个会话独有的标签,才能让这两个单行命令都正常工作;像 DEBUG_LAYER 这样的共享标签会在不同会话间发生冲突,并留下孤立日志。然后单独调查相关层,再提出修复方案。

危险信号

  • 未阅读完整的 Caused by: 链就修复崩溃
  • 未检查映射文件就猜测是 R8 问题
  • 添加 Thread.sleep() 来“修复” ANR 或竞态条件
  • 不弄清重复依赖为何存在,就通过添加 exclude 解决依赖冲突
  • 不弄清是什么触发了重新组合,就通过用 key() 包装来修复 Compose Bug
  • 将临时插桩留在代码树中——为每条调试日志加上每个会话独有的前缀(例如 DEBUG-a4f2),以便用一条 grep 命令清理,并在宣布修复完成前全部移除

评论

支持 Markdown