WebView menjalankan kode native di beberapa proses untuk merender konten web
di aplikasi Android Anda. Membiarkan instance WebView tidak dikelola dapat menyebabkan kebocoran
memori, error kehabisan memori (OOM), dan penurunan performa aplikasi.
Dokumen ini menjelaskan model memori multiproses WebView, menjelaskan cara
mengelola siklus prosesnya dengan benar untuk mencegah kebocoran, dan memberikan alur kerja
praktis untuk mendiagnosis masalah memori.
Memahami arsitektur memori WebView
Untuk mengelola memori WebView secara efektif, pahami cara Android mengalokasikan
resource untuk konten web:
Eksekusi multiproses: Di Android 8.0 (level API 26) dan yang lebih tinggi,
WebViewmemisahkan konten web dari fungsi inti aplikasi Anda di beberapa proses (pada perangkat dengan RAM rendah, proses ini mungkin akan kembali ke satu proses):- Proses host (Browser): Proses aplikasi utama tempat kode
Activitydan Java atau Kotlin Anda berjalan. - Proses Perender Terisolasi: Proses dengan sandbox terpisah
(
SandboxedProcessService) yang mengurai HTML dan CSS, mengeksekusi JavaScript, dan merender halaman web.
- Proses host (Browser): Proses aplikasi utama tempat kode
Jejak memori native: Sebagian besar memori
WebView, termasuk grafis yang dirender, hierarki DOM, dan memori runtime JavaScript, dialokasikan dalam memori native, bukan di heap Java. Heap dump Java (.hprof) hanya menampilkan objek wrapper Java ringan dan tidak menangkap memori sebenarnya yang digunakan oleh konten web.Dampak sistem dari memori native: Tidak seperti alokasi heap Java, yang dibatasi oleh batas
maxHeapaplikasi dan gagal dengan cepat denganOutOfMemoryError, memori native dapat bertambah secara diam-diam hingga gigabyte. Saat memori native yang belum dirilis mengisi RAM fisik dan ruang swap (zRAM), Low Memory Killer (LMK) Android mulai menghentikan proses latar belakang untuk mengklaim kembali memori. Hal ini menurunkan kualitas multitasking perangkat secara keseluruhan sebelum akhirnya menghentikan aplikasi latar depan.
Mengelola siklus proses WebView
Pengelolaan siklus proses yang tepat sangat penting untuk mencegah kebocoran memori. Kesalahan umum
adalah mengasumsikan bahwa menghapus WebView dari tata letak atau membiarkan
Activity selesai akan otomatis mengosongkan memorinya.
Untuk memastikan pembersihan lengkap referensi konteks Java dan resource render native, Anda harus secara eksplisit mengatur urutan penghapusan dalam siklus proses komponen host (seperti onDestroy()), menghentikan eksekusi halaman aktif, melepaskan tampilan dari penampungnya, dan merilis binding native.
Membersihkan instance WebView
Untuk memastikan penonaktifan yang bersih dan melepaskan resource saat Activity atau
Fragment Anda dihancurkan, lakukan hal berikut:
- Hapus
WebViewdari penampung induknya (ViewGroup). - Menghentikan pemuatan aktif dan menghapus histori navigasi.
- Panggil
destroy(). - Hapus referensi ke
null.
Contoh berikut menunjukkan cara membersihkan WebView dengan benar:
Kotlin
override fun onDestroy() { myWebView?.let { // Remove the WebView from its parent ViewGroup. (it.parent as? ViewGroup)?.removeView(it) // Stop active loading and clear history. it.stopLoading() it.clearHistory() // Destroy the instance. it.destroy() } myWebView = null super.onDestroy() }
Java
@Override
protected void onDestroy() {
if (myWebView != null) {
// Remove the WebView from its parent ViewGroup.
if (myWebView.getParent() instanceof ViewGroup) {
((ViewGroup) myWebView.getParent()).removeView(myWebView);
}
// Stop active loading and clear history.
myWebView.stopLoading();
myWebView.clearHistory();
// Destroy the instance.
myWebView.destroy();
}
myWebView = null;
super.onDestroy();
}
Memahami memori pasca-penghancuran
Saat Anda memanggil destroy(), sistem akan merilis konteks Activity, membersihkan
hierarki tampilan, dan menghentikan pekerjaan latar belakang web. Namun, Anda mungkin mengamati bahwa
memori fisik proses (Resident Set Size) tidak langsung turun ke
baseline pra-WebView.
Hal ini normal terjadi. Cache runtime native, pustaka bersama, dan halaman memori yang dialokasikan tetap berada dalam proses hingga sistem operasi mengklaimnya kembali atau proses berakhir. Tujuan utama destroy() adalah mencegah
kebocoran memori Activity kumulatif saat pengguna membuka dan menutup layar yang didukung
web.
Metrik proses debug utama
Saat menganalisis pemakaian memori WebView, fokuslah pada metrik berikut:
Resident Set Size (RSS): Total RAM fisik yang dipetakan ke dalam proses, termasuk kode dan library bersama (diberi label sebagai Total di Android Studio Profiler).
Anonymous RSS (RssAnon): Alokasi memori yang dialokasikan langsung oleh proses yang tidak didukung oleh file di disk (seperti heap native dan alokasi runtime JavaScript). Ini mewakili biaya memori utama konten web Anda (diberi label sebagai Allocated di Android Studio Profiler).
Jejak Memori Pribadi (PMF): Jumlah RSS Anonim dan swap (zRAM). PMF mencerminkan beban memori aktual yang tidak dapat dikeluarkan yang dikenakan aplikasi Anda pada sistem.
PMF Browser versus PMF Perender: Memori yang digunakan oleh proses utama aplikasi Anda versus memori yang digunakan oleh proses perender yang terisolasi. Konten web yang berat menyebabkan lonjakan terutama dalam proses perender.
Jumlah Objek Live (
WebViews,Activities,Views): Jumlah instance UI, Context, danWebViewaktif yang disimpan dalam memori. Melacak hal ini akan mengidentifikasi apakah pertumbuhan memori disebabkan oleh referensi Java yang dipertahankan atau alokasi khusus native.Private Other dan Native Heap: Di
dumpsys meminfo, alokasi C/C++ native dan pemetaan memori kustom (sepertiPartitionAllocChromium atau heap runtime JavaScript tersemat) muncul di bagian Native Heap dan Private Other, bukan Java Heap.
Untuk mengetahui informasi selengkapnya tentang penghitung memori proses dan kategorinya, lihat Glosarium memori proses.
Alur kerja diagnostik praktis
Karena WebView beroperasi di beberapa proses dan mengalokasikan memori native, gunakan alat dan teknik berikut untuk memeriksa jejaknya:
Alat pembuatan profil dan diagnostik
Untuk memeriksa alokasi memori dan mendiagnosis kebocoran, gunakan alat berikut:
Memory Profiler Android Studio: Gunakan Memory Profiler untuk memvisualisasikan alokasi native, melacak kategori memori dari waktu ke waktu, dan mendeteksi kebocoran
Activitydi seluruh transisi layar.Pelacakan memori dengan Perfetto: Gunakan Perfetto untuk mencatat penghitung memori tingkat sistem (seperti RSS dan RSS Anonim) guna mengamati pertumbuhan memori secara keseluruhan. Perhatikan bahwa alokasi mesin native
WebViewtidak menghasilkan stack panggilan di alat pembuatan profil heap Perfetto. Gunakan Chrome DevTools untuk memeriksa cuplikan heap JavaScript dan alokasi DOM di dalam konten web.
Memeriksa jumlah objek aktif
Untuk menentukan apakah pertumbuhan memori disebabkan oleh objek framework Java yang dipertahankan (seperti komponen UI) atau alokasi native, periksa bagian Objects dari dumpsys meminfo:
adb shell dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"Output menampilkan jumlah objek aktif:
Objects
Views: 142 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 12 AssetManagers: 0
Local Binders: 32 Proxy Binders: 45
Parcel memory: 15 Parcel count: 30
Death Recipients: 2 WebViews: 1
Bagian ini menampilkan jumlah objek framework aktif, handle IPC, dan
alokasi Parcel. Untuk diagnostik WebView, fokus utamanya adalah Activities
dan WebViews.
Lakukan interaksi pengguna target (seperti membuka dan menutup layar web) berulang kali dan bandingkan jumlahnya:
Kebocoran instance: Jika
WebViewsatauActivitiesbertambah pada setiap navigasi dan tidak kembali ke dasar pengukuran, aplikasi Anda mengalami kebocoran instance JavaWebViewatau hostActivity(misalnya, karena tidak adanyaViewGroup.removeView()atau referensi pemroses yang dipertahankan). KarenaActivityyang bocor menyematkan seluruh hierarki tampilan dan resource gambar yang didekode dalam memori, kunjungan berulang akan dengan cepat menghabiskan heap Java dan menyebabkan errorOutOfMemoryError.Kebocoran Native atau DOM: Jika
WebViewsdanActivitiestetap konstan sementara total RSS proses dan Private Other terus meningkat, kebocoran berasal dari resource native, elemen DOM, atau binding mesin JavaScript yang belum dirilis. Karena alokasi ini berada di memori native dan melewati pembersih sampah memori ART, alokasi tersebut tetap tidak terlihat oleh alat deteksi kebocoran Java standar dan terus terakumulasi hingga sistem operasi menghentikan aplikasi.
Membuat profil proses perender yang diisolasi menggunakan CLI
Menjalankan dumpsys meminfo dengan nama paket aplikasi Anda hanya akan menghasilkan memori untuk
proses host utama. Untuk memeriksa proses perender terisolasi tempat halaman web
dirender:
Temukan ID proses (PID) layanan perender terisolasi:
adb shell dumpsys activity processes <var>PACKAGE_NAME</var> | grep "Isolated.*SandboxedProcessService"Output akan menampilkan data proses yang diisolasi dan PID-nya RENDERER_PID (misalnya,
22155):Isolated #5: ProcessRecord{... 22155:com.google.android.webview.debug:sandboxed_process0:...}Periksa pengelompokan memori proses perender menggunakan PID-nya:
adb shell dumpsys meminfo <var>RENDERER_PID</var>Periksa proses aplikasi host untuk mengevaluasi jejak sisi browser:
adb shell dumpsys meminfo <var>PACKAGE_NAME</var>
Memeriksa peta dan alokasi memori
Untuk melihat subsistem native atau pengalokasi mana yang menggunakan memori anonim, periksa peta memori proses:
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"Tabel berikut mencantumkan tag memori anonim umum dan relevansinya dengan pertumbuhan memori:
| Tag Memori | Subsistem | Relevansi dengan konten aplikasi dan web | Penyebab umum peningkatan memori? |
|---|---|---|---|
[anon:partition_alloc] |
Chromium PartitionAlloc | Alokasi untuk hierarki DOM, buffering rendering, heap JavaScript V8, dan eksekusi WebAssembly di WebView. |
Ya (Tinggi): Memuat halaman web yang berat, DOM yang kaya media, atau gagal memanggil destroy() pada instance WebView yang dihapus akan langsung meningkatkan tag ini. |
[anon:scudo...] atau [anon:libc_malloc] |
Alokator heap native Android (Scudo / jemalloc) | Alokasi native C/C++ umum yang digunakan oleh library NDK, jembatan JNI, dan pipeline grafis native. | Ya (Sedang hingga Tinggi): Pertumbuhan terjadi saat wrapper JNI native atau dependensi C++ pihak ketiga mempertahankan alokasi yang belum dirilis di seluruh navigasi. |
[anon:...] (misalnya, [anon:quickjs_heap...]) |
Pembuatan skrip kustom atau runtime native | Mesin JavaScript tersemat, runtime WebAssembly kustom, atau kumpulan buffer native kustom. | Ya (Bergantung pada konteks): Umum terjadi di aplikasi hybrid yang menjalankan mesin pembuatan skrip bersama tampilan native dan gagal membersihkan binding runtime. |
Batasan API memori dalam aplikasi
API memori dalam aplikasi (seperti Debug.getMemoryInfo atau
ActivityManager.getProcessMemoryInfo) hanya mengukur proses panggilan.
Dalam mode multiproses, API ini tidak dapat menangkap memori yang digunakan oleh
proses perender yang terisolasi. Untuk penilaian total memori yang akurat, gunakan
alat sistem seperti dumpsys meminfo, Perfetto, atau Android Studio Profiler.
Melakukan triase memori tinggi di aplikasi hybrid
Saat mendiagnosis pertumbuhan memori yang tidak dapat dijelaskan selama interaksi WebView
berulang (seperti membuka link web atau menavigasi feed yang didukung web), gunakan
alur kerja triase berikut untuk mengisolasi apakah kebocoran berasal dari
lapisan Java atau mesin native:
Mengisolasi jenis kebocoran (Java versus native): Jalankan
dumpsys meminfo -a <var>PACKAGE_NAME</var> | grep -A 10 "Objects"sebelum dan sesudah transisi pengguna yang berulang (seperti membuka dan menutup artikel web atau menggeser feed).- Pengamatan: Jika jumlah
ActivitiesdanWebViewstetap stabil (misalnya, 1–2 instance aktif), aplikasi tidak mengalami kebocoran konteksActivityatau instanceWebViewJava.
- Pengamatan: Jika jumlah
Mengukur delta memori di seluruh interaksi (Pelacakan deret waktu): Ambil snapshot
dumpsys meminfodi beberapa interaksi pengguna untuk menghitung rasio alokasi per transisi:- Pengamatan: Heap Java tetap dibatasi dan sehat (melonjak selama penggunaan
dan menurun setelah pembersihan sampah memori), tetapi Private Other dan
Native Heap terus meningkat beberapa megabyte per transisi. Hal ini
membuktikan bahwa kebocoran sepenuhnya terjadi di memori native di luar runtime ART.
Heap dump Java standar (
.hprof) tidak akan menampilkan masalah apa pun.
- Pengamatan: Heap Java tetap dibatasi dan sehat (melonjak selama penggunaan
dan menurun setelah pembersihan sampah memori), tetapi Private Other dan
Native Heap terus meningkat beberapa megabyte per transisi. Hal ini
membuktikan bahwa kebocoran sepenuhnya terjadi di memori native di luar runtime ART.
Heap dump Java standar (
Periksa Peta Memori Anonim: Periksa peta memori proses menggunakan ADB (lihat Memeriksa peta dan alokasi memori):
adb shell "cat /proc/$(pidof <var>PACKAGE_NAME</var>)/maps" | grep "anon:"- Pengamatan: Pertumbuhan memori terkonsentrasi di
heap mesin pembuatan skrip tersemat atau
[anon:partition_alloc], disertai dengan peningkatan lambat dalam referensi global JNI. Hal ini menunjukkan bahwa meskipun tampilan Java diganti, objek halaman native atau binding JavaScript yang mendasarinya tidak dirilis.
- Pengamatan: Pertumbuhan memori terkonsentrasi di
heap mesin pembuatan skrip tersemat atau
Perbaikan:
- Pastikan setiap
WebViewyang didaur ulang atau dihapus secara eksplisit menghentikan skrip aktif (stopLoading()), menghapus histori, dan memanggildestroy(). - Hapus callback jembatan JavaScript kustom atau referensi global JNI yang terkait dengan tampilan yang ditutup.
- Pastikan
Private Otherdan RSS proses stabil setelah transisi navigasi.
- Pastikan setiap
Referensi lainnya
Untuk mempelajari lebih lanjut cara men-debug dan membuat profil memori serta performa WebView,
lihat referensi berikut: