Processar o encerramento do WebView

O WebView executa o conteúdo da Web em um processo de renderização separado. Às vezes, esse processo pode ser encerrado. Isso acontece se o sistema encerrar o renderizador para recuperar memória ou se o próprio processo falhar. Se você não lidar com essas finalizações, seu app também será encerrado.

Por que o processamento do encerramento de processos é fundamental

Em versões anteriores do Android, era menos provável que os usuários percebessem um problema quando um app não conseguia processar o encerramento do processo do renderizador. No entanto, as versões mais recentes do Android usam mecanismos mais complexos para gerenciar o consumo de recursos de processos em segundo plano. Consequentemente, se o app não implementar um gerenciador para onRenderProcessGone, é mais provável que os usuários observem encerramentos visíveis em primeiro plano ao retornar ao app. A implementação da API Termination Handling é necessária para garantir que o app se recupere de forma adequada e mantenha uma experiência do usuário estável.

Usar a API Termination Handling

Para lidar com essas situações, substitua onRenderProcessGone no WebViewClient.

Principais requisitos para recuperação

Para manter o app em execução após o encerramento de um processo de renderização, cada WebView no app precisa seguir estas regras:

  • Não reutilize a instância da WebView. Depois que o renderizador é removido, esse objeto WebView específico se torna inútil. Remova o objeto WebView da hierarquia de visualização, destrua-o e limpe todas as referências a ele na atividade ou no fragmento para evitar que métodos continuem sendo chamados acidentalmente na instância de WebView destruída.
  • Criar uma nova instância. Se você quiser continuar mostrando conteúdo da Web, precisará de uma nova instância de WebView.
  • Retorna verdadeiro. Sua implementação precisa retornar true para informar ao código da WebView que você lidou com a situação. Se algum WebView que usa esse processo de renderização não retornar true, o WebView vai encerrar ou causar falhas no app de acordo com o motivo de saída do processo de renderização.

Exemplo de implementação

O exemplo a seguir mostra como lidar com essas finalizações em uma Activity completa. Ele usa detail.didCrash() para registrar se o processo falhou ou se o sistema encerrou o renderizador para recuperar memória. Independentemente da causa, o código faz uma limpeza removendo a WebView inativa do layout, destruindo-a e limpando as referências da atividade para evitar NullPointerException. Em seguida, ele prepara uma nova instância e retorna true para que o app não seja fechado.

Observação:o exemplo a seguir sempre retorna true para manter o app em execução. Se você quiser que o app seja encerrado quando o renderizador do WebView for encerrado, retorne false desse callback. No entanto, recomendamos processar a recriação e retornar true para uma experiência do usuário estável.

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

Considerações importantes

Ao implementar onRenderProcessGone, considere o seguinte para garantir a estabilidade do app e manter a visibilidade dos eventos de encerramento:

  • Falhas repetidas:se um renderizador falhar ao carregar uma página específica, não recarregue a mesma página imediatamente. Isso pode fazer com que o novo WebView falhe novamente.
  • Visibilidade:as finalizações de apps causadas pela falta desse callback ainda não são refletidas nos relatórios de erros do Play Console. Embora as atualizações futuras possam resolver essa lacuna, você precisa resolver esses problemas agora para garantir a estabilidade do app. A implementação desse gerenciador oferece visibilidade imediata do motivo pelo qual o app é encerrado.
  • Distinguir o encerramento do renderizador do encerramento do processo do app:o onRenderProcessGone lida com situações em que o processo do renderizador é encerrado enquanto o processo do app permanece ativo. Se todo o processo do app for encerrado pelo sistema em segundo plano, use onSaveInstanceState() com WebView.saveState() e WebView.restoreState() para persistir e recuperar o histórico de navegação. Para gerenciar o estado salvo com segurança sem atingir os limites de transação, consulte Gerenciar o estado do WebView com eficiência.