WebView dan memori

WebView adalah komponen canggih yang memungkinkan Anda menampilkan konten web dalam aplikasi Android Anda. Namun, karena pada dasarnya merupakan mesin browser berfitur lengkap (Chromium), ia memiliki jejak memori yang signifikan dan arsitektur multi-proses yang kompleks.

Latar belakang teknis: Arsitektur multi-proses

Di perangkat modern berbasis Android, WebView menggunakan model multi-proses untuk meningkatkan keamanan dan stabilitas. Saat aplikasi Anda menggunakan WebView, memori didistribusikan di berbagai proses:

  1. Proses browser (proses aplikasi): Ini adalah proses utama aplikasi Anda. Objek ini berisi objek Java WebView dan bagian "browser" dari mesin Chromium. Proses ini mengelola UI, permintaan jaringan, dan rendering GPU (terintegrasi langsung dengan pipeline rendering HWUI Android). Tidak seperti Chrome, WebView tidak memiliki proses GPU terpisah.
  2. Proses perender: Proses ini bertanggung jawab untuk mem-parsing HTML, menjalankan JavaScript, dan tata letak. Bagian ini terisolasi dari sistem lainnya untuk keamanan. Saat ini, aplikasi hanya mendapatkan satu proses perender untuk semua WebView (kecuali untuk beberapa kasus khusus yang jarang terjadi), tidak seperti Chrome yang sering menggunakan proses perender terpisah untuk situs yang berbeda.

Arsitektur WebView

Mengapa hal ini penting untuk memori

Saat menggunakan dumpsys meminfo <your_package>, Anda hanya akan melihat memori yang digunakan oleh proses browser (proses aplikasi Anda). Memori yang digunakan oleh proses renderer diperhitungkan secara terpisah.

Di dalam proses browser, memori WebView didistribusikan sebagai:

  • Heap Java: Berisi wrapper Java WebView dan objek terkait.
  • Heap native: Berisi struktur data, cache, dan status internal mesin browser Chromium. Perhatikan bahwa karena penggunaan PartitionAlloc, beberapa alokasi native WebView mungkin tidak dihitung dalam "Native Heap" di dumpsys meminfo dan mungkin muncul dalam "Other" atau "Unknown".
  • Memori bersama: Digunakan untuk berbagi buffer grafis dan data lainnya. Hal ini mungkin tidak dikategorikan dengan jelas oleh dumpsys meminfo.

Alat pemecahan masalah

Chrome DevTools

Alat paling canggih untuk menganalisis memori di dalam WebView (proses perender) adalah Chrome DevTools.

  1. Aktifkan proses debug WebView di aplikasi Anda:

    // NOTE: In production, this should be gated behind a developer setting
    // or only enabled for debuggable builds to prevent reverse engineering.
    WebView.setWebContentsDebuggingEnabled(true);
    
  2. Hubungkan perangkat Anda melalui USB.

  3. Buka Chrome di mesin host Anda dan buka chrome://inspect/#devices.

  4. Temukan aplikasi Anda, lalu klik periksa.

  5. Di jendela DevTools, buka tab Memory untuk mengambil cuplikan heap atau merekam linimasa alokasi untuk heap JavaScript.

dumpsys meminfo

Gunakan adb shell dumpsys meminfo --all <package> untuk melihat perincian memori. Cari kategori WebView dalam output dan jumlah objek.

Membuat profil perender

Karena Perender berjalan dalam proses terpisah, Anda tidak dapat membuat profil heap native-nya hanya dengan membuat profil aplikasi Anda. Anda harus mengidentifikasi PID proses perender secara khusus.

Untuk mengidentifikasi PID perender yang benar saat beberapa WebView aktif:

  1. Menggunakan dumpsys activity:

    adb shell dumpsys activity processes <your_package_name>
    

    Cari bagian mConnections. Anda akan melihat ConnectionRecord yang menautkan aplikasi Anda ke SandboxedProcessService. PID proses tersebut adalah perender Anda. Contoh:

    mConnections:
      - ConnectionRecord{... com.android.memorylab/org.chromium.content.app.SandboxedProcessService0:0 ...}
    
  2. Periksa Nama Proses: Proses perender biasanya diberi nama com.google.android.webview:sandboxed_processX atau yang serupa. Jika hanya ada satu aplikasi yang menggunakan WebView, kemungkinan hanya akan ada satu.

Setelah memiliki PID, Anda dapat membuat profilnya menggunakan heapprofd.

Praktik terbaik untuk memori WebView

Penghancuran eksplisit

Aplikasi diharapkan memanggil WebView.destroy() untuk menunjukkan kapan aplikasi selesai menggunakan instance.

Meskipun WebView mencoba memastikan bahwa instance dapat dikumpulkan sampah dan merilis semua resourcenya secara otomatis, hal ini sulit dijamin dalam 100% kasus. Meskipun pembersihan sampah memori otomatis berfungsi, prosesnya mungkin tertunda secara signifikan, sehingga aplikasi menahan resource lebih lama dari yang diharapkan.

Jika aplikasi memanggil WebView.destroy() pada waktu yang tepat (misalnya, di Activity.onDestroy()), mempertahankan referensi ke objek WebView itu sendiri tidak akan membocorkan resource native yang signifikan. Tidak ada kebutuhan yang ketat untuk mengosongkan referensi ke objek WebView di kolom Aktivitas setelah menghapusnya, karena akan dibersihkan saat Aktivitas itu sendiri dikumpulkan sampah.

Latihan: praktik langsung dengan memori WebView

Latihan 1: mengamati jejak multi-proses

  1. Luncurkan MemoryLab dan lakukan pengukuran dasar memori aplikasi Anda:

    adb shell dumpsys meminfo com.android.memorylab
    

    Dasar sampel (rango): TOTAL PSS: 18915 KB

  2. Ketuk Luncurkan WebView (Normal).

  3. Di WebView, ketuk Allocate JS Memory (1000 DIVs) beberapa kali.

  4. Periksa kembali memori aplikasi:

    adb shell dumpsys meminfo com.android.memorylab
    
  5. Amati bahwa memori dalam proses aplikasi Anda tidak meningkat secara signifikan dibandingkan dengan baseline. Hal ini karena elemen DOM berada di Proses Renderer.

  6. Temukan proses perender:

    adb shell ps -A | grep webview | grep sandboxed
    

    Contoh output:

    u0_i9002     14227  1087    1632732 135880 do_epoll_wait       0 S com.google.android.webview:sandboxed_process0
    
  7. Periksa memori proses perender (menggunakan PID-nya):

    adb shell dumpsys meminfo 14227
    
  8. Amati PSS TOTAL tinggi dari proses perender. Dalam contoh yang kami jalankan, ukuran memori melonjak menjadi ~55 MB setelah beberapa alokasi. Perhatikan bahwa alokasi JavaScript (yang ditangani oleh mesin V8) biasanya berkontribusi pada bagian Private Other atau Unknown (mmap) dumpsys meminfo, bukan Heap Dalvik.

Latihan 2: kebocoran WebView sisi Java

Kesalahan umum adalah menahan instance WebView di kolom statis atau di objek yang berjalan lama yang menyebabkan kebocoran. Karena objek WebView adalah "anchor" berat yang menyimpan resource native dan berpotensi seluruh proses perender, kebocoran objek ini sangat merugikan.

Dampak Kebocoran WebView

  1. Di MemoryLab, ketuk Launch WebView (Java Leak).
  2. Aktivitas akan otomatis ditutup setelah halaman dimuat (mensimulasikan navigasi berulang dan akumulasi kebocoran).
  3. Ketuk tombol 4 kali.
  4. Periksa jumlah instance WebView di aplikasi Anda:

    adb shell dumpsys meminfo com.android.memorylab
    

    Cari bagian Objek di bagian bawah. Anda akan melihat jumlah untuk WebViews telah bertambah menjadi 4.

    Contoh output (4 instance yang bocor) di rango:

     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
    
  5. Rekam heap dump dan gunakan AHAT untuk menemukan kebocoran. Jika Anda tidak memiliki ahat di jalur, Anda dapat mem-build-nya dari hierarki 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.hprof
    
  6. Di antarmuka web AHAT (localhost:8888), klik link allocations (atau sites) di menu atas untuk melihat penggunaan memori secara keseluruhan.

    Alokasi AHAT

  7. Telusuri class android.webkit.WebView. Klik jumlah instance untuk melihat semua instance aktif. Anda akan melihat beberapa instance dalam daftar.

    Instance WebView AHAT

  8. Klik salah satu instance WebView yang bocor. Scroll ke bawah ke bagian Sample Path from GC Root. Anda akan melihat bahwa objek tersebut ditahan oleh daftar sLeakedWebViews di com.android.memorylab.WebViewActivity.

    Jalur AHAT ke Root GC


← Native | ↑ Atas | Kode aplikasi →