Obsługa zamknięcia WebView

WebView uruchamia treści internetowe w osobnym procesie renderowania. Czasami ten proces może zostać zakończony. Dzieje się tak, gdy system zamyka proces renderowania, aby odzyskać pamięć, lub gdy sam proces ulega awarii. Jeśli nie obsłużysz tych zakończeń, Twoja aplikacja też się zamknie.

Dlaczego obsługa zakończenia procesu jest tak ważna

W starszych wersjach Androida użytkownicy rzadziej zauważali problem, gdy aplikacja nie obsługiwała zakończenia procesu renderowania. Jednak nowsze wersje Androida używają bardziej złożonych mechanizmów do zarządzania zużyciem zasobów przez procesy działające w tle. W związku z tym, jeśli Twoja aplikacja nie implementuje obsługi dla onRenderProcessGone, istnieje większe prawdopodobieństwo, że użytkownicy zauważą widoczne zakończenia działania aplikacji na pierwszym planie, gdy do niej wrócą. Aby zapewnić prawidłowe działanie aplikacji i stabilność, musisz zaimplementować interfejs Termination Handling API.

Korzystanie z interfejsu Termination Handling API

Możesz obsługiwać te sytuacje, zastępując onRenderProcessGone w WebViewClient.

Najważniejsze wymagania dotyczące przywracania

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

  • Nie używaj ponownie instancji WebView. Gdy proces renderowania zostanie zakończony, dany obiekt WebView będzie 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 internetowe, potrzebujesz nowej instancji WebView.
  • Zwróć wartość true. Implementacja musi zwracać wartość true, aby poinformować kod WebView, że sytuacja została obsłużona. Jeśli jakikolwiek komponent WebView korzystający z tego procesu renderowania nie zwróci wartości true, WebView zamknie lub spowoduje awarię aplikacji zgodnie z przyczyną zakończenia procesu renderowania.

Przykład wdrożenia

Poniższy przykład pokazuje, jak obsługiwać te zakończenia w pełnej aktywności. Używa on detail.didCrash(), aby rejestrować, czy proces uległ awarii, czy też system zamknął proces renderowania, aby odzyskać pamięć. Niezależnie od przyczyny kod czyści dane, usuwając martwy komponent WebView z układu, niszcząc go, i czyszcząc odwołania do niego w aktywności, aby zapobiec wystąpieniu wyjątku NullPointerException. Następnie przygotowuje nową instancję i zwraca wartość true, aby aplikacja się nie zamykała.

Uwaga: poniższy przykład zawsze zwraca wartość true, aby aplikacja działała. Jeśli chcesz, aby aplikacja zamykała się, gdy proces renderowania WebView zostanie zakończony, możesz zwrócić wartość false z tego wywołania zwrotnego. Jednak w celu zapewnienia stabilności zalecamy obsługę ponownego tworzenia i zwracanie wartości 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
    }
}

Ważne informacje

Podczas implementowania onRenderProcessGone pamiętaj o tych kwestiach, aby zapewnić stabilność aplikacji i widoczność zdarzeń zakończenia:

  • Powtarzające się awarie: jeśli proces renderowania ulega awarii podczas wczytywania określonej strony, nie ładuj 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. Chociaż przyszłe aktualizacje mogą rozwiązać ten problem, musisz go teraz naprawić, aby zapewnić stabilność aplikacji. Zaimplementowanie tej obsługi zapewnia natychmiastową widoczność przyczyny zamknięcia aplikacji.