Gestire la chiusura di WebView

WebView esegue i contenuti web in un processo di rendering separato. A volte, questo processo può essere interrotto. Ciò accade se il sistema termina il renderer per recuperare memoria o se il processo stesso si arresta in modo anomalo. Se non gestisci queste terminazioni, anche la tua app verrà chiusa.

Perché la gestione della terminazione dei processi è fondamentale

Nelle versioni precedenti di Android, gli utenti avevano meno probabilità di notare un problema quando un'app non riusciva a gestire la terminazione del processo di rendering. Tuttavia, le versioni più recenti di Android utilizzano meccanismi più complessi per gestire il consumo di risorse dei processi in background. Di conseguenza, se la tua app non implementa un gestore per onRenderProcessGone, ora è più probabile che gli utenti osservino terminazioni visibili in primo piano quando tornano all'app. L'implementazione dell'API Termination Handling è necessaria per garantire che l'app si ripristini correttamente e mantenga un'esperienza utente stabile.

Utilizza l'API Termination Handling

Puoi gestire queste situazioni eseguendo l'override di onRenderProcessGone nel tuo WebViewClient.

Requisiti chiave per il recupero

Per mantenere l'app in esecuzione dopo la chiusura di un processo di rendering, ogni WebView nell'app deve seguire queste regole:

  • Non riutilizzare l'istanza WebView. Una volta che il renderer non è più disponibile, l'oggetto WebView specifico è inutile. Devi rimuovere l'oggetto WebView dalla gerarchia di oggetti View, distruggerlo e cancellare tutti i riferimenti nell'Activity o nel Fragment per evitare di continuare a chiamare accidentalmente i metodi nell'istanza WebView distrutta.
  • Crea una nuova istanza. Se vuoi continuare a mostrare contenuti web, devi creare una nuova istanza WebView.
  • Restituisci true. La tua implementazione deve restituire true per comunicare al codice WebView che hai gestito la situazione. Se un WebView che utilizza quel processo di rendering non restituisce true, WebView interromperà o arresterà in modo anomalo la tua app in corrispondenza del motivo di uscita dal processo di rendering.

Esempio di implementazione

L'esempio seguente mostra come gestire queste terminazioni all'interno di un'attività completa. Utilizza detail.didCrash() per registrare se il processo ha subito un arresto anomalo o se il sistema ha interrotto il renderer per recuperare memoria. Indipendentemente dalla causa, il codice esegue la pulizia rimuovendo la WebView non funzionante dal layout, distruggendola e cancellando i riferimenti dell'attività per impedire NullPointerException. Quindi prepara una nuova istanza e restituisce true in modo che l'app non si chiuda.

Nota:l'esempio seguente restituisce sempre true per mantenere l'app in esecuzione. Se vuoi specificamente che la tua app venga terminata quando il renderer WebView termina, puoi restituire false da questo callback. Tuttavia, ti consigliamo di gestire la ricreazione e di restituire true per un'esperienza utente stabile.

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

Considerazioni importanti

Quando implementi onRenderProcessGone, tieni presente le seguenti considerazioni per garantire la stabilità dell'app e mantenere la visibilità sugli eventi di terminazione:

  • Arresti anomali ripetuti:se un renderer si arresta in modo anomalo durante il caricamento di una pagina specifica, non ricaricare immediatamente la stessa pagina. Potrebbe causare un nuovo arresto anomalo di WebView.
  • Visibilità:le chiusure forzate delle app causate dalla mancanza di questo callback non sono ancora riportate nei report sugli arresti anomali di Play Console. Anche se i futuri aggiornamenti potrebbero risolvere questa lacuna, devi risolvere questi problemi ora per garantire la stabilità dell'app. L'implementazione di questo gestore fornisce visibilità immediata sul motivo per cui l'app si chiude.
  • Distinguere la terminazione del renderer dall'interruzione del processo dell'app: onRenderProcessGone gestisce le situazioni in cui il processo del renderer viene terminato mentre il processo dell'app rimane attivo. Se l'intero processo dell'app viene interrotto dal sistema in background, utilizza onSaveInstanceState() con WebView.saveState() e WebView.restoreState() per rendere persistente e recuperare la cronologia di navigazione. Per gestire lo stato salvato in modo sicuro senza raggiungere i limiti delle transazioni, consulta Gestisci lo stato di WebView in modo efficiente.