WebView での投機的読み込み

ナビゲーション レイテンシは、ユーザー エクスペリエンスの重要な指標です。デベロッパーがこのレイテンシを短縮できるように、WebView には投機的読み込みと接続の最適化のための API が用意されています。これにより、ユーザーが明示的にコンテンツに移動する前に、アプリでコンテンツを取得またはレンダリングできます。

WebView は、プロトコル ネゴシエーションを最適化するための QUIC ヒントとともに、preconnect、prefetch、prerender の 3 つの主要な投機的読み込みタイプをサポートしています。

投機的読み込み戦略を実装すると、次のことが可能になります。

  • ウェブ コンテンツの読み込みレイテンシを大幅に短縮: ネットワークの開始時間をアプリのライフサイクルの早い段階に移動し、HTTP/3 などの高速なプロトコルを優先します。
  • ナビゲーションの成功率の向上: ネットワークとキャッシュを事前にウォームアップすることで、一時的なネットワークの問題によるナビゲーションの失敗を減らすことができます。
  • 応答性の向上: 特に事前レンダリングにより、アプリの速度が大幅に向上したように感じられるインスタント トランジションが可能になります。

投機的読み込み戦略を選択する

これらの戦略の主な違いはスコープです。preconnect ヒント API と QUIC ヒント API はオリジンベースであるため、ターゲット ドメインのみが必要です。プリフェッチ API とプリレンダリング API は URL ベースであるため、正確なウェブページ パスが必要です。

Preconnect ヒントと QUIC ヒントはオリジン レベルで動作するため、アプリのライフサイクルのかなり早い段階で、ユーザーが移動する特定のコンテンツやページがわからなくても、これらを起動できます。

次の表は、これらの 3 つの戦略を比較したものです。ユースケースに適した戦略を選択する際に役立ちます。

機能 事前接続 Prefetch Prerender
最終目標 接続をウォームアップする 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、メモリ、ネットワーク)
使用場面 ターゲット オリジンはわかっているが、特定の URL がまだ決定されていない場合。 正確な URL がわかっていて、ナビゲーションが想定される場合。WebView 間でキャッシュが共有されます。 特定の WebView 内で正確な URL がわかっており、ナビゲーションが確実に行われる場合。
メリット オリジンの任意の URL の接続設定を高速化 一致した URL のネットワーク読み込みを高速化 有効化後すぐにナビゲーションを開始

オリジンに事前接続する

プリコネクトは、指定されたオリジンに対して DNS ルックアップと TCP/TLS または QUIC ハンドシェイクを事前に実行することで、今後の読み込みを高速化します。

正確な宛先 URL を必要とする Prefetch や Prerender とは異なり、Preconnect は厳密にオリジンベースです。これにより、プリフェッチやプリレンダリングよりもずっと早く Preconnect 呼び出しを行うことができます。

このリソース消費量の少ないプロファイル レベルの戦略により、オリジンがまだアクセスされていない場合、そのプロファイルを共有する WebView の初期レイテンシが短縮されます。接続は約 30 秒間開いたままになり、ハンドシェイクのオーバーヘッドがなくなるため、後続のクロスオリジン HTTP リクエスト、ナビゲーション、サブリソースにメリットがあります。

実装

事前接続を開始するには、Profile インスタンスで preconnect(String url) を呼び出します。この API は UI スレッドで呼び出す必要があり、WebViewFeature.PRECONNECT がサポートされている必要があります。

この API はオリジンで動作しますが、便宜上、完全な URL(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
}

QUIC ヒントで QUIC プロトコルのサポートを示す

HTTP/3(QUIC トランスポート プロトコル上で実行)は、0-RTT ハンドシェイク、接続の復元力の向上、パケットロス時のヘッドオブライン ブロッキングの解消など、HTTP/2 よりもレイテンシが大幅に改善されています。

デフォルトでは、WebView は、オリジンが QUIC をサポートしていることを示す情報(以前のインタラクションからの Alt-Svc ヘッダーや DNS HTTPS レコードなど)がある場合にのみ QUIC 接続を試みます。この事前知識がない場合、WebView は最初の接続に HTTP/2 または HTTP/1.1 を使用します。

addQuicHints を呼び出すと、このプロトコル サポート情報が事前に入力されるため、WebView は指定されたオリジンへの最初の接続ですぐに QUIC を使用して接続できます。

QUIC ヒントによる事前接続

preconnect ヒントと QUIC ヒントはどちらも Profile のオリジン レベルの最適化ですが、それぞれ異なる補完的な役割を果たします。

  • preconnect: ネットワーク接続(DNS ルックアップと TCP/TLS または QUIC ハンドシェイク)を約 30 秒間アクティブに開き、維持します。アクティブなネットワーク接続を開いたままにするため、デバイスとネットワークのリソースを消費します。このため、高い確率でターゲット オリジンになる場合にのみ予約する必要があります。
  • addQuicHints: ネットワーク トラフィックは発生しません。Profile ネットワーク スタックのインメモリ サーバー プロパティを更新して、プロトコルのサポートを記録します。オーバーヘッドはごくわずかなため、アプリの起動時に既知の HTTP/3 対応オリジンすべてに対して QUIC ヒントを安全に構成できます。

最適なパフォーマンスを得るには、preconnectprefetchUrlAsync、または loadUrl を呼び出す前に addQuicHints を呼び出します。これにより、後続のプリコネクトやページ リクエストは最初から HTTP/3 をネゴシエートします。

実装

QUIC ヒントを構成するには、Profile インスタンスで addQuicHints(Set<String> urls) を呼び出します。この API は UI スレッドで呼び出し、WebView が WebViewFeature.ADD_QUIC_HINTS_V1 機能をサポートしていることを確認する必要があります。

preconnect と同様に、addQuicHints はオリジンで動作しますが、完全な URL(https://www.example.com/index.html など)を指定することもできます。その場合、URL は自動的にオリジン(https://www.example.com)に正規化されます。

このメソッドは加法的です。複数回呼び出すと、指定されたオリジンが Profile 全体でマージされます。

Kotlin

// Must be called on the @UiThread
@OptIn(Profile.ExperimentalAddQuicHints::class)
if (WebViewFeature.isFeatureSupported(WebViewFeature.ADD_QUIC_HINTS_V1)) {
    val quicOrigins = setOf(
        "https://www.example.com",
        "https://api.example.com"
    )
    profile.addQuicHints(quicOrigins)
    // WebView now prioritizes HTTP/3 over QUIC for connections to these origins
}

Java

// Must be called on the @UiThread
// Requires @Profile.ExperimentalAddQuicHints annotation or suppression
if (WebViewFeature.isFeatureSupported(WebViewFeature.ADD_QUIC_HINTS_V1)) {
    Set<String> quicOrigins = new HashSet<>(Arrays.asList(
        "https://www.example.com",
        "https://api.example.com"
    ));
    profile.addQuicHints(quicOrigins);
    // WebView now prioritizes HTTP/3 over QUIC for connections to these origins
}

共通構成: PrefetchParametersPrerenderParameters

プリフェッチとプリレンダリングの両方で、PrefetchParameters または PrerenderParameters を使用してリクエストをカスタマイズします。これらのクラスを使用すると、No-Vary-Search 構成など、URL マッチング用の追加のヘッダーとヒントを指定できます。

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

コンテンツのプリフェッチ

プリフェッチでは、URL のメイン HTML リソースをダウンロードして、プロファイルのネットワーク キャッシュに保存します。WebView では、Profile は Cookie、HTTP キャッシュ、サービス ワーカーなどのブラウザデータのコンテナとして機能します。プリフェッチはプロファイル レベルのオペレーションであるため、そのプロファイルに関連付けられている 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() コールバックがトリガーされるタイミングと方法を変更します。これはプリフェッチされたコンテンツが正常に使用されるかどうかに直接影響するため、2 段階のライフサイクルを理解することが重要です。

投機的フェーズとナビゲーション フェーズにおける 2 段階の WebView プリフェッチ インターセプトのライフサイクルを示す図。
図 1. WebView プリフェッチ リクエストとナビゲーションの 2 段階のインターセプト ライフサイクル。

1. 投機的フェーズ(プリフェッチ リクエスト)

prefetchUrlAsync() が呼び出されると、WebView はメインの HTML リソースをバックグラウンドでダウンロードします。このバックグラウンド リクエストでは shouldInterceptRequest() は完全にスキップされます。通常インターセプタ内で処理されるカスタム ロジック、認可トークン、ヘッダー挿入は、プリフェッチされた HTML リソースには適用されません。

2. ナビゲーション フェーズ(ユーザーの有効化)

アプリが明示的に URL に移動する場合(WebViewCompat.navigateloadUrl を使用するなど)、またはユーザーが一致するリンクをクリックする場合、WebView はプリフェッチされたキャッシュを使用できるかどうかを判断します。

  • メイン HTML の評価: この時点で、WebView はメイン HTML の shouldInterceptRequest() をトリガーします。プリフェッチ キャッシュからページを正常に配信するには、インターセプタが null を返す必要があります。カスタム WebResourceResponse を返すと、WebView はインターセプタを尊重し、プリフェッチ キャッシュを完全にバイパスします。

  • サブリソースの評価: プリフェッチされた HTML の使用が許可されると、ページのレンダリングを完了するために必要な後続のすべてのサブリソース(画像、スクリプト、CSS など)に対して shouldInterceptRequest() が通常どおりトリガーされます。

主な動作

次の運用上の特性と資格要件のチェックにより、WebView がプリフェッチ リクエストを開始して管理する方法が規定されます。

  • スレッド セーフティ: リクエストは任意のスレッドから開始できます。
  • 有効性: フェッチを開始する前に、WebView は次のことを確認して、リクエストが安全でコンテキストに適切であることを確認します。
    • 既存の Cookie: ユーザーのプライバシーを保護し、CSRF のような副作用を防ぐため、リクエストでサーバーの状態変更をトリガーする可能性のある特定の認証済み Cookie が必要な場合、WebView はプリフェッチをスキップする可能性があります。
    • Service Worker の存在: Service Worker がすでに URL のスコープを制御している場合、WebView は標準のネットワーク プリフェッチを開始するのではなく、Service Worker の fetch ハンドラに委任できます。
    • プロキシの可用性: WebView は、複雑なネットワーク構成で投機的リクエストが失敗しないように、現在のネットワーク パス(構成済みのプロキシを含む)が安定していることを確認します。
  • プリフェッチが(有効なパラメータを指定しても)開始に失敗する場合、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 は事前レンダリングされた URL をキャンセルします。
  • 許可されない API: JavaScript がバックグラウンド コンテキストで特定の API(音声再生、アラートなど)にアクセスしようとすると、プリレンダリングは直ちに終了します。
  • インスタンスの制限: WebView ごとに許可されるアクティブなプリレンダリング URL の数には上限があります。

URL の一致と No-Vary-Search(NVS)

WebView では、プリロードされたリソースが意図したナビゲーションでのみ提供されるように、信頼性の高いマッチング アルゴリズムが必要です。

完全一致と NVS 一致

デフォルトでは、プリフェッチとプリレンダリングには URL の完全一致が必要です。移動先の URL がプリロードされた URL と同じ場合、キャッシュからすぐに提供されます。クエリ パラメータが異なる場合、WebView は次の No-Vary-Search(NVS)ルールを使用します。

  • ヒント: デベロッパーは、開始時に setExpectedNoVarySearchHeader() ヒントを提供します。ナビゲートされた URL が、ヒント付きパラメータを除いたリクエスト URL と一致する場合、WebView はサーバーの実際のヘッダーを待つために一時的にブロックされます。
  • サーバー ヘッダー: サーバーからの NVS レスポンス ヘッダーが最終的な権限です。サーバーがクエリの違いを無視すべきだと確認した場合、一致はキャッシュから提供されます。そうでない場合、WebView はコールド ネットワーク ロードにフォールバックします。

No-Vary-Search(NVS)は高度な使用を想定したものであり、ほとんどのデベロッパーはプリフェッチとナビゲーションの両方にまったく同じ URL(WebViewCompat.navigate または loadUrl)を渡すため、NVS を必要としない可能性があります。このガイダンスは、プリフェッチ URL とナビゲーション URL の間でクエリ パラメータに違いがある場合にのみ必要です。

グローバル構成

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

エラー処理と例外

投機的オペレーションは、OutcomeReceiverCompat または PrerenderOperationCallback を使用して結果を報告します。

主な例外

投機的読み込みオペレーションが失敗すると、エラー ハンドラは次のいずれかのプライマリ例外タイプを報告し、特定の失敗シナリオの診断に役立てます。

  • PrefetchException: すべての非同期プリフェッチ エラーの基本クラス。
  • PrefetchNetworkException: ネットワークまたはサーバーレベルの障害を示します。サーバーサイドの問題の診断に役立つ httpStatusCode フィールド(404 や 503 など)を含めることができます。
  • PrerenderException: メモリ不足や、バックグラウンドでの許可されていない API(音声再生など)の使用による失敗など、プリレンダリング関連のすべてのエラーのスーパー クラス。

最適化戦略

次の推奨事項に沿って、システム リソースを節約しながら投機的読み込みのメリットを最大限に活用してください。

  • 早期に開始: アプリの起動時、またはナビゲーションのデスティネーションが予測されるとすぐにプリフェッチを開始します。
  • QUIC ヒントと接続の事前ウォーミングを組み合わせる: preconnect()prefetchUrlAsync()、または標準のナビゲーションを開始する前に addQuicHints() を呼び出し、WebView が HTTP/3 を使用して接続を確立しようとすることを保証します。
  • 統合戦略: プリフェッチ キャッシュにすでに存在する URL をプリレンダリングする場合、プリレンダリング ナビゲーションはそのキャッシュから提供されるため、冗長なネットワーク リクエストを回避できます。
  • 割り当てをモニタリングする: プリレンダリングはリソースを大量に消費します。複数の候補に対してプリフェッチを優先し、最も可能性の高いナビゲーションに対してプリレンダリングを予約します。
  • スキームのサポート: すべての URL で必須の HTTPS スキームが使用されていることを確認します。無効なスキームまたは null 入力は、同期 IllegalArgumentException をトリガーします。

参考情報

ウェブアプリのデバッグ、WebView の起動パフォーマンスの最適化、レンダラ プロセスの終了の処理について詳しくは、以下のリソースをご覧ください。