处理 WebView 终止

WebView 在单独的渲染器进程中运行其 Web 内容。有时,此过程可能会终止。如果系统终止渲染器以回收内存,或者进程本身崩溃,就会发生这种情况。如果您不处理这些终止,您的应用也会退出。

为什么处理进程终止至关重要

在较低版本的 Android 上,当应用无法处理渲染程序进程终止时,用户不太可能注意到问题。不过,较新版本的 Android 会使用更复杂的机制来管理后台进程的资源消耗。因此,如果您的应用未实现 onRenderProcessGone 的处理程序,那么现在用户在返回应用时,更有可能观察到可见的前台终止。您必须实现 Termination Handling API,以确保应用能够顺利恢复并保持稳定的用户体验。

使用 Termination Handling API

您可以通过在 WebViewClient 中替换 onRenderProcessGone 来处理这些情况。

恢复的关键要求

为了在渲染程序进程终止后让应用继续运行,应用中的每个 WebView 都必须遵循以下规则:

  • 请勿重复使用 WebView 实例。渲染程序消失后,该特定 WebView 对象将无法使用。您必须从视图层次结构中移除 WebView 对象,将其销毁,并清除 Activity 或 Fragment 中对它的任何引用,以避免意外继续对已销毁的 WebView 实例调用方法。
  • 创建新实例。如果您想继续显示 Web 内容,则需要一个新的 WebView 实例。
  • 返回 true。您的实现必须返回 true,以告知 WebView 代码您已处理该情况。如果使用该渲染程序进程的任何 WebView 未返回 true,WebView 将根据渲染程序进程退出原因终止或崩溃您的应用。

实现示例

以下示例展示了如何在完整的 Activity 中处理这些终止情况。它使用 detail.didCrash() 记录进程是否崩溃,或者系统是否终止了渲染程序以回收内存。无论原因如何,代码都会通过以下方式进行清理:从布局中移除失效的 WebView,将其销毁,并清除 Activity 的引用以防止 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 管理中心崩溃报告中。虽然未来的更新可能会解决这一差距,但您现在必须解决这些问题,以确保应用稳定性。实现此处理程序可让您立即了解应用退出的原因。
  • 区分渲染程序终止与应用进程终止: onRenderProcessGone 可处理渲染程序进程终止而应用进程保持活跃的情况。如果整个应用进程在后台被系统终止,请将 onSaveInstanceState() 与 WebView.saveState() 和 WebView.restoreState() 搭配使用,以持久保存和恢复导航历史记录。如需在不达到事务限制的情况下安全地管理已保存的状态,请参阅高效管理 WebView 状态。