WebView での投機的読み込み

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

WebView は、プロトコル ネゴシエーションを最適化するための QUIC ヒントとともに、投機的読み込みの 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
Core API メソッド preconnect(...) prefetchUrlAsync(...) prerenderUrlAsync(...)
構成 なし PrefetchCache.setMaxPrefetches()
PrefetchCache.setPrefetchTtlSeconds()
setMaxPrerenders()
リソースの使用量 低(ネットワーク) 中(ネットワーク、メモリ) 高(CPU、メモリ、ネットワーク)
使用場面 ターゲット オリジンはわかっているが、特定の URL はまだ決まっていない場合。 正確な URL がわかっていて、ナビゲーションが想定される場合。WebView 間でキャッシュが共有されます。 特定の WebView 内で正確な URL がわかっており、ナビゲーションが確実に行われる場合。
メリット オリジンの任意の URL の接続設定を高速化 一致した URL のネットワーク ロードを高速化 有効化後すぐにナビゲーションを開始

オリジンに事前接続する

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

正確な接続先 URL が必要なプリフェッチやプリレンダリングとは異なり、プリコネクトは厳密にオリジンベースです。これにより、正確な URL がわからなくても Preconnect を呼び出すことができます。呼び出すタイミングについては、実装のヒントをご覧ください。

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

実装

事前接続を開始するには、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 ヒントを安全に構成できます。

最適なパフォーマンスを得るには、preconnect、prefetchUrlAsync、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
}

共通構成: PrefetchParameters と PrerenderParameters

プリフェッチとプリレンダリングの両方で、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.navigate や loadUrl を使用するなど)、またはユーザーが一致するリンクをクリックする場合、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 の起動パフォーマンスの最適化、レンダラ プロセスの終了の処理について詳しくは、次のリソースをご覧ください。