التعامل مع إنهاء WebView

يشغّل WebView محتوى على الويب في عملية عارض منفصلة. في بعض الأحيان، يمكن إنهاء هذه العملية. يحدث ذلك إذا أنهى النظام عملية العرض لاستعادة الذاكرة أو إذا تعطّلت العملية نفسها. إذا لم تتعامل مع عمليات الإنهاء هذه، فسيتم إنهاء تطبيقك أيضًا.

أهمية معالجة إنهاء العمليات

في إصدارات Android الأقدم، كان من غير المرجّح أن يلاحظ المستخدمون مشكلة عندما يتعذّر على التطبيق معالجة إنهاء عملية العارض. ومع ذلك، تستخدم الإصدارات الأحدث من Android آليات أكثر تعقيدًا لإدارة استهلاك الموارد للعمليات في الخلفية. وبالتالي، إذا لم يتضمّن تطبيقك معالجًا لـ onRenderProcessGone، من المرجّح أن يلاحظ المستخدمون الآن عمليات إيقاف مرئية في المقدّمة عند العودة إلى التطبيق. يجب استخدام واجهة برمجة التطبيقات Termination Handling API لضمان استرداد تطبيقك بشكل سلس والحفاظ على تجربة مستخدم مستقرة.

استخدام واجهة Termination Handling API

يمكنك التعامل مع هذه الحالات من خلال إلغاء onRenderProcessGone في WebViewClient.

المتطلبات الأساسية للاسترداد

لمواصلة تشغيل تطبيقك بعد إنهاء عملية العارض، يجب أن يتّبع كل WebView في تطبيقك القواعد التالية:

  • لا تُعِد استخدام مثيل WebView. بعد اختفاء محرّك العرض، يصبح عنصر WebView المحدّد هذا عديم الفائدة. يجب إزالة عنصر WebView من هيكلية طرق العرض وتدميره ومحو أي إشارات إليه في "النشاط" أو "الجزء" لتجنُّب الاستمرار في استدعاء الطرق عن طريق الخطأ في مثيل WebView الذي تم تدميره.
  • أنشئ مثيلاً جديدًا. إذا أردت مواصلة عرض المحتوى على الويب، عليك استخدام مثيل WebView جديد.
  • عرض القيمة "صحيح" يجب أن يعرض التنفيذ القيمة true لإعلام رمز WebView بأنّك تعاملت مع الموقف. إذا لم يعرض أي WebView يستخدم عملية العارض هذه true، سيوقف WebView تطبيقك أو يتعطّل وفقًا لسبب الخروج من عملية العارض.

مثال على عملية التنفيذ

يوضّح المثال التالي كيفية التعامل مع عمليات الإنهاء هذه ضمن نشاط كامل. يستخدم detail.didCrash() لتسجيل ما إذا كانت العملية قد تعطّلت أو إذا أنهى النظام عملية العرض لاستعادة الذاكرة. بغض النظر عن السبب، يزيل الرمز WebView غير الصالح من التنسيق، ويتلفه، ويمحو مراجع النشاط لمنع حدوث NullPointerException. بعد ذلك، يتم إعداد مثيل جديد وعرض القيمة true حتى لا يتم إغلاق التطبيق.

ملاحظة: يعرض المثال التالي دائمًا القيمة true لمواصلة تشغيل التطبيق. إذا أردت تحديدًا إنهاء تطبيقك عند إنهاء عرض WebView، يمكنك عرض false من ردّ الاتصال هذا. ومع ذلك، ننصحك بمعالجة عملية إعادة الإنشاء وعرض القيمة 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
    }
}

اعتبارات مُهمّة

عند تنفيذ onRenderProcessGone، يجب مراعاة ما يلي لضمان استقرار التطبيق والحفاظ على إمكانية الاطّلاع على أحداث الإنهاء:

  • الأعطال المتكررة: إذا تعطّل العارض أثناء تحميل صفحة معيّنة، لا تُعِد تحميل الصفحة نفسها على الفور. وقد يؤدي ذلك إلى تعطُّل WebView الجديد مرة أخرى.
  • الظهور: لا تظهر عمليات إيقاف التطبيق الناتجة عن عدم توفُّر دالة الاستدعاء هذه في تقارير الأعطال على Play Console. ومع أنّ التحديثات المستقبلية قد تعالج هذه المشكلة، عليك حلّها الآن لضمان ثبات التطبيق. ويوفّر تنفيذ هذا المعالج رؤية فورية لسبب خروج تطبيقك.
  • التمييز بين إنهاء عملية العارض وإيقاف عملية التطبيق نهائيًا: يتعامل onRenderProcessGone مع الحالات التي يتم فيها إنهاء عملية العارض بينما تظل عملية التطبيق نشطة. إذا أنهى النظام عملية التطبيق بالكامل في الخلفية، استخدِم onSaveInstanceState() مع WebView.saveState() وWebView.restoreState() للاحتفاظ بسجلّ التنقّل واسترداده. لإدارة الحالة المحفوظة بأمان بدون تجاوز حدود المعاملات، يُرجى الاطّلاع على إدارة حالة WebView بكفاءة.