تحسين عملية بدء تشغيل WebView

عندما يستخدم تطبيقك مكوّن WebView لأول مرة، ينفِّذ النظام مهام بدء تشغيل محدّدة. عملية بدء التشغيل هذه ثقيلة. يحدث ذلك تلقائيًا في سلسلة التعليمات الخاصة بواجهة المستخدم في المرة الأولى التي يستدعي فيها التطبيق العديد من واجهات برمجة التطبيقات ضمن حزمتَي android.webkit أو androidx.webkit، أو يوسّع تنسيقًا يحتوي على علامة WebView.

أهمية ذلك

وبما أنّ عملية بدء التشغيل الضمني هذه تحدث بالكامل على سلسلة التعليمات الرئيسية، فإنّها تمنع تطبيقك من معالجة البيانات التي يُدخلها المستخدم وتزيد بشكل كبير من خطر حدوث أخطاء "التطبيق لا يستجيب" (ANR). لمزيد من المعلومات حول طريقة تعامل نظام التشغيل Android مع نموذج التنفيذ ذي السلسلة الواحدة، يمكنك الاطّلاع على نظرة عامة على العمليات وسلاسل التنفيذ.

أسباب التشغيل الضمني

يمكن بدء التشغيل الضمني بالطرق التالية:

  • برمجيًا: استدعاء واجهات برمجة التطبيقات مثل WebSettings.getUserAgentString()
  • استخدام التصاميم: استدعاء setContentView() أو layoutInflater.inflate() في مورد XML يتضمّن <WebView>

يمكن أن يؤثر بدء التشغيل الضمني أيضًا بشكل سلبي في مقاييس نشاطك التجاري، مثل وقت بدء تشغيل التطبيق والوقت اللازم لظهور الشاشة الأولى. إذا لم يكن الإعداد الضمني مناسبًا لتطبيقك، استخدِم startUpWebView بدلاً من ذلك.

تتناول هذه الصفحة كيفية تحسين أداء بدء تشغيل WebView باستخدام واجهة برمجة التطبيقات startUpWebView.

التحكّم في بدء تشغيل WebView

لتحسين الأداء وتقليل أخطاء ANR، استخدِم واجهة برمجة التطبيقات startUpWebView المتاحة في مكتبة Jetpack Webkit. تمنحك واجهة برمجة التطبيقات هذه تحكّمًا صريحًا في وقت بدء تشغيل WebView. ويؤدي ذلك إلى نقل جزء كبير من عبء العمل عند بدء التشغيل إلى سلسلة محادثات في الخلفية، كما يتيح تنفيذ أي عمل يجب أن يتم في سلسلة واجهة المستخدم على شكل أجزاء، بدلاً من كتلة واحدة كبيرة. يؤدي ذلك إلى إتاحة سلسلة التعليمات الخاصة بواجهة المستخدم للتعامل مع مهام أخرى مهمة في التطبيق بشكل متوازٍ، ما يقلّل من فرص حظر تجربة المستخدم.

تستخدم واجهة برمجة التطبيقات معاودة الاتصال androidx.webkit.WebViewOutcomeReceiver، ما يتيح لك تتبُّع عمليات الإعداد الناجحة.

لاستخدام واجهة برمجة التطبيقات هذه، أضِف مكتبة Jetpack Webkit إلى ملف build.gradle. تأكَّد من استخدام الإصدار 1.16.0 أو إصدار أحدث:

dependencies {
    implementation("androidx.webkit:webkit:1.16.0")
}

استخدام واجهة برمجة التطبيقات startUpWebView

تعتمد طريقة تحسين مسار بدء التشغيل على الوقت الذي يحتاج فيه تطبيقك إلى عرض WebView.

عندما لا يكون WebView على المسار الحرج

إذا لم يكن تطبيقك بحاجة إلى تحميل WebView على الفور، يمكنك إخفاء تكلفة التهيئة بالكامل. يجب استدعاء startUpWebView في وقت مبكر من دورة حياة تطبيقك والانتظار إلى أن يتم تشغيل معاودة الاتصال الخاصة بالنجاح.

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

عندما يكون WebView على المسار الحرج

إذا كانت تجربة المستخدم الأساسية في تطبيقك تتطلّب استخدام WebView على الفور، من المحتمل أنّه لا يمكنك الانتظار إلى حين اكتمال عملية بدء تشغيل WebView. في هذه الحالة، يجب أن تستمر في طلب startUpWebView في أقرب وقت ممكن خلال دورة حياة التطبيق (مثل Application.onCreate)، ولكن لا تنتظر أن يتم تشغيل معاودة الاتصال. بدلاً من ذلك، استخدِم واجهات برمجة تطبيقات WebView مباشرةً عند الحاجة إليها.

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

إذا اتصلت بـ startUpWebView واستدعيت على الفور واجهات برمجة تطبيقات WebView بعد ذلك في سلسلة التعليمات الرئيسية، سيتم حظر سلسلة تعليمات واجهة المستخدم في انتظار اكتمال عملية التهيئة. في هذا السيناريو، لن يكون هناك أي تحسّن في الأداء.

إذا كان استخدام WebView يمكن أن يصبح على المسار الحرج ولكنك لا تريد بدء تشغيل WebView بالكامل، يمكنك اختيار تنفيذ مهام بدء تشغيل WebView بشكل انتقائي التي يمكن تشغيلها على سلسلة تعليمات في الخلفية، ما يتيح استخدام سلسلة واجهة المستخدم لمهام أخرى مهمة في التطبيق. يمكنك استخدام shouldRunUiThreadStartUpTasks(false) لهذا الغرض.

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

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

تستخدِم واجهة برمجة التطبيقات وظيفة معاودة الاتصال androidx.webkit.WebViewOutcomeReceiver، ما يتيح لك تتبُّع عمليات الإعداد الناجحة أو معالجة أخطاء بيانات التشخيص.

يمكنك استدعاء startUpWebView عدة مرات من أجزاء مختلفة من تطبيقك بدون أي مشكلة، ولكن ننصحك بتجنُّب تنفيذ حلقة إعادة محاولة بسيطة.

يوضّح نموذج الرمز البرمجي التالي كيفية استخدام واجهة برمجة التطبيقات WebViewCompat.startUpWebView لإجراء عملية تهيئة غير متزامنة.

Kotlin

import android.content.Context
import android.util.Log
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewOutcomeReceiver
import androidx.webkit.WebViewStartUpConfig
import androidx.webkit.WebViewStartUpResult
import androidx.webkit.WebViewStartupException
import java.util.concurrent.Executors

fun initializeWebView(context: Context) {
    // 1. Create a startup configuration specifying the background thread
    // that WebView will use to run its initialization tasks.
    val startUpConfig = WebViewStartUpConfig.Builder(
        Executors.newSingleThreadExecutor()
    ).build()

    // 2. Trigger WebView startup asynchronously
    WebViewCompat.startUpWebView(
        context,
        startUpConfig,
        object : WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException> {

            override fun onResult(result: WebViewStartUpResult) {
                // Success: The WebView has finished its background initialization.
                // This callback is guaranteed to be invoked on the UI thread.
                setupWebView()
            }

            override fun onError(error: WebViewStartupException) {
                // Failure: The initialization encountered a startup exception.
                Log.e("WebViewStartup", "Failed to initialize WebView", error)
            }
        }
    )
}

Java

import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.webkit.WebViewCompat;
import androidx.webkit.WebViewOutcomeReceiver;
import androidx.webkit.WebViewStartUpConfig;
import androidx.webkit.WebViewStartUpResult;
import androidx.webkit.WebViewStartupException;
import java.util.concurrent.Executors;

public void initializeWebView(Context context) {
    // 1. Create the startup configuration specifying the background thread pool
    // to handle internal non-UI initialization processes.
    WebViewStartUpConfig startUpConfig = new WebViewStartUpConfig.Builder(
            Executors.newSingleThreadExecutor()
    ).build();

    // 2. Trigger WebView startup asynchronously
    WebViewCompat.startUpWebView(
            context,
            startUpConfig,
            new WebViewOutcomeReceiver<WebViewStartUpResult, WebViewStartupException>() {

                @Override
                public void onResult(@NonNull WebViewStartUpResult result) {
                    // Success: The WebView has finished its background initialization.
                    // This callback is invoked directly on the UI thread.
                    setupWebView();
                }

                @Override
                public void onError(@NonNull WebViewStartupException error) {
                    // Failure: Handled using the concrete WebViewStartupException
                    Log.e("WebViewStartup", "Failed to initialize WebView", error);
                }
            }
    );
}

تصحيح أخطاء بدء التشغيل غير المتزامن

إذا لم يؤدِّ استخدام startUpWebView إلى تحقيق مزايا الأداء المتوقّعة، يكون السبب غالبًا هو أنّه يتم تهيئة WebView ضمنيًا في مكان آخر في تطبيقك قبل تنفيذ طلبك. قد يرجع ذلك إلى الأسباب التالية:

  • المكتبات أو حِزم تطوير البرامج (SDK) التابعة لجهات خارجية التي تم إعدادها في وقت مبكر من دورة حياة التطبيق

  • ContentProviders يتم إدخالها في حزمة APK لتفعيل واجهات WebView API أثناء بدء تشغيل التطبيق.

  • عمليات تضخيم التنسيق أو طلبات برمجية (مثل جلب سلاسل وكيل المستخدم) التي تحدث في وقت مبكر بشكل غير متوقع

لمساعدتك في تحديد مكان حدوث عمليات التهيئة غير المتوقّعة هذه وسبب حدوثها، يوفّر العنصر WebViewStartUpResult إمكانات تدقيق مدمجة:

  • getUiThreadBlockingStartUpLocations(): تعرض هذه السمة قائمة بعناصر StartUpLocation تمثّل المواقع التي حظرت فيها مهام بدء تشغيل WebView سلسلة المحادثات الرئيسية لواجهة المستخدم.

  • getNonUiThreadBlockingStartUpLocations(): تعرض هذه السمة المواقع الإلكترونية المحدّدة التي حظرت فيها مهام بدء التشغيل سلاسل الخلفية.

يحتوي كل StartUpLocation على تتبُّع تسلسل استدعاء الدوال البرمجية يمكنك تسجيله أو فحصه للعثور على الفئة والدالة البرمجية المحدّدتَين اللتَين أدّتا إلى بدء عملية التهيئة.

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

يمكنك فحص هذه المواقع الجغرافية داخل معاودة الاتصال onResult لتدقيق مسار بدء التشغيل:

override fun onResult(result: WebViewStartUpResult) {
    // Check if WebView startup was blocked on the UI thread prior to or during initialization
    val uiBlockingLocations = result.getUiThreadBlockingStartUpLocations()
    if (!uiBlockingLocations.isNullOrEmpty()) {
        for (location in uiBlockingLocations) {
            // Log the stack trace of the call site that triggered the UI-blocking startup
            Log.w("WebViewDebug", "WebView startup blocked the UI thread here:", location.getStack())
        }
    } else {
        Log.i("WebViewDebug", "Excellent! No UI-blocking WebView startup detected.")
    }

    // Check where background initialization tasks were executed
    val backgroundLocations = result.getNonUiThreadBlockingStartUpLocations()
    backgroundLocations?.forEach { location ->
        Log.d("WebViewDebug", "WebView background startup occurred at: ${location.getStack()}")
    }

    setupWebView()
}

كيفية استخدام هذه البيانات أثناء التدقيق

عند تدقيق عملية بدء تشغيل WebView في تطبيقك، استخدِم الاستراتيجيات التالية لتحليل بيانات التشخيص ومعالجة المشاكل التي تؤدي إلى بطء الأداء:

  • البحث عن عمليات تتبُّع تسلسل استدعاء الدوال البرمجية غير متوقّعة: إذا لم يكن getUiThreadBlockingStartUpLocations() فارغًا، اطّلِع على عمليات تتبُّع تسلسل استدعاء الدوال البرمجية المطبوعة. إذا ظهرت لك فئات تابعة لحِزم تطوير برامج (SDK) تابعة لجهات خارجية أو مكونات غير متوقّعة، فهذا يعني أنّك عثرت على مشكلة في الأداء ناتجة عن عملية الإعداد الضمني.

  • التحقّق من ترتيب طلبات البيانات: إذا كانت نتائج سجلّك توضّح أنّه تم إجراء عملية تهيئة ضمنية قبل طلب startUpWebView اليدوي، عليك نقل عملية تهيئة startUpWebView إلى موضع سابق في تطبيقك أو ضبط حزمة SDK المخالفة لتأخير المهام التي تعتمد على WebView.

نقل البيانات من الحلول السابقة

في السابق، ربما استخدمت حلولاً بديلة صريحة لفرض تهيئة WebView على سلسلة الخلفية، مثل جلب سلسلة وكيل المستخدم.

تُعدّ هذه الحلول البديلة من الممارسات غير المتوافقة، وقد يتغيّر السلوك الأساسي لها في الإصدارات القادمة. إذا كان تطبيقك يعتمد على أي حلول بديلة صريحة وغير موثّقة لتشغيل WebView أو إدارته، ننصحك باستخدام واجهة برمجة التطبيقات startUpWebView بدلاً من ذلك. تعمل واجهة برمجة التطبيقات startUpWebView على جميع إصدارات Android وWebView المتوافقة مع مكتبة Jetpack Webkit.

ضمان مرونة التطبيق واستقراره

يساعد استخدام تنفيذ Jetpack Webkit في ضمان سلوك متّسق في جميع أنحاء منظومة Android المتكاملة. من المزايا الرئيسية لواجهة برمجة التطبيقات هذه قدرتها على التكيّف، إذ إنّها تحافظ على مستوى الأداء نفسه الذي تحقّقه الحلول اليدوية على الأجهزة القديمة التي لا تتوفّر فيها التحسينات الأحدث. يتيح لك ذلك الاستفادة من مزايا بدء التشغيل الحديثة على الأجهزة الجديدة بدون التأثير سلبًا في أداء الأجهزة القديمة.

في حين أنّ تحسين عملية بدء تشغيل WebView يقلّل من مخاطر حدوث أخطاء ANR أثناء تشغيل التطبيق، عليك أيضًا حماية تطبيقك من الأعطال التي تحدث في وقت التشغيل في عارض المحتوى ومن استعادة الذاكرة في النظام. للحفاظ على استقرار التطبيق بشكل شامل بعد تشغيل WebView، يُرجى الاطّلاع على التعامل مع إنهاء WebView. بالإضافة إلى ذلك، احرص على الحفاظ على حالة المستخدم عند إيقاف العملية نهائيًا في الخلفية من خلال استخدام WebView.saveState() واتّباع ممارسات آمنة للذاكرة في إدارة حالة WebView بكفاءة.

إذا واجهت مشاكل أو كانت لديك ملاحظات حول واجهة برمجة التطبيقات startUpWebView، يمكنك الإبلاغ عن خطأ في أداة تتبُّع الأخطاء المتاحة للجميع.