Menangani penghentian WebView

WebView menjalankan konten webnya dalam proses perender terpisah. Terkadang, proses ini dapat dihentikan. Hal ini terjadi jika sistem menghentikan perender untuk mengklaim kembali memori atau jika proses itu sendiri mengalami error. Jika Anda tidak menangani penghentian ini, aplikasi Anda juga akan keluar.

Alasan pentingnya menangani penghentian proses

Pada versi Android yang lebih rendah, pengguna cenderung tidak menyadari masalah saat aplikasi gagal menangani penghentian proses perender. Namun, versi Android yang lebih baru menggunakan mekanisme yang lebih kompleks untuk mengelola konsumsi resource proses latar belakang. Oleh karena itu, jika aplikasi Anda tidak menerapkan pengendali untuk onRenderProcessGone, kemungkinan besar pengguna akan melihat penghentian latar depan yang terlihat saat kembali ke aplikasi. Penerapan Termination Handling API diperlukan untuk memastikan aplikasi Anda pulih dengan baik dan mempertahankan pengalaman pengguna yang stabil.

Menggunakan Termination Handling API

Anda dapat menangani situasi ini dengan mengganti onRenderProcessGone di WebViewClient.

Persyaratan utama untuk pemulihan

Agar aplikasi Anda tetap berjalan setelah proses perender dihentikan, setiap WebView di aplikasi Anda harus mengikuti aturan berikut:

  • Jangan gunakan kembali instance WebView. Setelah perender hilang, objek WebView tertentu tersebut tidak akan berguna. Anda harus menghapus objek WebView dari hierarki tampilan, menghancurkannya, dan menghapus referensi apa pun ke objek tersebut di Aktivitas atau Fragmen untuk menghindari pemanggilan metode secara tidak sengaja pada instance WebView yang dihancurkan.
  • Buat instance baru. Jika ingin terus menampilkan konten web, Anda memerlukan instance WebView baru.
  • Menampilkan true. Implementasi Anda harus menampilkan true untuk memberi tahu kode WebView bahwa Anda telah menangani situasi tersebut. Jika ada WebView yang menggunakan proses perender tersebut tidak menampilkan true, WebView akan menghentikan atau membuat aplikasi Anda error sesuai dengan alasan keluar dari proses perender.

Contoh penerapan

Contoh berikut menunjukkan cara menangani penghentian ini dalam Aktivitas lengkap. Class ini menggunakan detail.didCrash() untuk mencatat apakah proses error atau apakah sistem menghentikan perender untuk mengklaim kembali memori. Terlepas dari penyebabnya, kode akan melakukan pembersihan dengan menghapus WebView yang tidak berfungsi dari tata letak, menghancurkannya, dan menghapus referensi Aktivitas untuk mencegah NullPointerException. Kemudian, class ini menyiapkan instance baru dan menampilkan true agar aplikasi tidak ditutup.

Catatan: Contoh berikut selalu menampilkan true agar aplikasi tetap berjalan. Jika Anda secara khusus ingin aplikasi dihentikan saat perender WebView dihentikan, Anda dapat menampilkan false dari callback ini. Namun, sebaiknya tangani pembuatan ulang dan tampilkan true untuk pengalaman pengguna yang stabil.

class MyWebViewActivity : AppCompatActivity() {

    private var webViewContainer: ViewGroup? = null
    private var myWebView: WebView? = null

    private var crashCount = 0
    private val MAX_CRASHES = 3

    // NEW FLAG: Tracks if the WebView crashed while the activity was hidden
    private var isWebViewPendingRecovery = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_web_view)
        webViewContainer = findViewById(R.id.my_web_view_container)

        initWebView()
    }

    // NEW LIFECYCLE HOOK: Triggers when the user brings the app back to the foreground
    override fun onResume() {
        super.onResume()
        // If a crash happened in the background, rebuild it now that the user can see it
        if (isWebViewPendingRecovery) {
            isWebViewPendingRecovery = false
            Log.i("MY_APP_TAG", "Activity resumed. Recovering pending WebView now.")
            recoverWebView()
        }
    }

    private fun initWebView() {
        val webView = WebView(this)
        myWebView = webView

        webViewContainer?.addView(webView)

        webView.webViewClient = object : WebViewClient() {
            override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean {
                if (detail.didCrash()) {
                    Log.e("MY_APP_TAG", "The WebView rendering process crashed!")
                } else {
                    Log.e("MY_APP_TAG", "System killed the WebView rendering process to reclaim memory.")
                }

                // Safely clean up the dead WebView
                webViewContainer?.removeView(webView)
                webView.destroy()
                myWebView = null

                // MODIFIED LOGIC: Check the lifecycle state of the Activity
                // lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) means the activity is visible and active
                if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
                    Log.i("MY_APP_TAG", "Activity is in foreground. Recovering immediately.")
                    recoverWebView()
                } else {
                    // The app is in the background. Mark it for later!
                    Log.w("MY_APP_TAG", "Activity is in background. Postponing recovery until user returns.")
                    isWebViewPendingRecovery = true
                }

                return true
            }
        }
        webView.loadUrl("https://www.example.com")
    }

    // HELPER FUNCTION: Extracted the recovery logic to reuse it
    private fun recoverWebView() {
        if (crashCount < MAX_CRASHES) {
            crashCount++
            Log.w("MY_APP_TAG", "Recovering WebView... Attempt $crashCount")
            initWebView()
        } else {
            Log.e("MY_APP_TAG", "WebView crashed too many times. Showing error UI.")
            showErrorUI()
        }
    }

    private fun showErrorUI() {
        // Example: Inflate an error view
    }

    override fun onDestroy() {
        super.onDestroy()
        myWebView?.let {
            webViewContainer?.removeView(it)
            it.destroy()
        }
        myWebView = null
    }
}

Pertimbangan penting

Saat menerapkan onRenderProcessGone, perhatikan pertimbangan berikut untuk memastikan stabilitas aplikasi dan mempertahankan visibilitas ke peristiwa penghentian:

  • Error berulang: Jika perender error saat memuat halaman tertentu, jangan langsung memuat ulang halaman yang sama. Hal ini dapat menyebabkan WebView baru error lagi.
  • Visibilitas: Penghentian aplikasi yang disebabkan oleh tidak adanya callback ini belum tercermin dalam laporan error Konsol Play. Meskipun update mendatang mungkin mengatasi kesenjangan ini, Anda harus menyelesaikan masalah ini sekarang untuk memastikan stabilitas aplikasi. Dengan menerapkan pengendali ini, Anda dapat langsung mengetahui alasan aplikasi Anda keluar.
  • Membedakan penghentian perender dari penghentian proses aplikasi: onRenderProcessGone menangani situasi saat proses perender dihentikan sementara proses aplikasi Anda tetap aktif. Jika seluruh proses aplikasi dihentikan oleh sistem di latar belakang, gunakan onSaveInstanceState() dengan WebView.saveState() dan WebView.restoreState() untuk mempertahankan dan memulihkan histori navigasi. Untuk mengelola status tersimpan dengan aman tanpa mencapai batas transaksi, lihat Mengelola status WebView secara efisien.