LeakCanary は、Square社によるAndroidアプリケーションの自動メモリリーク検出のためのオープンソースライブラリです。開発プロセスに統合され、Activity、Fragment、ViewModelおよびその他のコンポーネントのライフサイクルをリアルタイムで監視し、リークが発生したらすぐに通知します。Square Open Sourceによると、このライブラリは数千のプロジェクトで使用され、Androidにおけるメモリ診断のデファクトスタンダードと見なされています。
重要ポイント
LeakCanary は、Square社によって開発されたAndroidアプリケーションの自動メモリリーク検出のためのライブラリです。アプリのビルドプロセスに統合され、破棄されるべきオブジェクト(Activity、Fragment、View)がメモリに残っているかどうかを自動的に監視します。リークを検出すると、LeakCanaryはヒープダンプを生成し、オブジェクトを保持する参照チェーンを分析します。
このライブラリはAndroidコミュニティの標準となっています。GitHubによると、プロジェクトは28,000以上のスターを獲得し、Google、Uber、Airbnb、Facebookのアプリケーションで使用されています。LeakCanaryには2つのメインバージョンがあります:従来の1.x(手動設定)と最新の2.x(ContentProviderによる自動統合)です。バージョン2.xではApplicationクラスの変更は不要で、依存関係の追加だけで完全に機能します。
LeakCanaryの主な役割は、オブジェクトのライフサイクルが終了した後もメモリに存在し続ける状況を検出することです。これは、静的フィールド、シングルトン、未登録のコールバック、匿名クラス、外部オブジェクトをキャプチャするクロージャを介したリークに典型的です。
モバイルデバイスのRAM容量が限られているため、Androidでのメモリリークはデスクトップよりも深刻です。画面遷移ごとに 5〜10 MB のリークでも、アプリを30〜40分使用するとOutOfMemoryErrorを引き起こす可能性があります。LeakCanaryは、本番環境でのクラッシュを待たずに、開発段階でそのような問題を検出します。
LeakCanary は、弱参照(WeakReference)と強制ガベージコレクションを組み合わせて使用します。ActivityまたはFragmentがonDestroyを呼び出すと、LeakCanaryはそのオブジェクトへのWeakReferenceを作成し、短い遅延(デフォルトでは5秒)後にGCをトリガーします。GC後もオブジェクトがWeakReferenceを介してアクセス可能な場合、強い参照によって保持されているため、リークとして記録されます。
リークを検出した後、LeakCanaryは ヒープダンプ(メモリダンプ)を作成します — HPROF形式のアプリケーションメモリの完全なスナップショットです。次に、内蔵アナライザー(バージョン2.xの場合はShark)がGC Rootsからリークオブジェクトまでの到達可能性グラフを構築し、最短経路 — オブジェクトをメモリに保持する参照チェーンを見つけます。
// LeakCanaryの簡略化された検出ロジック
class ObjectWatcher {
private val watchedReferences = CopyOnWriteArrayList<KeyedWeakReference>()
fun watch(watchedObject: Any, description: String) {
val reference = KeyedWeakReference(watchedObject, description)
watchedReferences.add(reference)
BackgroundHandler.postDelayed({
checkForLeaks()
}, 5000)
}
private fun checkForLeaks() {
GcTrigger.runGc() // 強制GC
for (ref in watchedReferences) {
if (ref.get() != null) {
onLeakFound(ref) // オブジェクトがGCを生き延びました — リークです
}
}
}
}
重要なポイントは GcTrigger.runGc() の強制呼び出しです。これがないと、実際にリークしたオブジェクトとGCがまだ収集していないオブジェクトを区別できません。LeakCanaryはこれを最大3回実行します:3回のGCサイクル後もオブジェクトがメモリに残っている場合、リークが確定します。
Shark は、LeakCanary 2.xに組み込まれたヒープダンプアナライザーで、Kotlinで書かれています。以前のHAHAアナライザーとは異なり、SharkはHPROFファイル全体をメモリにロードせず、最小限の割り当てでオブジェクトグラフをトラバースします。これにより、分析中のRAM消費量が50 MBから2〜5 MBに削減され、分析時間が30秒から1〜3秒に短縮されます。
最新のAndroidプロジェクトに LeakCanary 2.x をインストールするには、build.gradleに1行追加するだけです。ライブラリはContentProviderを使用して自動的に初期化されるため、Applicationクラスを変更したりMainActivityにコードを追加したりする必要はありません。リリースAPKに余分なコードが含まれないよう、依存関係はdebugビルドにのみ追加されます。
// build.gradle(app/module)
dependencies {
// debugImplementation — デバッグビルド専用ライブラリ
debugImplementation "com.squareup.leakcanary:leakcanary-android:2.14"
}
依存関係を追加してプロジェクトを再ビルドすると、LeakCanary が自動的にアプリに表示されます。初回起動時に、ライブラリはアクティベーションを確認するシステム通知を表示します。検出されたすべてのリークは通知として表示され、通知をタップすると詳細なレポート(LeakTrace)を含む画面が開きます。
カスタマイズするには、独自の AppWatcherInstaller を作成し、パラメーターをオーバーライドできます:GCタイムアウト、追跡するオブジェクトタイプのリスト、ディスクへのヒープダンプ保存の有効化など。ただし、90%のプロジェクトではデフォルト設定で十分です。
バージョン2.12以降、LeakCanary はViewModel、コルーチンスコープ、ComposeのStateオブジェクトの自動追跡をサポートしています。追加の依存関係は不要です — ライブラリはプロジェクトで使用されているJetpackコンポーネントを自動的に検出し、対応する検出器をアクティブにします。
LeakCanary のレポート(LeakTrace)は、GC Rootからリークオブジェクトまでの複数行の参照チェーンです。各行は、強い参照が通過するクラスとフィールドを示します。開発者はチェーンを下から上に読む必要があります:一番下の行がリークオブジェクト、一番上の行がエントリポイント(GC Root)です。
典型的なLeakTraceは次のようになります:GC Root → Applicationの静的フィールド → シングルトン → コールバック → Activity。開発者がこのようなチェーンを目にした場合、問題は明らかです。シングルトン がActivityへの参照をキャプチャしたコールバックを保持しています。解決策は、シングルトン内の強い参照を弱い参照に置き換えることです。
┬
├─ android.app.Application
│ Leaking: NO (Application — singleton)
│ ↓ Application.leakedActivities
├─ java.util.ArrayList
│ Leaking: NO (ArrayList — normal)
│ ↓ ArrayList[0]
├─ com.example.MainActivity
│ Leaking: YES (Activity destroyed but still in memory)
│ ↓ MainActivity.mCallback
├─ com.example.CallbackWrapper
│ Leaking: UNKNOWN
│ ↓ CallbackWrapper.mListener
│ ~~~~~~~~~~
├─ com.example.MyCallback (anonymous)
│ Leaking: UNKNOWN
│ ↓ MyCallback.this$0
├─ com.example.MainActivity
│ Leaking: YES (MainActivity is the leak)
╰
この例では、LeakCanary はMainActivityが次のチェーンを介して保持されていることを示しています:Application → ArrayList → MainActivity → CallbackWrapper → MyCallback → 再びMainActivity。this$0矢印は、匿名クラスMyCallbackがActivityへの外部参照をキャプチャしたことを示しています。解決策は、コールバックを弱い参照にするか、onDestroyでキャンセルすることです。
LeakCanaryはチェーンの各要素について リークステータス も表示します:NO(リークなし — ルート要素)、YES(オブジェクトは破棄されるべき)、UNKNOWN(ステータスを判別できません)。UNKNOWNステータスは問題を意味しません — LeakCanaryが明確に分類できない中間オブジェクトです。
バージョン1.xから2.xへの移行は根本的なものでした:開発者はライブラリをゼロから書き直し、古いHAHAアナライザーをKotlinで書かれた独自のエンジン Shark に置き換えました。Sharkは桁違いに高速で、分析に必要なメモリが少なく、リークの根本原因をより正確に特定します。
| パラメーター | LeakCanary 1.x | LeakCanary 2.x |
|---|---|---|
| アナライザー言語 | Java(HAHA — Android SDKのフォーク) | Kotlin(Shark — 独自エンジン) |
| セットアップ | Applicationでの手動AppWatcher設定 | ContentProviderによる自動設定 |
| 速度 | ヒープダンプ分析に10〜30秒 | ヒープダンプ分析に1〜5秒 |
| パフォーマンス | 分析中に10〜50 MBのRAMを消費 | 分析中に2〜10 MBのRAMを消費 |
Shark の主な利点は、ヒープダンプ全体をメモリにロードせず、最小限の割り当てで参照グラフをトラバースすることです。これにより、LeakCanary 2.xは、分析中のOutOfMemoryErrorのリスクなしに、RAMの少ないデバイスでも使用できます。
バージョン2.xでは、後でAndroid Studio Memory Profilerで分析するために ヒープダンプをエクスポート する機能も導入されました。これを行うには、AppWatcher設定でdumpHeapWhenLeakFoundを有効にします。
LeakCanary は、Androidに共通するいくつかの種類のリークを効果的に検出します。最も頻繁に見られるのは、Activityへの静的参照によるリークです — 開発者がシングルトン内にActivityコンテキストへの参照を保持し、Activityがライフサイクル終了後にGCによって収集されなくなります。
2番目に多いカテゴリは、未登録のリスナー を介したリークです。onStartでregisterListenerが呼び出されたが、onStop/onDestroyでunregisterListenerが呼び出されなかった場合、アクティビティが破棄された後もリスナーオブジェクトがシステムによって保持されます。LeakCanaryは、どのリスナーがどのシステムサービスで生存しているかを明確に示します。
// 典型的なリーク:シングルトンコールバックにキャプチャされたActivity
object AnalyticsManager {
private var callback: ((String) -> Unit)? = null
fun register(callback: (String) -> Unit) {
this.callback = callback // コールバックへの強い参照
}
fun unregister() {
callback = null // onDestroyで呼び出すのを忘れないでください!
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
AnalyticsManager.register { event ->
logEvent(event) // ラムダがthisをキャプチャしています
}
// onDestroyでunregisterを呼び出さない場合 → Activityリーク
}
}
3番目のカテゴリは、BackStack内のFragment を介したリークです。FragmentTransaction.addToBackStack()が戻る際にFragmentを削除せずに呼び出された場合、古いFragmentインスタンスがメモリに残ります。LeakCanaryは、開発の早期段階でこのような隠れたリークを検出するのに役立ちます。
検出されたリークごとに、LeakCanaryは説明と修正の推奨事項を提供します。バージョン2.14では Android Lint との統合が追加されました — CIでリークが検出されると、ライブラリが自動的にイシュートラッカーにタスクを作成できます。
よくある質問
はい、必須です。 LeakCanaryはbuild.gradleでdebugImplementationを介して追加されるため、リリースビルドから自動的に除外されます。implementationを使用すると、ライブラリがリリースAPKに含まれ、エンドユーザーにリークを表示することになります — これは許容できません。
パフォーマンスへの影響は最小限です。LeakCanary はコンポーネントのonDestroy後にのみアクティブになり、UIのレンダリングやタッチ処理に干渉しません。唯一のコストは、短い強制GCの一時停止(約100ミリ秒)と、リーク発生時のヒープダンプの書き込み(数分の1秒)です。
LeakCanaryは自動的にヒープダンプをHPROF形式でアプリのフォルダに保存します。ファイルはAndroid Studioからエクスポートできます:Device File Explorer → data/data/com.example/files/leakcanary/。表示するには、Capture → Open Heap DumpからMemory Profilerでファイルを開きます。
はい、バージョン2.12以降、LeakCanaryは Jetpack Compose を完全にサポートしています。ライブラリはCompositionコンテキストとStateオブジェクトを追跡し、Composable関数のリークを自動的に検出します。個別の設定は不要です — そのまま動作します。
誤検出の可能性はありますが、まれです。LeakCanaryはリークを宣言する前に3回のGC呼び出しを使用するため、ほとんどの誤検出を排除します。検出が誤検出だと思われる場合は、設定で特定のクラスに対して IgnoredReference を作成してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。