تحسين عملية بدء تشغيل 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 أثناء بدء تشغيل التطبيق.

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

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

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