處理 WebView 終止

WebView 會在獨立的轉譯器程序中執行網頁內容。有時這個程序可能會終止。如果系統終止轉譯器以回收記憶體,或是程序本身當機,就會發生這種情況。如未處理這些終止作業,應用程式也會一併結束。

為何處理程序終止至關重要

在較舊的 Android 版本中,如果應用程式無法處理終止的算繪器程序,使用者較不容易發現問題。不過,新版 Android 會使用更複雜的機制來管理背景程序的資源耗用量。因此,如果應用程式未實作 onRenderProcessGone 的處理常式,使用者返回應用程式時,就更有可能看到前景終止。您必須實作 Termination Handling API,確保應用程式能順利復原,並維持穩定的使用者體驗。

使用 Termination Handling API

如要處理這些情況,請在 WebViewClient 中覆寫 onRenderProcessGone

復原的重要條件

如要在算繪程序終止後讓應用程式繼續執行,應用程式中的每個 WebView 都必須遵守下列規則:

  • 請勿重複使用 WebView 執行個體。轉譯器消失後,該特定 WebView 物件就沒有用處。您必須從檢視區塊階層中移除 WebView 物件、將其銷毀,並清除 Activity 或 Fragment 中對該物件的所有參照,以免意外繼續呼叫已銷毀的 WebView 執行個體上的方法。
  • 建立新執行個體。如要繼續顯示網頁內容,您需要新的 WebView 執行個體。
  • 傳回 true。實作項目必須傳回 true,告知 WebView 程式碼您已處理情況。如果使用該算繪器程序的任何 WebView未傳回 true,WebView 會根據算繪器程序結束原因終止或當機。

導入範例

以下範例說明如何在完整的 Activity 中處理這些終止作業。系統會使用 detail.didCrash() 記錄程序是否當機,或系統是否終止了算繪器來回收記憶體。無論原因為何,程式碼都會從版面配置中移除已失效的 WebView、將其刪除,並清除活動的參照,以防止 NullPointerException。接著準備新的例項並傳回 true,避免應用程式關閉。

注意:下列範例一律會傳回 true,確保應用程式持續執行。 如果希望應用程式在 WebView 轉譯器終止時終止,可以從這個回呼傳回 false。不過,建議您處理重新建立作業並傳回 true,以確保使用者體驗穩定。

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
    }
}

重要事項

導入 onRenderProcessGone 時,請注意下列事項,確保應用程式穩定性,並持續掌握終止事件的能見度:

  • 重複當機:如果算繪器在載入特定網頁時當機,請不要立即重新載入相同網頁。這可能會導致新的 WebView 再次當機。
  • 顯示:如果缺少這個回呼而導致應用程式終止,Play 管理中心的當機報告目前不會反映這類情況。日後的更新或許會解決這個問題,但您現在必須解決這些問題,才能確保應用程式穩定性。實作這個處理常式可立即瞭解應用程式的退出原因。