LeakCanary は、Android アプリのメモリリーク検出に広く使われるライブラリである。
導入は依存関係の追加だけで足りる。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
}デバッグビルドを端末へ入れると、アプリと並んでカナリアのランチャが現れ、リークを検知するとトーストや通知で知らせる。

アプリケーション側に初期化コードを書かなくても動作に介入できる理由が、本記事の主題である。
メモリリークと監視の起点
メモリリークとは、本来回収可能になるべきオブジェクトが、参照チェーンによって GC 対象から外れたまま残ることである。View ベースの Android アプリでは、画面単位のライフサイクルを持つ Activity が代表的な監視対象になる。
リーク検出には、コンポーネントの破棄を知る手段が要る。Activity は公開 API としてライフサイクル監視を提供する。したがってソース上には Application.registerActivityLifecycleCallbacks のような登録箇所が見えるはずである。
LeakCanary のリポジトリ を ripgrep で辿ると、その期待どおりの経路が現れる。

ActivityWatcher と FragmentAndViewModelWatcher が中心的な監視実装である。呼び出し関係を追うと、おおよそ次の流れに整理できる。

監視そのものは AppWatcher と各 Watcher が担う。問題は、それらのインストールがどの時点で、誰の手によって走るのかである。
自動インストールの古典形:ContentProvider
Android では、プロセス起動時に Application の onCreate より前に ContentProvider の onCreate が呼ばれる。ライブラリ作者は、自前の ContentProvider をライブラリの AndroidManifest.xml に宣言し、マージマニフェスト経由でホストアプリへ持ち込むことで、アプリ側の一行も借りずに初期化できる。
LeakCanary の標準アーティファクト leakcanary-android が依存する object-watcher 側では、次のようなインストーラがマニフェストに載る(現行ソースでは MainProcessAppWatcherInstaller)。
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />実装は CRUD を持たない「初期化専用」Provider である。onCreate で Application を取り出し、AppWatcher.manualInstall を呼ぶ。
internal class MainProcessAppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
// query / insert / update / delete は未使用
}manualInstall の先で既定の Watcher 群(Activity や Fragment など)が登録され、リーク監視が始まる。依存を debugImplementation しただけでカナリアが動く、という体験の正体は、このマニフェストマージと ContentProvider 起動順序である。
ContentProvider による自動初期化は Firebase や古い WorkManager などでも使われてきた一方、ライブラリごとに Provider が増えると起動コストが積み上がる、という問題が知られている。
AndroidX App Startup と LeakCanary の対応
Google は AndroidX App Startup を導入した。各ライブラリが個別の ContentProvider を持つ代わりに、共通の InitializationProvider 一つを共有し、その下の <meta-data> で Initializer 実装を列挙する。起動時に App Startup が依存関係を解決しながら Initializer.create を呼ぶ。
LeakCanary は 2.8 系から、この経路をオプションとして用意している。通常の leakcanary-android を、leakcanary-android-startup に差し替える。
dependencies {
// debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
debugImplementation("com.squareup.leakcanary:leakcanary-android-startup:2.14")
}startup 用モジュールは、自前の ContentProvider ではなく App Startup のマニフェストエントリをマージする。object-watcher 側の例は次のとおりである。
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="leakcanary.AppWatcherStartupInitializer"
android:value="androidx.startup" />
</provider>対応する Initializer は、やはり AppWatcher.manualInstall を呼ぶ薄いラッパである。
class AppWatcherStartupInitializer : Initializer<AppWatcherStartupInitializer> {
override fun create(context: Context) = apply {
val application = context.applicationContext as Application
AppWatcher.manualInstall(application)
}
override fun dependencies() = emptyList<Class<out Initializer<*>>>()
}Changelog の整理どおり、leakcanary-android は自動インストール用コードを乗せる薄い入口であり、コアは leakcanary-android-core にある。手動で AppWatcher.manualInstall する場合は core を直接依存し、自動インストールを避ける構成も取れる。object-watcher と plumber にも、それぞれ *-core / 既定(ContentProvider)/ *-startup の対応関係がある。
| 経路 | 依存の目安 | 起動フック |
|---|---|---|
| 既定の自動インストール | leakcanary-android | ライブラリ専用 ContentProvider |
| App Startup | leakcanary-android-startup | 共有 InitializationProvider + Initializer |
| 手動 | leakcanary-android-core など | アプリの Application.onCreate などで manualInstall |
アプリが既に App Startup で他ライブラリを束ねている場合、LeakCanary も startup アーティファクトに揃えると、ContentProvider の個数を増やさずに自動インストールを維持できる。初期化のタイミングや順序を厳密に制御したい場合は、自動インストールを切って manualInstall に寄せる。
調査の進め方についてのメモ
今回の経路は、公開 API(Activity ライフサイクル)とマニフェスト/起動順という二つの「入口」からソースを辿る形で見えた。検索ツール(grep / ripgrep)で名前の手がかりを拾い、IDE で参照を追う、という手順は、AI 支援の有無にかかわらず有効である。
ライブラリの「依存を足すだけで動く」体験の多くは、ランタイムの魔法ではなく、マニフェストマージとコンポーネント起動順序というプラットフォームの規約に乗っている。ContentProvider から App Startup への移行も、同じ規約の上で初期化の集約方法を変えたものとして読める。