WebView 中的推測載入

導覽延遲是評估使用者體驗的重要指標。為協助開發人員縮短延遲時間,WebView 提供推測載入的 API,讓應用程式在使用者明確瀏覽內容前,就能擷取或算繪內容。

WebView 支援三種主要推測載入類型:預先連線、預先擷取和預先算繪。

實作推測載入策略可達成下列目標:

  • 大幅縮短網頁內容載入延遲時間:將網路啟動時間移至應用程式生命週期的較早階段。
  • 提高瀏覽成功率:預先暖機網路和快取,可減少因暫時性網路問題導致瀏覽失敗的機率。
  • 提升回應速度:預先算繪功能可實現即時轉換,讓應用程式速度大幅提升。

選擇推測載入策略

這兩種策略的主要差異在於範圍:preconnect API 是以來源為準,因此只需要目標網域。預先擷取和預先算繪 API 是以網址為準,因此需要確切的網頁路徑。

由於預先連線是在來源層級運作,因此可以在應用程式生命週期中更早啟動,甚至在您知道使用者將前往的特定內容或頁面之前啟動。

下表比較這三種策略,協助您為自己的用途選擇合適的策略:

功能 預先連線 預先擷取 預先算繪
主要目標 暖身 僅快取 HTML (不含 JavaScript 或 CSS) 預先算繪整個頁面
範圍 設定檔層級 (在 WebView 之間共用) 設定檔層級 (在 WebView 之間共用) WebView 層級 (繫結至特定 WebView)
Jetpack WebKit API androidx.webkit.Profile androidx.webkit.Profile androidx.webkit.WebViewCompat
核心 API 方法 preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
設定 不適用 PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
資源使用情況 低 (網路) 中 (網路、記憶體) 高 (CPU、記憶體、網路)
使用時機 目標來源已知,但尚未確定特定網址。 當確切網址已知且可能進行導覽時,系統會跨 WebView 共用快取。 確切網址已知,且特定 WebView 內導覽高度確定時。
福利 為來源上的任何網址加快連線設定速度 加快相符網址的網路載入速度 啟用後即可立即導航

預先連結至來源

預先連線會預先執行指定來源的 DNS 查詢和 TCP/TLS 握手程序,加快日後的載入速度。

與需要確切到達網址的預先擷取和預先算繪不同,預先連線嚴格來說是以來源為準。因此,您可以在比預先擷取和預先算繪更早的時間點發出 Preconnect 呼叫。

這項低資源的設定檔層級策略可減少任何設定檔的 WebView 分享初始延遲時間,前提是尚未造訪來源。連線會保持開啟約 30 秒,藉此消除交握作業負擔,讓後續的跨來源 HTTP 要求、導覽或子資源受益。

導入作業

如要啟動預先連線,請在 Profile 例項上呼叫 preconnect(String url)。這個 API 必須在 UI 執行緒上呼叫,且需要支援 WebViewFeature.PRECONNECT

API 會在來源上運作,但為方便起見,您可以提供完整網址 (例如 https://www.example.com/index.html)。系統會自動將這視為對來源的呼叫 (例如 https://www.example.com)。您可以多次呼叫這個 API,連結多個來源。

Kotlin

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

Java

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

常見設定:PrefetchParametersPrerenderParameters

預先擷取和預先算繪都會使用 PrefetchParametersPrerenderParameters 自訂要求。這些類別可讓您提供網址比對的其他標頭和提示,例如 No-Vary-Search 設定

Kotlin

// 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()

Java

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();

預先擷取內容

預先擷取會下載網址的主要 HTML 資源,並儲存在設定檔的網路快取中。在 WebView 中,Profile 會做為瀏覽器資料的容器,包括 Cookie、HTTP 快取和 Service Worker。由於預先擷取是設定檔層級的作業,因此與該設定檔相關聯的任何 WebView 都可以運用快取回應。

導入作業

如要啟動預先擷取,請在 Profile 例項上呼叫 prefetchUrlAsync()。這項作業僅支援 HTTPS 配置。

Kotlin

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

Java

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 預先擷取攔截的兩階段生命週期。
圖 1:WebView 預先擷取要求和導覽的兩階段攔截生命週期。

1. 推測階段 (預先擷取要求)

系統叫用 prefetchUrlAsync() 時,WebView 會在背景下載主要 HTML 資源。系統會完全略過這項背景要求的 shouldInterceptRequest()。通常在攔截器內處理的任何自訂邏輯、授權權杖或標頭插入作業,都不會套用至預先擷取的 HTML 資源。

2. 導覽階段 (使用者啟用)

當應用程式明確導覽至網址 (例如使用 loadUrl()) 或使用者點選相符連結時,WebView 會判斷是否可以使用預先擷取的快取:

  • 主要 HTML 評估:WebView 會在此時觸發主要 HTML 的 shouldInterceptRequest()。如要從預先擷取快取成功提供網頁,攔截器必須傳回 null。如果您傳回自訂 WebResourceResponse,WebView 會遵守攔截器,並完全略過預先擷取快取。

  • 子資源評估:預先擷取的 HTML 獲得使用權後,shouldInterceptRequest() 會正常觸發,以取得完成網頁算繪所需的所有後續子資源 (例如圖片、指令碼和 CSS)。

主要行為

下列運作特性和資格檢查會控管 WebView 發起及管理預先擷取要求的方式:

  • 執行緒安全:要求可從任何執行緒啟動。
  • 資格條件:WebView 會先檢查下列項目,確保要求安全無虞且符合情境,再啟動擷取作業:
    • 現有 Cookie:為保護使用者隱私權及避免類似 CSRF 的副作用,如果要求需要特定的已驗證 Cookie,可能會在伺服器上觸發狀態變更,WebView 就可能會略過預先擷取。
    • Service Worker 是否存在:如果 Service Worker 已控管網址範圍,WebView 可以延後處理,改為使用 Service Worker 的擷取處理常式,而非啟動標準網路預先擷取。
    • Proxy 可用性:WebView 會驗證目前的網路路徑 (包括任何已設定的 Proxy) 是否穩定,避免在複雜的網路設定下,推測性要求失敗。
  • 如果預先擷取作業無法啟動 (即使參數有效),通常是因為 WebView 判斷背景要求可能會干擾使用者目前的工作階段或安全狀態。
  • 取消:使用 CancellationSignal 終止進行中的要求,並防止要求遭到快取。

預先算繪網頁

預先轉譯會建立隱藏的「網頁內容」,在背景完整轉譯網頁,包括執行指令碼和擷取子資源。預先算繪與預先擷取採用相同的底層基礎架構。如果應用程式啟動預先算繪,WebView 會先預先擷取回應,以提供預先算繪的導覽功能,避免多餘的網路活動。

導入作業

預先算繪是 WebView 執行個體層級的作業。從 UI 執行緒使用 WebViewCompat 呼叫 prerenderUrlAsync()

Kotlin

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

Java

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

預先擷取和預先算繪都是完全非同步的作業。prefetchUrlAsync() 可從任何執行緒呼叫,但 prerenderUrlAsync() 必須從 UI 執行緒啟動。

技術限制

為兼顧即時導覽和系統健康狀態,WebView 會強制執行下列執行階段限制:

  • 記憶體壓力:如果裝置的 RAM 不足,WebView 會取消預先算繪的網址。
  • 不允許的 API:如果 JavaScript 嘗試在背景環境中存取特定 API (例如音訊播放、快訊),系統會立即終止預先算繪。
  • 執行個體限制:每個 WebView 允許的有效預先算繪網址數量有限。

網址比對和 No-Vary-Search (NVS)

WebView 需要可靠的相符演算法,確保預先載入的資源只會用於預期的導覽。

完全比對與 NVS 比對

根據預設,預先擷取和預先算繪功能必須完全比對網址。如果導向的網址與預先載入的網址相同,系統會立即從快取提供該網址。如果查詢參數不同,WebView 會使用下列「不因搜尋而異」(NVS) 規則:

  • 提示:開發人員會在啟動期間提供 setExpectedNoVarySearchHeader() 提示。如果導覽的網址與要求網址 (扣除提示參數) 相符,WebView 會短暫封鎖,等待伺服器的實際標頭。
  • 伺服器標頭:伺服器傳回的 NVS 回應標頭是最終的授權單位。如果伺服器確認應忽略查詢差異,系統會從快取提供相符項目。如果沒有,WebView 會改用冷網路載入。

No-Vary-Search (NVS) 適用於進階用途,大多數開發人員可能不需要這項功能,因為他們會將完全相同的網址傳遞至預先擷取和 loadUrl()。如果預先擷取網址和 loadUrl() 網址的查詢參數不同,才需要遵循這項指引。

全域設定

在設定檔層級調整推測載入行為,方法是設定PrefetchCache限制和預先算繪上限。你也可以將自訂預先擷取限制重設為系統預設值:

Kotlin

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

Java

// 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);

錯誤處理和例外狀況

推測作業會使用 OutcomeReceiverCompatPrerenderOperationCallback 回報結果。

主要例外狀況

如果推測載入作業失敗,錯誤處理常式會回報下列其中一種主要例外狀況類型,協助您診斷特定失敗情境:

  • PrefetchException所有非同步預先擷取錯誤的基礎類別。
  • PrefetchNetworkException表示網路或伺服器層級發生故障。其中可能包含 httpStatusCode 欄位 (例如 404 或 503),有助於診斷伺服器端問題。
  • PrerenderException所有預先算繪相關錯誤的超類別,例如因記憶體壓力或在背景使用不允許的 API (如音訊播放) 而導致的失敗。

最佳化策略

請遵循下列建議,盡量發揮預測載入的優點,同時節省系統資源:

  • 提早啟動:在應用程式啟動期間或可能前往導覽目的地時,立即開始預先擷取。
  • 整合策略:如果您預先算繪預先擷取快取中已有的網址,系統會從該快取提供預先算繪的導覽,避免多餘的網路要求。
  • 監控配額:預先算繪會耗用大量資源。建議為幾個可能的候選項目預先擷取,並為最有可能的導覽保留預先算繪。
  • 支援的通訊協定:請確認所有網址都使用強制性 HTTPS 通訊協定。無效的配置或空值輸入會觸發同步 IllegalArgumentException

其他資源

如要進一步瞭解如何對網頁應用程式偵錯、最佳化 WebView 啟動效能,以及處理轉譯器程序終止問題,請參閱下列資源: