بهینه‌سازی راه‌اندازی «وب‌نما»

وقتی برنامه‌تان برای اولین‌بار از WebView استفاده می‌کند، سیستم کارهای راه‌اندازی خاصی را انجام می‌دهد. این فرایند راه‌اندازی سنگین است. به‌طور پیش‌فرض، این اتفاق به‌طور ضمنی در رشته میانای کاربر در اولین باری که برنامه بسیاری از APIها را در بسته‌های android.webkit یا androidx.webkit فراخوانی می‌کند، یا طرح‌بندی‌ای را که حاوی برچسب «وب‌نما» است گسترش می‌دهد، رخ می‌دهد.

چرا این موضوع اهمیت دارد

چون این راه‌اندازی ضمنی کاملاً در رشته اصلی اتفاق می‌افتد، برنامه شما را از پردازش ورودی کاربر مسدود می‌کند و خطر خطاهای «برنامه پاسخ نمی‌دهد» (ANR) را به‌شدت افزایش می‌دهد. برای کسب اطلاعات بیشتر درباره نحوه مدیریت مدل اجرای تک‌رشته‌ای توسط Android، به نمای کلی فرایندها و رشته‌ها مراجعه کنید.

راه‌اندازهای راه‌اندازی ضمنی

راه‌اندازی ضمنی می‌تواند به روش‌های زیر راه‌اندازی شود:

  • برنامه‌نویسی: فراخوانی میاناهای برنامه‌سازی کاربردی مانند WebSettings.getUserAgentString().
  • استفاده از چیدمان‌ها: فراخوانی setContentView() یا layoutInflater.inflate() در منبع XML که شامل <WebView> است.

راه‌اندازی ضمنی همچنین می‌تواند بر سنجه‌های کسب‌وکار شما، مانند زمان راه‌اندازی برنامه و زمان لازم برای نمایش اولیه، تأثیر منفی بگذارد. اگر مقداردهی اولیه ضمنی برای برنامه‌تان بهینه نیست، به‌جای آن از startUpWebView استفاده کنید.

این صفحه درباره نحوه بهینه‌سازی عملکرد راه‌اندازی «وب‌نما» بااستفاده از startUpWebView API بحث می‌کند.

کنترل راه‌اندازی «وب‌نما» را در دست بگیرید

برای بهبود عملکرد و به‌حداقل رساندن خطاهای ANR، از startUpWebView API موجود در کتابخانه Jetpack Webkit استفاده کنید. این میانای برنامه‌سازی کاربردی به شما کنترل صریح می‌دهد که WebView چه زمانی راه‌اندازی شود. این کار بخش قابل‌توجهی از حجم کاری راه‌اندازی را به یک رشته پس‌زمینه منتقل می‌کند و امکان می‌دهد هر کاری که باید در رشته میانای کاربر انجام شود، به‌جای یک بلوک یکپارچه بزرگ، به‌صورت تکه‌تکه انجام شود. این کار رشته میانای کاربر را آزاد می‌کند تا بتواند کارهای مهم دیگر برنامه را به‌طور موازی انجام دهد و احتمال مسدود شدن تجربه کاربری را کاهش می‌دهد.

این میانای برنامه‌سازی کاربردی از فراخوان برگشتی androidx.webkit.WebViewOutcomeReceiver استفاده می‌کند و به شما امکان می‌دهد راه‌اندازی‌های موفق را پیگیری کنید.

برای استفاده از این میانای برنامه‌سازی کاربردی، کتابخانه Jetpack Webkit را به فایل build.gradle خود اضافه کنید. مطمئن شوید از نسخه ۱.۱۶.۰ یا بالاتر استفاده می‌کنید:

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

استفاده از میانای برنامه‌سازی کاربردی startUpWebView

نحوه بهینه‌سازی جریان راه‌اندازی به این بستگی دارد که برنامه‌تان واقعاً چه زمانی باید «وب‌نما» را نمایش دهد.

وقتی WebView در مسیر اصلی نیست

اگر برنامه‌تان نیازی به بار کردن فوری «وب‌نما» ندارد، می‌توانید هزینه مقداردهی اولیه را کاملاً پنهان کنید. startUpWebView را در اوایل چرخه حیات برنامه‌تان فراخوانی کنید و منتظر بمانید تا فراخوانی موفقیت‌آمیز اجرا شود.

در حالت ایده‌آل، باید قبل‌از فراخوانی دیگر WebView APIها، منتظر فراخوانی برگشتی باشید. اگر startUpWebView را راه‌اندازی کنید اما قبل‌از لمس کردن دیگر مؤلفه‌های WebView منتظر تکمیل آن نمانید، سیستم رشته میانای کاربر را تا زمان تکمیل مقداردهی اولیه مسدود می‌کند. ممکن است برنامه‌تان از کار پس‌زمینه‌ای که قبلاً تکمیل شده است، کمی بهبود عملکرد داشته باشد، اما نه حداکثر بهبود.

وقتی «وب‌نما» در مسیر اصلی باشد

اگر مسیر کاربر اصلی برنامه‌تان فوراً به «وب‌نما» نیاز دارد، احتمالاً نمی‌توانید منتظر بمانید تا راه‌اندازی «وب‌نما» تکمیل شود. در این سناریو، همچنان باید startUpWebView را در اسرع وقت در چرخه حیات برنامه (مثلاً در Application.onCreate) فراخوانی کنید، اما منتظر راه‌اندازی فراخوانی برگشتی نمانید. به‌جای آن، درصورت نیاز مستقیماً از میاناهای برنامه‌سازی کاربردی WebView استفاده کنید.

برای بهره‌مندی حداکثری از راه‌اندازی ناهمگام، بسیار مهم است که نمونه‌سازی WebView یا فراخوانی WebView API را تا زمانی که هیچ عملیات رشته میانای کاربر مسیر بحرانی دیگری برای اجرا باقی نمانده باشد (مانند گسترش سلسله‌مراتب چیدمان، مقداردهی اولیه سایر SDKها، یا ترسیم قاب اولیه) به‌تعویق بیندازید.

اگر startUpWebView را فراخوانی کنید و بلافاصله پس‌از آن WebView API را در رشته اصلی فراخوانی کنید، رشته میانای کاربر مسدود می‌شود و منتظر می‌ماند تا مقداردهی اولیه انجام شود. در این سناریو، هیچ مزیت عملکردی وجود ندارد.

اگر استفاده از WebView می‌تواند در مسیر بحرانی قرار گیرد اما نمی‌خواهید WebView را به‌طور کامل راه‌اندازی کنید، می‌توانید انتخاب کنید که وظایف راه‌اندازی WebView را به‌طور انتخابی اجرا کنید که قادر به اجرا در رشته پس‌زمینه هستند و رشته میانای کاربر را برای سایر وظایف بحرانی برنامه آزاد می‌کنند. برای این کار، می‌توانید از shouldRunUiThreadStartUpTasks(false) استفاده کنید.

بعداً در چرخه حیات برنامه‌تان، می‌توانید دوباره startUpWebView را با shouldRunUiThreadStartUpTasks(true) فراخوانی کنید تا تکالیف راه‌اندازی باقی‌مانده را در رشته میانای کاربر تکمیل کنید. اینکه در آن نقطه منتظر تماس برگشتی بمانید یا نه بستگی به این دارد که آیا استفاده از WebView در مسیر بحرانی قرار دارد یا نه.

نمونه پیاده‌سازی

این میانای برنامه‌سازی کاربردی از فراخوانی برگشتی androidx.webkit.WebViewOutcomeReceiver استفاده می‌کند و به شما امکان می‌دهد راه‌اندازی‌های موفق را ردیابی کنید یا به خرابی‌های عیب‌یابی رسیدگی کنید.

فراخوانی چندباره startUpWebView از بخش‌های مختلف برنامه ایمن است. توصیه می‌کنیم از پیاده‌سازی حلقه تکرار ساده خودداری کنید.

نمونه کد زیر نحوه استفاده از WebViewCompat.startUpWebView API را برای مقداردهی اولیه ناهمگام نشان می‌دهد.

کاتلین

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

جاوا

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 به‌طور ضمنی در جای دیگری از برنامه شما قبل‌از اجرای فراخوانی شما مقداردهی اولیه می‌شود. این مشکل ممکن است به دلایل زیر باشد:

  • کتابخانه‌های طرف سوم یا کیت‌های توسعه نرم‌افزار که در اوایل چرخه حیات برنامه مقداردهی اولیه شده‌اند.

  • 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() خالی نیست، به ردیابی‌های پشته چاپ‌شده نگاه کنید. اگر کلاس‌های متعلق به کیت‌های توسعه نرم‌افزار طرف سوم یا مؤلفه‌های غیرمنتظره را می‌بینید، یک گلوگاه مقداردهی اولیه ضمنی پیدا کرده‌اید.

  • درستی‌سنجی ترتیب فراخوانی: اگر خروجی‌های گزارش شما نشان می‌دهد که مقداردهی اولیه ضمنی قبل‌از فراخوانی دستی startUpWebView شما رخ داده است، باید مقداردهی اولیه startUpWebView را در برنامه خود زودتر انجام دهید یا SDK متخلف را پیکربندی کنید تا کارهای وابسته به WebView خود را به‌تأخیر بیندازد.

انتقال از راه‌حل‌های قبلی

در گذشته، ممکن بود از راه‌حل‌های صریح برای اجبار مقداردهی اولیه WebView در رشته پس‌زمینه استفاده کنید، مانند واکشی رشته عامل کاربر.

این راه‌حل‌ها به‌عنوان روش‌های پشتیبانی‌نشده درنظر گرفته می‌شوند و رفتار اساسی آن‌ها می‌تواند در نسخه‌های آتی تغییر کند. اگر برنامه شما برای راه‌اندازی یا مدیریت راه‌اندازی WebView به راه‌حل‌های صریح و غیرمستند متکی است، توصیه می‌کنیم به‌جای آن از startUpWebView API استفاده کنید. ‫startUpWebView API در همه نسخه‌های Android و «وب‌نما» که توسط کتابخانه Jetpack Webkit پشتیبانی می‌شوند کار می‌کند.

اطمینان از انعطاف‌پذیری و پایداری برنامه

استفاده از پیاده‌سازی Jetpack Webkit کمک می‌کند تا رفتار یکپارچه در سراسر کل بوم‌سازگان Android تضمین شود. مزیت کلیدی این API انعطاف‌پذیری آن است: در دستگاه‌های قدیمی که بهینه‌سازی‌های جدیدتر دردسترس نیستند، API برابری عملکرد را با راه‌حل‌های دستی حفظ می‌کند. این کار به شما امکان می‌دهد از مزایای راه‌اندازی مدرن در دستگاه‌های جدیدتر بهره‌مند شوید بدون اینکه عملکرد دستگاه‌های قدیمی‌تر تحت‌تأثیر قرار گیرد.

درحالی‌که بهینه‌سازی راه‌اندازی WebView خطر خطاهای ANR را درطول راه‌اندازی برنامه کاهش می‌دهد، باید از برنامه‌تان دربرابر ازکارافتادن‌های پردازنده زمان اجرا و بازیابی حافظه سیستم نیز محافظت کنید. برای حفظ پایداری جامع برنامه پس‌از اجرای «وب‌نما»، به رسیدگی به توقف «وب‌نما» مراجعه کنید. علاوه‌براین، بااستفاده از WebView.saveState() و دنبال کردن روال‌های مطلوب ایمن ازنظر حافظه در مدیریت کارآمد وضعیت «وب‌نما»، وضعیت کاربر را در سراسر مرگ فرایند پس‌زمینه‌ای حفظ کنید.

اگر با مشکلی مواجه شدید یا بازخوردی درباره startUpWebView API دارید، اشکالی را در ردیاب مشکل عمومی ثبت کنید.