بارگذاری حدسی در وب ویو

تأخیر پیمایش یک معیار حیاتی برای تجربه کاربری است. برای کمک به توسعه‌دهندگان در کاهش این تأخیر، WebView رابط‌های برنامه‌نویسی کاربردی (API) را برای بارگذاری احتمالی (speculative loading) ارائه می‌دهد که به برنامه شما اجازه می‌دهد قبل از اینکه کاربر صریحاً به آن پیمایش کند، محتوا را دریافت یا رندر کند.

وب ویو از سه نوع اصلی بارگذاری گمانه‌زنی پشتیبانی می‌کند: پیش‌اتصال (Preconnect)، پیش‌واکشی (Prefetch) و پیش‌رندر (Prerender).

با پیاده‌سازی یک استراتژی بارگذاری احتمالی، می‌توانید به موارد زیر دست یابید:

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

یک استراتژی بارگذاری احتمالی انتخاب کنید

وجه تمایز کلیدی بین این استراتژی‌ها در دامنه آنهاست: Preconnect API مبتنی بر مبدا است، به این معنی که فقط به دامنه هدف نیاز دارد. Prefetch و Prerender APIها مبتنی بر URL هستند، به این معنی که به مسیر دقیق صفحه وب نیاز دارند.

از آنجا که Preconnect در سطح مبدا عمل می‌کند، می‌تواند خیلی زودتر در چرخه حیات برنامه آغاز شود، حتی قبل از اینکه شما محتوای خاص یا صفحه‌ای را که کاربر به آن هدایت خواهد شد، بدانید.

جدول زیر این سه استراتژی را با هم مقایسه می‌کند تا به شما در انتخاب استراتژی مناسب برای مورد استفاده‌تان کمک کند:

ویژگی پیش اتصال پیش واکشی پیش‌رندر
هدف اصلی اتصال را گرم کنید فقط HTML را کش کنید (بدون جاوا اسکریپت یا CSS) پیش رندر کل صفحه
محدوده سطح پروفایل (مشترک در سراسر WebViews) سطح پروفایل (مشترک در سراسر WebViews) سطح WebView (محدود به یک WebView خاص)
رابط برنامه‌نویسی کاربردی جت‌پک وب‌کیت androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
متدهای اصلی API preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
پیکربندی ناموجود PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
استفاده از منابع کم (شبکه) رسانه (شبکه، حافظه) بالا (پردازنده، حافظه، شبکه)
چه زمانی استفاده شود وقتی مبدا هدف مشخص است، اما URL خاص هنوز مشخص نشده است. وقتی آدرس اینترنتی (URL) دقیق مشخص باشد و ناوبری (navigation) محتمل باشد، و ذخیره‌سازی (caching) در سراسر WebViewها (وب‌ویوها) به اشتراک گذاشته شده باشد. وقتی آدرس اینترنتی (URL) دقیق مشخص باشد و پیمایش در یک WebView خاص کاملاً قطعی باشد.
مزایا راه‌اندازی سریع‌تر اتصال برای هر URL در مبدا بارگذاری سریع‌تر شبکه برای URLهای منطبق ناوبری واقعاً فوری پس از فعال‌سازی

اتصال اولیه به مبدا

پیش‌اتصال با انجام پیشگیرانه‌ی جستجوهای DNS و دست‌دهی‌های TCP/TLS برای یک مبدأ مشخص، سرعت بارگذاری‌های آینده را افزایش می‌دهد.

برخلاف Prefetch و Prerender که به یک URL مقصد دقیق نیاز دارند، Preconnect کاملاً مبتنی بر مبدا است. این به شما امکان می‌دهد فراخوانی Preconnect را خیلی زودتر از Prefetch و Prerender انجام دهید.

این استراتژی کم‌مصرف و در سطح پروفایل، تأخیر اولیه را برای هر WebView که آن پروفایل را به اشتراک می‌گذارد، کاهش می‌دهد، مشروط بر اینکه مبدا قبلاً بازدید نشده باشد. اتصال تقریباً به مدت 30 ثانیه باز می‌ماند و با حذف سربار handshake، به هرگونه درخواست HTTP متقابل بعدی، پیمایش‌ها یا زیرمنابع کمک می‌کند.

پیاده‌سازی

برای شروع یک پیش‌اتصال، تابع preconnect(String url) را روی یک نمونه Profile فراخوانی کنید. این API باید روی نخ UI فراخوانی شود و مستلزم پشتیبانی از WebViewFeature.PRECONNECT است.

این API روی مبدأ عمل می‌کند، اما برای راحتی، می‌توان یک URL کامل ارائه داد (مانند https://www.example.com/index.html ). این به طور خودکار به عنوان فراخوانی مبدأ در نظر گرفته می‌شود (برای مثال، https://www.example.com ). با فراخوانی چندین باره این API می‌توان به چندین مبدأ متصل شد.

کاتلین

// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
    profile.preconnect("https://www.example.com/index.html")
    // This initiates a connection to the origin https://www.example.com
}

جاوا

// Must be called on the @UiThread
if (WebViewFeature.isFeatureSupported(WebViewFeature.PRECONNECT)) {
    profile.preconnect("https://www.example.com/index.html");
    // This initiates a connection to the origin https://www.example.com
}

پیکربندی رایج: PrefetchParameters و PrerenderParameters

هر دو کلاس Prefetch و Prerender PrefetchParameters یا PrerenderParameters برای سفارشی‌سازی درخواست استفاده می‌کنند. این کلاس‌ها به شما امکان می‌دهند هدرها و نکات اضافی برای تطبیق URL، مانند پیکربندی‌های No-Vary-Search، ارائه دهید.

کاتلین

// Isolated configuration specifically for Cache-Level Prefetching
val prefetchParams = PrefetchParameters.Builder()
    .addAdditionalHeader("X-Custom-Client", "Android-App-V2")
    .setExpectedNoVarySearchHeader(
        NoVarySearchHeader.varyExcept(true, listOf("session_id", "click_ref"))
    )
    .build()

جاوا

PrefetchParameters prefetchParams = new PrefetchParameters.Builder()
    .addAdditionalHeader("X-Custom-Header", "value")
    /**
     * Hint to ignore specific query parameters during cache matching.
     * This allows the cache to match even if the tracking_id differs.
     */
    .setExpectedNoVarySearchHeader(
        NoVarySearchHeader.varyExcept(true, Arrays.asList("tracking_id"))
    )
    /**
     * Determines if Client Hints are sent.
     * NOTE: This is ignored for Prerendering API requests, which default to
     * the WebView's WebSettings.getJavaScriptEnabled() value.
     */
    .setJavaScriptEnabled(true)
    .build();

پیش‌واکشی محتوا

پیش واکشی (Prefetching) منبع اصلی HTML یک URL را دانلود کرده و آن را در حافظه پنهان شبکه پروفایل ذخیره می‌کند. در WebView، یک Profile به عنوان ظرفی برای داده‌های مرورگر، از جمله کوکی‌ها، حافظه پنهان HTTP و سرویس ورکرها عمل می‌کند. از آنجا که پیش واکشی یک عملیات در سطح پروفایل است، هر WebView مرتبط با آن پروفایل می‌تواند از پاسخ ذخیره شده استفاده کند.

پیاده‌سازی

برای شروع یک پیش‌واکشی، تابع prefetchUrlAsync() را روی یک نمونه‌ی Profile فراخوانی کنید. این عملیات فقط از طرح HTTPS پشتیبانی می‌کند.

کاتلین

profile.prefetchUrlAsync(
    url,
    prefetchParams,
    cancellationSignal,
    executor,
    object : WebViewOutcomeReceiver<PrefetchResult, PrefetchException> {
        override fun onResult(result: PrefetchResult) {
            if (result.wasDuplicate()) {
                // URL and No-Vary-Search permutations already exist in the cache layer
            } else {
                // The HTML payload has been successfully secured in the HTTP cache
            }
        }

        override fun onError(error: PrefetchException) {
            when (error) {
                is PrefetchNetworkException -> {
                    // Isolates network layer or server-side HTTP anomalies
                    val code = error.httpStatusCode
                    // Facilitates rapid diagnosis of 4xx or 5xx server responses
                }
                else -> {
                    // Catches generalized execution failures and system constraints
                }
            }
        }
    }
)

جاوا

profile.prefetchUrlAsync(
    url,
    prefetchParams,
    cancellationSignal,
    executor,
    new WebViewOutcomeReceiver<PrefetchResult, PrefetchException>() {
        @Override
        public void onResult(PrefetchResult result) {
            if (result.wasDuplicate()) {
                // URL and No-Vary-Search permutations already exist in the cache layer
            } else {
                // The HTML payload has been successfully secured in the HTTP cache
            }
        }

        @Override
        public void onError(PrefetchException error) {
            if (error instanceof PrefetchNetworkException) {
                // Isolates network layer or server-side HTTP anomalies
                int code = ((PrefetchNetworkException) error).httpStatusCode;
                // Facilitates rapid diagnosis of 4xx or 5xx server responses
            } else {
                // Catches generalized execution failures and system constraints
            }
        }
    }
);

چرخه حیات رهگیری

درخواست پیش‌واکشی WebView، زمان و نحوه‌ی فعال شدن تابع فراخوانی shouldInterceptRequest() را تغییر می‌دهد. از آنجا که این موضوع تأثیر مستقیمی بر استفاده‌ی موفقیت‌آمیز از محتوای پیش‌واکشی‌شده‌ی شما دارد، درک چرخه‌ی حیات دو مرحله‌ای آن بسیار مهم است:

نموداری که چرخه حیات رهگیری پیش‌واکشی دو مرحله‌ای WebView را در طول فازهای گمانه‌زن و ناوبری نشان می‌دهد.
شکل ۱. چرخه حیات رهگیری دو مرحله‌ای برای درخواست‌ها و پیمایش‌های پیش‌واکشی WebView.

۱. مرحله گمانه‌زنی (درخواست پیش‌واکشی)

وقتی prefetchUrlAsync() فراخوانی می‌شود، WebView منبع اصلی HTML را در پس‌زمینه دانلود می‌کند. shouldInterceptRequest() برای این درخواست پس‌زمینه کاملاً نادیده گرفته می‌شود. هرگونه منطق سفارشی، توکن‌های مجوز یا تزریق هدر که معمولاً درون interceptor شما مدیریت می‌شوند، روی منبع HTML از پیش واکشی شده اعمال نمی‌شوند.

۲. مرحله ناوبری (فعال‌سازی کاربر)

وقتی برنامه صریحاً به URL هدایت می‌شود (برای مثال، با استفاده از loadUrl() ) یا کاربر روی لینک منطبق کلیک می‌کند، WebView تعیین می‌کند که آیا می‌تواند از حافظه پنهان از پیش واکشی شده استفاده کند یا خیر:

  • ارزیابی HTML اصلی: WebView در حال حاضر shouldInterceptRequest() برای HTML اصلی فعال می‌کند. برای ارائه موفقیت‌آمیز صفحه از حافظه پنهان prefetch، interceptor شما باید null را برگرداند. اگر یک WebResourceResponse سفارشی را برگردانید، WebView interceptor شما را در نظر می‌گیرد و به طور کامل از حافظه پنهان prefetch عبور می‌کند.

  • ارزیابی زیرمنبع: پس از اینکه HTML پیش‌واکشی شده برای استفاده پاک شد، shouldInterceptRequest() به طور معمول برای تمام زیرمنابع بعدی (مانند تصاویر، اسکریپت‌ها و CSS) که برای تکمیل رندر صفحه مورد نیاز هستند، فعال می‌شود.

رفتارهای کلیدی

ویژگی‌های عملیاتی و بررسی‌های واجد شرایط بودن زیر، نحوه‌ی آغاز و مدیریت درخواست‌های پیش‌واکشی توسط WebView را تعیین می‌کنند:

  • ایمنی نخ: درخواست‌ها می‌توانند از هر نخی آغاز شوند.
  • واجد شرایط بودن: قبل از شروع واکشی، WebView با بررسی موارد زیر، از ایمن بودن و مناسب بودن درخواست از نظر محتوایی اطمینان حاصل می‌کند:
    • کوکی‌های موجود: برای محافظت از حریم خصوصی کاربر و جلوگیری از عوارض جانبی مشابه CSRF، اگر درخواست به کوکی‌های احراز هویت شده خاصی نیاز داشته باشد که می‌توانند باعث تغییر وضعیت در سرور شوند، WebView ممکن است از پیش‌واکشی صرف نظر کند.
    • حضور سرویس ورکر: اگر یک سرویس ورکر از قبل دامنه URL را کنترل می‌کند، WebView می‌تواند به جای شروع یک پیش‌واکشی استاندارد شبکه، به کنترل‌کننده واکشی سرویس ورکر مراجعه کند.
    • در دسترس بودن پروکسی: WebView تأیید می‌کند که مسیر شبکه فعلی (شامل هرگونه پروکسی پیکربندی‌شده) پایدار است تا از عدم موفقیت درخواست‌های احتمالی تحت پیکربندی‌های پیچیده شبکه جلوگیری شود.
  • اگر یک پیش‌واکشی (prefetch) شروع نشود (حتی با پارامترهای معتبر)، اغلب به این دلیل است که WebView تشخیص داده است که یک درخواست پس‌زمینه می‌تواند با جلسه فعلی کاربر یا وضعیت امنیتی او تداخل داشته باشد.
  • لغو: از CancellationSignal برای خاتمه دادن به یک درخواست در حین ارسال و جلوگیری از ذخیره شدن آن در حافظه پنهان استفاده کنید.

پیش‌پردازش صفحات

پیش‌رندرینگ، «محتوای وب» پنهانی را برای رندر کامل یک صفحه در پس‌زمینه ایجاد می‌کند، که شامل اجرای اسکریپت و واکشی منابع فرعی می‌شود. پیش‌رندرینگ به همان زیرساخت اساسی پیش‌رندرینگ متکی است. اگر یک برنامه پیش‌رندر را آغاز کند، WebView ابتدا پیش‌واکشی پاسخ را انجام می‌دهد تا ناوبری پیش‌رندر را ارائه دهد و از فعالیت شبکه اضافی جلوگیری کند.

پیاده‌سازی

پیش‌رندرینگ یک عملیات در سطح نمونه‌ی WebView است. با استفاده از WebViewCompat از نخ رابط کاربری، تابع prerenderUrlAsync() را فراخوانی کنید.

کاتلین

WebViewCompat.prerenderUrlAsync(
    webView,
    url,
    cancellationSignal,
    executor,
    params,
    object : PrerenderOperationCallback {
        override fun onPrerenderActivated() {
            // Called when the user navigates to the URL and the hidden page is swapped in
        }

        override fun onError(exception: Throwable) {
            // exception is an instance of PrerenderException
            // Handle prerender failure (for example, memory pressure or disallowed JavaScript APIs)
        }
    }
)

جاوا

WebViewCompat.prerenderUrlAsync(webView, url, cancellationSignal, executor, params, new PrerenderOperationCallback() {
    @Override
    public void onPrerenderActivated() {
        // Called when the user navigates to the URL and the hidden page is swapped in.
    }

    @Override
    public void onError(@NonNull Throwable exception) {
        // Handle prerender failure (for example, resource constraints or disallowed APIs).
    }
});

هر دو تابع prefetch و prerender کاملاً ناهمزمان هستند. prefetchUrlAsync() را می‌توان از هر نخی فراخوانی کرد، در حالی که prerenderUrlAsync() باید از نخ رابط کاربری (UI thread) فراخوانی شود.

محدودیت‌های فنی

برای ایجاد تعادل بین ناوبری فوری و سلامت سیستم، WebView محدودیت‌های زمان اجرای زیر را اعمال می‌کند:

  • فشار بر حافظه: اگر رم دستگاه کم باشد، وب ویو URLهای از پیش رندر شده را لغو می‌کند.
  • APIهای غیرمجاز: هرگونه تلاش جاوااسکریپت برای دسترسی به APIهای خاص (مثلاً پخش صدا، هشدارها) در یک زمینه پس‌زمینه، پیش‌رندر را فوراً خاتمه می‌دهد.
  • محدودیت نمونه: محدودیتی برای تعداد URLهای از پیش رندر شده فعال مجاز در هر WebView وجود دارد.

تطبیق URL و جستجوی بدون تغییر (NVS)

وب ویو به یک الگوریتم تطبیق قابل اعتماد نیاز دارد تا اطمینان حاصل شود که یک منبع از پیش بارگذاری شده فقط برای ناوبری مورد نظر خود ارائه می‌شود.

تطابق دقیق در مقابل تطابق NVS

به طور پیش‌فرض، پیش‌واکشی و پیش‌رندر نیاز به تطابق دقیق URL دارند. اگر URL پیمایش‌شده با URL از پیش بارگذاری‌شده یکسان باشد، بلافاصله از حافظه پنهان (cache) ارائه می‌شود. اگر پارامترهای پرس‌وجو متفاوت باشند، WebView از قوانین جستجوی بدون تغییر (NVS) زیر استفاده می‌کند:

  • نکته: توسعه‌دهندگان در هنگام شروع، یک نکته‌ی setExpectedNoVarySearchHeader() ارائه می‌دهند. اگر URL پیمایش‌شده با URL درخواست منهای پارامترهای اشاره‌شده مطابقت داشته باشد، WebView به‌طور خلاصه مسدود می‌شود تا منتظر هدرهای واقعی سرور بماند.
  • سرآیند سرور: سرآیند پاسخ NVS از سرور، مرجع نهایی است. اگر سرور تأیید کند که تفاوت‌های پرس‌وجو باید نادیده گرفته شوند، تطابق از حافظه پنهان ارائه می‌شود. در غیر این صورت، WebView به بارگذاری سرد شبکه بازمی‌گردد.

No-Vary-Search (NVS) برای کاربردهای پیشرفته است و اکثر توسعه‌دهندگان ممکن است به آن نیازی نداشته باشند زیرا آنها دقیقاً همان URL را به هر دو prefetch و loadUrl() ارسال می‌کنند. این راهنمایی فقط در صورتی ضروری است که تفاوت‌هایی در پارامترهای پرس‌وجو بین prefetch URL و loadUrl() URL وجود داشته باشد.

پیکربندی سراسری

با پیکربندی محدودیت‌های PrefetchCache و حداکثر پیش‌رندرها، رفتار بارگذاری احتمالی را در سطح پروفایل تنظیم کنید. همچنین می‌توانید محدودیت‌های پیش‌رندر سفارشی را به پیش‌فرض‌های سیستم برگردانید:

کاتلین

// Configure prefetch cache limits
profile.prefetchCache.setMaxPrefetches(10)
profile.prefetchCache.setPrefetchTtlSeconds(60)

// Reset to system defaults when needed
profile.prefetchCache.clearMaxPrefetches()

// Configure maximum active prerenders
profile.setMaxPrerenders(2)

جاوا

// Configure prefetch cache limits
PrefetchCache prefetchCache = profile.getPrefetchCache();
prefetchCache.setMaxPrefetches(10);
prefetchCache.setPrefetchTtlSeconds(60);

// Reset to system defaults when needed
prefetchCache.clearMaxPrefetches();

// Configure maximum active prerenders
profile.setMaxPrerenders(2);

مدیریت خطا و استثنائات

عملیات گمانه‌زنی از OutcomeReceiverCompat یا PrerenderOperationCallback برای گزارش نتایج استفاده می‌کنند.

استثنائات اولیه

وقتی یک عملیات بارگذاری گمانه‌زنی با شکست مواجه می‌شود، مدیریت‌کننده‌ی خطا یکی از انواع استثناهای اصلی زیر را گزارش می‌دهد تا به شما در تشخیص سناریوهای خاص شکست کمک کند:

  • PrefetchException : کلاس پایه برای همه خطاهای پیش واکشی ناهمزمان.
  • PrefetchNetworkException : نشان‌دهنده‌ی خرابی در سطح شبکه یا سرور است. می‌تواند شامل یک فیلد httpStatusCode (مانند ۴۰۴ یا ۵۰۳) باشد تا به تشخیص مشکلات سمت سرور کمک کند.
  • PrerenderException : سوپرکلاس برای همه خطاهای مربوط به پیش رندر، مانند خرابی‌های ناشی از فشار بر حافظه یا استفاده از APIهای غیرمجاز (مانند پخش صدا) در پس‌زمینه.

استراتژی‌های بهینه‌سازی

برای به حداکثر رساندن مزایای بارگذاری احتمالی و در عین حال حفظ منابع سیستم، این توصیه‌ها را دنبال کنید:

  • شروع زودهنگام: پیش‌واکشی را در حین راه‌اندازی برنامه یا به محض احتمال وجود یک مقصد ناوبری شروع کنید.
  • استراتژی یکپارچه: اگر یک URL را که از قبل در حافظه پنهان پیش واکشی (prefetch cache) قرار دارد، پیش‌رندر کنید، ناوبری پیش‌رندر از آن حافظه پنهان ارائه می‌شود و از درخواست‌های اضافی شبکه جلوگیری می‌شود.
  • سهمیه‌های نظارت: پیش‌رندر منابع زیادی مصرف می‌کند. پیش‌واکشی را برای چندین کاندید احتمالی ترجیح دهید و پیش‌رندر را برای محتمل‌ترین ناوبری نگه دارید.
  • پشتیبانی از طرحواره: اطمینان حاصل کنید که همه URLها از طرحواره اجباری HTTPS استفاده می‌کنند. طرحواره‌های نامعتبر یا ورودی‌های تهی باعث ایجاد یک IllegalArgumentException همزمان می‌شوند.

منابع اضافی

برای کسب اطلاعات بیشتر در مورد اشکال‌زدایی برنامه‌های وب، بهینه‌سازی عملکرد راه‌اندازی WebView و مدیریت خاتمه فرآیند رندر، به منابع زیر مراجعه کنید: