WebView'nin sonlandırılmasını işleme

WebView, web içeriğini ayrı bir oluşturucu işleminde çalıştırır. Bazen bu işlem sonlandırılabilir. Bu durum, sistem belleği geri kazanmak için oluşturucuyu sonlandırdığında veya işlemin kendisi kilitlendiğinde ortaya çıkar. Bu sonlandırmaları işlemezseniz uygulamanız da kapanır.

İşlem sonlandırmayı yönetmek neden önemlidir?

Android'in daha eski sürümlerinde, bir uygulama oluşturucu işlemi sonlandırmayı başaramadığında kullanıcıların sorunu fark etme olasılığı daha düşüktü. Ancak Android'in daha yeni sürümleri, arka plan işlemlerinin kaynak tüketimini yönetmek için daha karmaşık mekanizmalar kullanır. Bu nedenle, uygulamanız onRenderProcessGone için bir işleyici uygulamıyorsa kullanıcıların uygulamaya döndüklerinde görünür ön plan sonlandırmaları görme olasılığı artık daha yüksektir. Uygulamanızın sorunsuz bir şekilde kurtarılmasını ve istikrarlı bir kullanıcı deneyimi sunmasını sağlamak için Termination Handling API'yi uygulamanız gerekir.

Termination Handling API'yi kullanma

WebViewClient dosyanızda onRenderProcessGone değerini geçersiz kılarak bu durumları yönetebilirsiniz.

Kurtarma için temel şartlar

Uygulamanızın, oluşturma işlemi sonlandırıldıktan sonra çalışmaya devam etmesi için uygulamanızdaki her WebView'un aşağıdaki kurallara uyması gerekir:

  • WebView örneğini yeniden kullanmayın. Oluşturucu kaldırıldıktan sonra ilgili WebView nesnesi kullanılamaz. Yok edilen WebView örneğinde yanlışlıkla yöntemleri çağırmaya devam etmemek için WebView nesnesini görünüm hiyerarşinizden kaldırmanız, yok etmeniz ve Etkinlik veya Parça'daki tüm referanslarını temizlemeniz gerekir.
  • Yeni bir örnek oluşturun. Web içeriği göstermeye devam etmek istiyorsanız yeni bir WebView örneği oluşturmanız gerekir.
  • Doğru değerini döndürün. Uygulamanız, WebView koduna durumu ele aldığınızı bildirmek için true döndürmelidir. Bu oluşturucu işlemini kullanan herhangi bir WebView, true döndürmezse WebView, oluşturucu işleminin çıkış nedenine göre uygulamanızı sonlandırır veya kilitler.

Uygulama örneği

Aşağıdaki örnekte, bu sonlandırma işlemlerinin eksiksiz bir Activity içinde nasıl işleneceği gösterilmektedir. İşlemin kilitlenip kilitlenmediğini veya sistemin belleği geri almak için oluşturucuyu sonlandırıp sonlandırmadığını günlüğe kaydetmek için detail.didCrash() kullanır. Nedeni ne olursa olsun kod, çalışmayan WebView'u düzenden kaldırarak, yok ederek ve etkinliğin referanslarını temizleyerek NullPointerException sorununu önler. Ardından yeni bir örnek hazırlar ve uygulamanın kapanmaması için true değerini döndürür.

Not: Aşağıdaki örnek, uygulamanın çalışmaya devam etmesi için her zaman true değerini döndürür. Uygulamanızın özellikle WebView oluşturucu sonlandırıldığında sonlandırılmasını istiyorsanız bu geri çağırmadan false döndürebilirsiniz. Ancak kararlı bir kullanıcı deneyimi için yeniden oluşturma işlemini gerçekleştirmenizi ve true değerini döndürmenizi öneririz.

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

Dikkat edilmesi gerekenler

onRenderProcessGone uygularken uygulama kararlılığını sağlamak ve sonlandırma etkinliklerini görünür kılmak için aşağıdaki noktaları göz önünde bulundurun:

  • Tekrarlanan kilitlenmeler: Bir oluşturucu belirli bir sayfayı yüklerken kilitlenirse aynı sayfayı hemen yeniden yüklemeyin. Bu durum, yeni WebView'un tekrar kilitlenmesine neden olabilir.
  • Görünürlük: Bu geri çağırmanın eksik olmasından kaynaklanan uygulama sonlandırmaları henüz Play Console kilitlenme raporlarına yansıtılmamaktadır. Gelecekteki güncellemeler bu boşluğu doldurabilir ancak uygulamanın kararlılığını sağlamak için bu sorunları hemen çözmeniz gerekir. Bu işleyiciyi uygulamak, uygulamanızın neden kapandığına dair anında görünürlük sağlar.
  • Oluşturucu sonlandırmasını uygulama işlemi sonlandırmasından ayırt etme: onRenderProcessGone, uygulama işleminiz etkin kalırken oluşturucu işleminin sonlandırıldığı durumları ele alır. Uygulama işleminin tamamı arka planda sistem tarafından sonlandırılırsa navigasyon geçmişini korumak ve kurtarmak için WebView.saveState() ve WebView.restoreState() ile onSaveInstanceState() kullanın. Kaydedilen durumu işlem sınırlarına ulaşmadan güvenli bir şekilde yönetmek için WebView durumunu verimli bir şekilde yönetme başlıklı makaleyi inceleyin.