WebView は、Android アプリ内でウェブ コンテンツを表示できる強力なコンポーネントです。ただし、WebView は基本的にフル機能のブラウザ エンジン(Chromium)であるため、メモリ使用量が大きく、マルチプロセス アーキテクチャが複雑です。
技術的背景: マルチプロセス アーキテクチャ
最新の Android 搭載デバイスでは、WebView はマルチプロセス モデルを使用してセキュリティと安定性を向上させています。アプリで WebView を使用すると、メモリはさまざまなプロセスに分散されます。
- ブラウザ プロセス(アプリ プロセス): これはアプリのメイン
プロセスです。Java の
WebViewオブジェクトと Chromium エンジンの「ブラウザ」部分が含まれています。このプロセスは、UI、ネットワーク リクエスト、GPU レンダリング(Android HWUI レンダリング パイプラインと直接統合)を管理します。 Chrome とは異なり、WebView には個別の GPU プロセスはありません。 - レンダラ プロセス: このプロセスは、HTML の解析、 JavaScript の実行、レイアウトを担当します。セキュリティ上の理由から、システムの他の部分から分離されています。現在、アプリはすべての WebView に対して 1 つのレンダラ プロセスを取得します(まれに特別なケースを除く)。Chrome では、多くの場合、サイトごとに個別のレンダラ プロセスが使用されます。

メモリに関する重要性
dumpsys meminfo <your_package> を使用すると、
**ブラウザ プロセス** (アプリ プロセス)で使用されるメモリのみが表示されます。レンダラ プロセスで使用されるメモリは個別に計上されます。
ブラウザ プロセス内では、WebView メモリは次のように分散されます。
- Java ヒープ:
WebViewJava ラッパーと関連オブジェクトが含まれます。 - ネイティブ ヒープ: Chromium ブラウザ エンジンの内部データ
構造、キャッシュ、状態が含まれます。PartitionAlloc の使用により、WebView のネイティブ割り当ての一部が
dumpsys meminfoの [Native Heap] にカウントされず、代わりに [Other] または [Unknown] に表示される場合があります。 - 共有メモリ: グラフィック バッファやその他のデータの共有に使用されます。
dumpsys meminfoで明確に分類されない場合があります。
トラブルシューティング ツール
Chrome DevTools
WebView の内部(レンダラ プロセス)のメモリを分析する最も強力なツールは、Chrome DevTools です。
アプリで WebView のデバッグを有効にします。
// NOTE: In production, this should be gated behind a developer setting // or only enabled for debuggable builds to prevent reverse engineering. WebView.setWebContentsDebuggingEnabled(true);デバイスを USB 経由で接続します。
ホストマシンで Chrome を開き、
chrome://inspect/#devicesに移動します。アプリを見つけて [inspect] をクリックします。
DevTools ウィンドウで、[Memory] タブに移動して、ヒープ スナップショットを取得するか、JavaScript ヒープの割り当てタイムラインを記録します。
dumpsys meminfo
adb shell dumpsys meminfo --all <package> を使用して、メモリの内訳を確認します。
出力で WebView カテゴリとオブジェクト数を確認します。
レンダラのプロファイリング
レンダラは別のプロセスで実行されるため、アプリをプロファイリングするだけではネイティブ ヒープをプロファイリングできません。レンダラ プロセスの PID を特定する必要があります。
複数の WebView がアクティブな場合に正しいレンダラ PID を特定するには:
を使用する
dumpsys activity:adb shell dumpsys activity processes <your_package_name>mConnectionsセクションを探します。アプリをSandboxedProcessServiceにリンクするConnectionRecordが表示されます。そのプロセスの PID がレンダラです。例:mConnections: - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}プロセス名を確認する: レンダラ プロセスは通常、
com.google.android.webview:sandboxed_processXなどの名前が付けられます。WebView を使用しているアプリが 1 つしかない場合は、1 つだけになります。
PID がわかったら、heapprofd を使用してプロファイリングできます。
WebView メモリに関するベスト プラクティス
明示的な破棄
アプリは、インスタンスの処理が完了したときに WebView.destroy() を呼び出す必要があります。
WebView は、インスタンスをガベージ コレクションしてすべてのリソースを自動的に解放できるようにしますが、100% のケースで保証することは困難です。自動ガベージ コレクションが機能する場合でも、大幅に遅延する可能性があり、アプリが想定よりもはるかに長くリソースを保持する可能性があります。
アプリが適切なタイミング(
Activity.onDestroy() など)で WebView.destroy() を呼び出す場合、WebView オブジェクト自体への参照を保持しても、
ネイティブ リソースがリークすることはありません。Activity フィールドの WebView オブジェクトへの参照を破棄後に null にする必要はありません。Activity 自体がガベージ コレクションされるときにクリーンアップされます。
演習: WebView メモリのハンズオン
演習 1: マルチプロセス フットプリントの観察
MemoryLab を起動し、アプリのメモリのベースライン測定を行います。
adb shell dumpsys meminfo com.android.memorylabベースラインの例(rango):
TOTAL PSS: 18915 KB[Launch WebView (Normal)] をタップします。
WebView で、[Allocate JS Memory (1000 DIVs)] を数回タップします。
アプリのメモリをもう一度確認します。
adb shell dumpsys meminfo com.android.memorylabアプリ プロセスのメモリがベースラインと比較して大幅に増加していないことを確認します。 これは、DOM 要素がレンダラ プロセス にあるためです。
レンダラ プロセスを見つけます。
adb shell ps -A | grep webview | grep sandboxed出力例:
u0_i9002 14227 1087 1632732 135880 do_epoll_wait 0 S com.google.android.webview:sandboxed_process0レンダラ プロセスのメモリ(PID を使用)を確認します。
adb shell dumpsys meminfo 14227レンダラ プロセスの TOTAL PSS が高いことを確認します。サンプル実行では、数回の割り当て後に ~55 MB に増加しました。JavaScript の割り当て(V8 エンジンによって処理される)は通常、Dalvik ヒープではなく、
dumpsys meminfoの [Private Other] または [Unknown](mmap)セクションに寄与します。
演習 2: Java 側の WebView リーク
よくある間違いは、静的フィールドまたはリークする存続期間の長いオブジェクトで WebView インスタンスを保持することです。WebView オブジェクトは、ネイティブ リソースとレンダラ プロセス全体を保持する重い「アンカー」であるため、リークするとコストが非常に高くなります。

- MemoryLab で、[Launch WebView (Java Leak)] をタップします。
- ページが読み込まれると、アクティビティは自動的に閉じます(繰り返しのナビゲーションとリークの蓄積をシミュレートします)。
- ボタンを 4 回タップします。
アプリ内の
WebViewインスタンスの数を確認します。adb shell dumpsys meminfo com.android.memorylab一番下の [Objects] セクションを探します。
WebViewsの数が 4 に増えています。rango での出力例(4 つのインスタンスがリーク):
Objects Views: 51 ViewRootImpl: 5 AppContexts: 14 Activities: 5 Assets: 38 AssetManagers: 0 Local Binders: 55 Proxy Binders: 77 Parcel memory: 41 Parcel count: 68 Death Recipients: 3 WebViews: 4ヒープダンプをキャプチャし、AHAT を使用してリークを見つけます。パスに
ahatがない場合は、Android ツリーからビルドできます。# Dump heap from device adb shell am dumpheap com.android.memorylab /data/local/tmp/heap.hprof adb pull /data/local/tmp/heap.hprof # Run ahat using the built JAR (found in out/host/linux-x86/framework/) java -jar out/host/linux-x86/framework/ahat.jar -p 8888 heap.hprofAHAT ウェブ インターフェース(
localhost:8888)で、上部のメニューの [allocations] リンク(または [sites])をクリックして、メモリ使用量全体を確認します。
android.webkit.WebViewクラスを検索します。[instance count] をクリックして、すべてのライブ インスタンスを表示します。リストに複数のインスタンスが表示されます。
リークした
WebViewインスタンスの 1 つをクリックします。[Sample Path from GC Root] セクションまでスクロールします。com.android.memorylab.WebViewActivityのsLeakedWebViewsリストによって保持されていることがわかります。