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、將其刪除,並清除 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 狀態」。