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 interromper o renderizador para recuperar a memória ou se o próprio processo falhar. Se você não processar essas finalizações, o app também será encerrado.

Por que o tratamento do encerramento de processos é essencial

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

Usar a API Termination Handling

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

Principais requisitos para a 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 do WebView. Depois que o renderizador desaparece, esse objeto WebView específico não tem mais utilidade. Remova o objeto WebView da hierarquia de visualização, destrua-o e limpe todas as referências a ele na sua atividade ou fragmento para evitar continuar chamando métodos acidentalmente na instância destruída do WebView.
  • Crie uma instância. Se quiser continuar mostrando conteúdo da Web, você precisa de uma nova instância do WebView.
  • Retorne "true". Sua implementação precisa retornar true para informar ao código WebView que você resolveu a situação. Se algum WebView que usa esse processo de renderização não retornar true, o WebView vai encerrar ou travar o app de acordo com o motivo de saída do processo de renderização.

Exemplo de implementação

O exemplo a seguir mostra como processar essas finalizações em uma atividade completa. Ele usa detail.didCrash() para registrar se o processo falhou ou se o sistema encerrou o renderizador para recuperar a memória. Independente da causa, o código limpa 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 da 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, tenha em mente as seguintes considerações para garantir a estabilidade do app e manter a visibilidade dos eventos de encerramento:

  • Travamentos repetidos:se um renderizador travar ao carregar uma página específica, não recarregue a mesma página imediatamente. Isso pode fazer com que a nova WebView falhe de novo.
  • Visibilidade:as finalizações de apps causadas pela falta desse callback ainda não são refletidas nos relatórios de falhas do Play Console. Embora as atualizações futuras possam resolver essa lacuna, é necessário resolver esses problemas agora para garantir a estabilidade do app. A implementação desse processador oferece visibilidade imediata sobre o motivo da saída do app.