android-debugging について
android-debugging は、変更前に証拠を集めて根本原因を調べる Android 向けスキルです。クラッシュ、ANR、メモリリーク、パフォーマンストレース、難読化コード、ビルド失敗、実行時 UI の調査を扱います。
主な機能
- 01Logcat のクラッシュと ANR トレースの分析
- 02リーク調査とヒープダンプ
- 03Perfetto による性能測定
- 04難読化スタックトレースのデコード
- 05Gradle ビルド失敗の診断
- 06実行時 UI レイアウトツリーの確認
活用シーン
- 01アプリクラッシュの原因を追跡
- 02ANR の背後にあるメインスレッドの停止を特定
- 03性能トレースを基準値と比較
- 04UI 状態の確認やビルド失敗の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rcosteira79/ android-skills --skill android-debuggingインストール後、Claude Code で/android-debuggingを入力して読み込まれたか確認します。
詳細
Android のデバッグ
概要
根本原因を最優先する方針に基づいた、Android 固有の証拠収集と調査の手法です。
根本原因を最優先する方針:
- 根本原因を特定する前に修正してはいけません。 まず調査します。再現し、証拠(推測ではなく、観測された値)を集め、原因をたどります。理解していない症状にパッチを当てると、バグが2つ生まれます。
- 推測に3回失敗したら、再び推測するのではなく、立ち止まってアーキテクチャを見直してください。
以下の 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)
Logcat や ANR トレースで分かる範囲を超えて、CPU、グラフィックス、I/O、IPC、メモリ、電力にまたがるボトルネックを調査するには、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 を実行します(16ms より遅いフレームの場合は SELECT name, dur FROM slice WHERE dur > 16e6 など)。
調査の意図(ジャンク、起動の遅さ、バッテリー消費)を適切な Perfetto SQL に変換し、トレースを反復的に調べるエージェント主導のワークフローについては、Google の perfetto-sql および perfetto-trace-analysis スキルをご覧ください(ローカルにインストールされているか確認するには android skills list、インストールされていない場合は android skills add perfetto-sql perfetto-trace-analysis を実行します)。これらのスキルには、ドメインヒント(CPU/グラフィックス/I/O/IPC/メモリ/電力)、必須のスクラッチパッドを使った証拠の連鎖パターン、および LIKE より GLOB を優先するクエリルールが含まれています。
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 plugin では、難読化を免れた文字列リテラルやフレームワークのクラス名を手がかりに難読化コードをたどる、文字列を使ったアンカー戦略を含む一連のワークフローを解説しています(まずローカルにインストール済みか確認してください。android-reverse-engineering:* スキルとして提供されています)。
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 の状態はどうなっているか」が知りたい場合は、adb screencap より android layout を優先してください。JSON ツリーは grep で検索でき、呼び出し間の --diff の状態も保持されます。
Compose の再コンポジションに関するバグ
安定性、再コンポジションのスキップ、ベースラインプロファイルなど、Compose のパフォーマンスを詳しく分析するには、android-skills:compose → compose/references/performance.md を参照してください。
状態が正しくない、または予期せず再レンダリングされる場合:
- Layout Inspector(Android Studio)で「Show recomposition counts」を有効にし、負荷の高い箇所を特定します。ヘッドレス/CLI のワークフローでは、
android layout --diffによりフレーム間で変化した内容の JSON ツリーを取得できます。 - 一時的に
SideEffect { Log.d("Recompose", "MyComposable recomposed") }を追加して確認します。 Stateオブジェクトがコンポジション内で作成されていないことを確認します(rememberを使います)。- 状態を保持するデータクラスの
equals()を確認します。equalsが実装されていない場合、同じ値を持つ新しいオブジェクトでも再コンポジションが発生します。
注: Compose コンパイラー 2.0+(Kotlin 2.0+)以降、強力なスキップモードがデフォルトで有効になり、安定した参照をキャプチャするラムダはコンパイラーによって自動的にメモ化されます。ほとんどの場合、手動で remember {{ }} でラップする必要はなくなりました。ラムダによる過剰な再コンポジションが見られる場合は、remember でラップするのではなく、キャプチャされた参照が不安定かどうか(可変コレクション、データクラスでないクラスなど)を確認してください。
(標準の adb コマンドである devices、install -r、am start、pm clear、ps、run-as … ls /data/data/<pkg>/ はここでは扱いません。前述のとおり、視覚的な状態に関するバグでは screencap より android layout を優先してください。)
複数コンポーネントの証拠収集テンプレート
複数のレイヤーにまたがる問題の場合(例:リポジトリ → 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 の問題だと推測する
- ANR や競合状態を「修正」するために
Thread.sleep()を追加する - 重複が存在する理由を理解せずに
excludeを追加して依存関係の競合を解決する - 再コンポーズを引き起こす要因を理解せずに
key()でラップして Compose のバグを修正する - 一時的な計測用コードをコードベースに残す — すべてのデバッグログにセッションごとに一意のプレフィックス(例:
DEBUG-a4f2)を付け、grep一度で削除できるようにし、修正完了を宣言する前にすべて削除する