はじめに

こんにちは。and factory Androidエンジニアの鬼倉です。

今回は、私が携わるAndroidプロジェクトでClaude Codeを活用し、ANR改善に取り組んだアプローチを紹介します。ANRの原因はさまざまですが、本記事ではメモリリークを原因とするANRに焦点を当てます。

Android開発者にとって、メモリリーク対応のためにLeakCanaryを導入したはいいものの、結局修正できず通知が出続けているという方も多いのではないでしょうか?

そこで今回私のプロジェクトでは、LeakCanaryによる検知に加えてClaude CodeのSkillsを活用した自動修正の仕組みを構築しましたので紹介します。


ANRとは?

ANR(Application Not Responding)とは、アプリが指定時間内に操作を応答できなかった場合に発生するシステムエラーです。代表的には、UIスレッドが入力イベントに対して5秒以内に応答できない場合などでトリガーされます(BroadcastReceiverやServiceでも発生)。デッドロック、I/O処理のブロック、高負荷処理などさまざまな原因で発生します。Crashと同様にユーザー体験を大きく損なう問題ですが、Crashほど原因が明確でなく再現も難しいため軽視されがちです。ANR率はFirebase CrashlyticsやGoogle Play Consoleで確認できます。


本記事の環境

技術バージョン
Kotlin2.3.20
AGP(Android Gradle Plugin)8.9.3
LeakCanary2.14

ANR改善が進まなかった背景

まずはそもそもメモリリークによるANRの修正改善がなぜ進んでいなかったのかを整理します。

FirebaseのANRレポートだけでは原因を深掘りしにくい

Crashの場合、Firebase Crashlyticsに発生元のExceptionが記録されるため、その箇所が直接的な修正対象になります。一方、メモリリーク由来のANRは事情が異なります。複数箇所のメモリリークが徐々に蓄積し、最終的にANRとして発生します。そのため、Firebase上のレポートから直接的な原因箇所を特定することが困難です。

さらに、メモリリークの蓄積は端末のメモリ状況やユーザーの操作パターンに依存するため、開発環境での再現も難しいという問題があります。

ANR改善のためのLeakCanary導入と修正対応の難しさ

ANRの発生件数を改善するため、メモリリーク検知の定番ライブラリであるLeakCanaryを導入しました。LeakCanaryを導入すると、開発中の手元の端末上でメモリリークの発生をリアルタイムに把握できます。

しかし、検知できることと修正できることは別の問題です。LeakCanaryはメモリリークの発生タイミングやある程度の情報を提供しますが、明確なコード上の原因までは教えてくれません。開発者自身が情報をもとに原因となるコードを探し出し、修正する必要があります。

さらに厄介なのは、メモリリークの原因となるコードは一見すると問題がないように見える点です。修正にはAndroid開発やKotlinに関する深い知識が求められ、1件あたりの対応に時間を要します。結果として、他の機能開発やCrash対応に比べて優先度が下がり、改善が後回しになりがちな状況が続いていました。

Claude Codeを活用したメモリリーク改善アプローチ

以上の理由により、LeakCanaryやFirebaseのANRログだけでは自力での修正は困難でした。それに対してどのようにAIを活用して修正をしていくのでしょうか?

最もシンプルなアプローチとしては、LeakCanaryが通知を出したら内容をClaude Codeにコピーペーストして修正を依頼することです。この方法でももちろん対応できます。

しかし、Logcatを含めた前後情報があればより精度が高まりますし、コピーペーストという作業をなるべく減らし即座に修正を依頼する環境を構築しなければ、再び後回しになりかねません。

今回私たちが構築したのは、LeakCanaryが通知を出した瞬間にClaude Codeへ/investigate-leakと依頼するだけで完結する方法です。AIが自ら端末のLogcatを確認し、ログ情報からメモリリークの修正を提案します。

LeakCanaryのリークトレースをClaude Codeから取得可能にする

なお、本記事のアプローチではLogcatの内容をAIに読み取らせるため、ユーザーの個人情報やAPIキーといった機密情報がLogに出力されていないことが前提となります。

LeakCanaryはメモリリークを検知すると端末上に通知を表示します。しかし、デフォルトではヒープダンプの解析結果がlogcatに出力されません。Claude Codeがリークトレースを自律的に取得できるよう、解析結果をlogcatに出力する仕組みを追加しました。

具体的には、LeakCanaryのonHeapAnalyzedListenerをカスタマイズしています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import leakcanary.LeakCanary
import leakcanary.OnHeapAnalyzedListener
import timber.log.Timber

val defaultListener = LeakCanary.config.onHeapAnalyzedListener
LeakCanary.config = LeakCanary.config.copy(
    onHeapAnalyzedListener = OnHeapAnalyzedListener { heapAnalysis ->
        defaultListener.onHeapAnalyzed(heapAnalysis)

        // ここでLogに出力(ここではTimberを利用しています)
        Timber.tag("LeakCanary").d(heapAnalysis.toString())
    }
)

この設定により、Claude Codeは以下のadbコマンドでリークトレース全文を取得できます。

1
adb logcat -d -s LeakCanary

なお、LeakCanaryは debugImplementation で導入するため、このクラスは debug ソースセットに配置してください。main ソースセットに置くと、releaseビルド時にLeakCanaryのクラスが見つからずビルドエラーになります。

Claude Code Skillsでメモリリーク調査を自動化する

次に、LeakCanaryが検知したメモリリークの調査から修正までを一貫して実行するClaude Code Skill(investigate-leak)を作成しました。

このSkillは、以下のフローで動作します。

flowchart LR
    A[LeakCanary\n通知発生] --> B[端末接続\n確認]
    B --> C[リークトレース\n取得]
    C --> D[リークトレース\n解析]
    D --> E[コードベース\n照合]
    E --> F[修正プラン\n提示]
    F --> G[修正の\n実装]
  1. 端末接続の確認adb devicesで接続状態を確認する
  2. リークトレースの取得adb logcat -d -s LeakCanaryでログを取得する
  3. リークトレースの解析 — リファレンスチェーンからリーク原因の参照を特定する
  4. コードベースとの照合 — リークに関連するクラスやフィールドをコードベースから検索し、根本原因を特定する
  5. 修正プランの提示 — 調査結果と具体的な修正内容をまとめて提示する
  6. 修正の実装 — ユーザーが承認した場合、修正を実装する

開発者は、LeakCanaryの通知が出たタイミングでClaude Code上から/investigate-leakを実行するだけで、ログの取得から原因特定、修正プランの提示までが自動的に進みます。

Skillのポイント: リークトレースの取得

Skill内では、adbコマンドを使ってリークトレースを取得する手順を定義しています。

1
adb logcat -d -s LeakCanary | tail -500

Skillにこの手順を記載することで、Claude Codeが直接Logcatを参照します。開発者がリークトレースをコピーペーストする手間を省き、即座に調査を開始できる仕組みです。

また、リークトレースの読み方もSkill内に定義しています。以下はその一部です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
┬───
│ GC Root: System class
├─ com.example.SomeClass instance
│    Leaking: NO (...)
│    ↓ SomeClass.listener        ← この参照が原因
│                ~~~~~~~~        ← 波線が原因箇所を示す
├─ com.example.LeakedActivity instance
│    Leaking: YES (Activity#mDestroyed is true)
╰───

このようにLeakCanaryのログ形式もSkillに記載することで、解析精度を高めています。

また、よくあるリークパターンと典型的な修正方法もSkill内に定義しており、パターンマッチによる迅速な原因特定と修正を可能にしています。

導入して変わったこと

今まで放置されていたメモリリークを、このSkillの導入後わずか数日で3箇所修正できました。すでに開発環境で操作していてもLeakCanaryの通知が発生しなくなっています。

さらに、新規実装時にLeakCanaryが反応した場合でも即座に修正を依頼できるため、メモリリークのないクリーンな状態を維持しながら開発を進められるようになりました。

まとめ

  • メモリリーク由来のANRは原因特定が難しく、改善が後回しになりやすい
  • LeakCanaryで検知はできるが、修正には深い知識と時間が必要
  • LeakCanaryの解析結果をlogcatに出力する仕組みを追加し、Claude Codeがadb経由でリークトレースを自律的に取得できるようにした
  • Claude Code Skillsで検知から修正プラン提示・実装までを自動化した
  • 導入後数日で3箇所のメモリリークを修正し、新規実装時もクリーンな状態を維持できるようになった

なお、Skill本体のコードは本記事では掲載していませんが、紹介したコンセプトと設計方針をもとにすれば同様の仕組みはゼロから構築可能です。

LeakCanaryを導入しているものの改善が進んでいないプロジェクトも多いのではないでしょうか。現在であればAIを活用することで、スムーズにメモリリークの修正を進められます。ぜひClaude Codeを活用してANR改善に取り組んでみてください。