Obsługa zamknięcia WebView

WebView uruchamia treści z internetu w osobnym procesie renderowania. Czasami ten proces może zostać przerwany. Dzieje się tak, gdy system wyłącza mechanizm renderowania, aby odzyskać pamięć, lub gdy proces ulega awarii. Jeśli nie obsłużysz tych zakończeń, aplikacja również zostanie zamknięta.

Dlaczego obsługa zakończenia procesu jest kluczowa

W starszych wersjach Androida użytkownicy rzadziej zauważali problem, gdy aplikacja nie radziła sobie z zakończeniem procesu renderowania. Nowsze wersje Androida używają jednak bardziej złożonych mechanizmów zarządzania wykorzystaniem zasobów przez procesy w tle. Jeśli więc Twoja aplikacja nie implementuje modułu obsługi dla onRenderProcessGone, jest teraz bardziej prawdopodobne, że użytkownicy będą widzieć widoczne zakończenia na pierwszym planie po powrocie do aplikacji. Aby aplikacja mogła się prawidłowo przywracać i zapewniać stabilne działanie, musisz zaimplementować interfejs API do obsługi zakończeń.

Korzystanie z interfejsu Termination Handling API

Możesz poradzić sobie z takimi sytuacjami, zastępując onRenderProcessGone w WebViewClient.

Najważniejsze wymagania dotyczące odzyskiwania

Aby aplikacja działała po zakończeniu procesu renderowania, każdy komponent WebView w aplikacji musi spełniać te warunki:

  • Nie używaj ponownie instancji WebView. Gdy silnik renderujący zniknie, dany obiekt WebView stanie się bezużyteczny. Aby uniknąć przypadkowego wywoływania metod w zniszczonej instancji WebView, musisz usunąć obiekt WebView z hierarchii widoków, zniszczyć go i wyczyścić wszystkie odwołania do niego w aktywności lub fragmencie.
  • Utwórz nową instancję. Jeśli chcesz nadal wyświetlać treści z internetu, musisz utworzyć nową instancję WebView.
  • Zwróć wartość „true”. Twoja implementacja musi zwracać true, aby poinformować kod WebView, że sytuacja została obsłużona. Jeśli jakikolwiek obiekt WebView korzystający z tego procesu renderowania nie zwróci wartości true, WebView zamknie lub zawiesi aplikację zgodnie z powodem zakończenia procesu renderowania.

Przykład wdrożenia

Poniższy przykład pokazuje, jak obsługiwać te zakończenia w ramach pełnej aktywności. Używa detail.didCrash() do rejestrowania, czy proces uległ awarii lub czy system zakończył działanie mechanizmu renderowania, aby odzyskać pamięć. Niezależnie od przyczyny kod czyści pamięć, usuwając nieaktywny obiekt WebView z układu, niszcząc go i czyszcząc odwołania do aktywności, aby zapobiec NullPointerException. Następnie przygotowuje nową instancję i zwraca true, aby aplikacja nie została zamknięta.

Uwaga: w poniższym przykładzie zawsze zwracana jest wartość true, aby aplikacja działała. Jeśli chcesz, aby aplikacja została zamknięta, gdy zostanie zamknięty mechanizm renderowania WebView, możesz zwrócić false z tego wywołania zwrotnego. Zalecamy jednak obsługę ponownego tworzenia i zwracanie wartości true, aby zapewnić stabilne działanie.

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

Ważne informacje

Podczas wdrażania metody onRenderProcessGone pamiętaj o tych kwestiach, aby zapewnić stabilność aplikacji i zachować widoczność zdarzeń zakończenia:

  • Powtarzające się awarie: jeśli podczas wczytywania konkretnej strony dojdzie do awarii mechanizmu renderowania, nie odświeżaj od razu tej samej strony. Może to spowodować ponowną awarię nowego komponentu WebView.
  • Widoczność: zamknięcia aplikacji spowodowane brakiem tego wywołania zwrotnego nie są jeszcze uwzględniane w raportach o awariach w Konsoli Play. Przyszłe aktualizacje mogą rozwiązać ten problem, ale aby zapewnić stabilność aplikacji, musisz rozwiązać te problemy już teraz. Zaimplementowanie tego modułu obsługi zapewnia natychmiastowy wgląd w przyczyny zamykania aplikacji.
  • Odróżnianie zakończenia działania renderera od śmierci procesu aplikacji: onRenderProcessGone obsługuje sytuacje, w których proces renderera zostaje zakończony, ale proces aplikacji pozostaje aktywny. Jeśli cały proces aplikacji zostanie zamknięty przez system w tle, użyj onSaveInstanceState() z WebView.saveState() i WebView.restoreState(), aby zachować i przywrócić historię nawigacji. Aby bezpiecznie zarządzać zapisanym stanem bez przekraczania limitów transakcji, zapoznaj się z artykułem Efektywne zarządzanie stanem WebView.