WebViewCompat.navigate は WebView.loadUrl の強化版で、WebView でのウェブページの読み込み、履歴管理、ナビゲーション ライフサイクルのトラッキングをきめ細かく制御できます。
以前は、loadUrl を使用してページ ナビゲーションを開始する場合、次のような制限がありました。
- 履歴エントリの置き換えなし: 現在の履歴エントリを置き換えることができなかったため、バックスタックにエントリを追加せずに新しいページに移動することができませんでした。
- コールバックの分離: 特定の
loadUrl呼び出しとWebViewClientの後続のコールバック イベントを関連付ける直接的なメカニズムはありませんでした。 - 追加のヘッダーが保存されない:
loadUrlに渡されたカスタム ヘッダーがWebView状態の一部として保存されなかったため、状態の復元時に失われました。
WebViewCompat.navigate API は、次の機能を導入することでこれらの問題を解決します。
- ナビゲーション履歴エントリの置換:
WebView履歴スタック内の現在のページを置き換えることができます。 - 相関コールバック トラッキング: ナビゲーション ライフサイクルのすべてのステージで一意の識別子として機能する
Navigationオブジェクトを返します。 - 保存済み状態ヘッダーのサポート: 状態の復元時に再利用できるように、追加のヘッダーが
WebView状態バンドルに確実に保存されます。
主な機能と制限事項
WebViewCompat.navigate を採用する前に、次の運用ルールと制約を考慮してください。
スレッドの安全性: UI(メイン)スレッドで
WebViewCompat.navigateを呼び出す必要があります。キャンセルと優先順位: 進行中のナビゲーションを明示的にキャンセルすることはできません。ただし、同じ
WebViewで新しいnavigate呼び出しを開始すると、アクティブなナビゲーションはすべて置き換えられます。URI スキームのサポート: 標準(
https:やhttp:など)とカスタムの URI スキームがサポートされています。javascript:スキームはサポートされていません。URL サイズの上限: サポートされている URL 文字列の最大長は 2 MB です。
機能の確認: さまざまな WebView APK バージョン間の互換性を維持するため、API を呼び出す前に
WebViewFeature.isFeatureSupportedを使用して常に機能の可用性を確認してください。
ナビゲーションを開始してライフサイクルを追跡する
ナビゲーションを構成してライフサイクルを追跡するには、次の操作を行います。
WebViewのセットアップ中にWebViewCompat.addNavigationListenerを使用してNavigationListener実装を登録し、構造化されたライフサイクル コールバックを受け取ります。メモリリークとコールバックの重複実行を防ぐため、リスナーは 1 回だけ登録します(ナビゲーション呼び出しのたびに行うのではなく)。NavigationParameters.Builderを使用してNavigationParametersインスタンスを構築し、履歴の置換やカスタム HTTP ヘッダーなどのオプションの動作を指定します。WebViewCompat.navigateを呼び出し、WebViewインスタンス、リンク先 URL、パラメータを渡します。
WebViewCompat.navigate は、リクエストを一意に識別する Navigation オブジェクトを返します。NavigationListener コールバックで、このオブジェクトと受信した Navigation パラメータを比較して、特定のナビゲーションをトラッキングします。
実装例
次の例は、ナビゲーション パラメータを構成し、WebViewCompat.navigate を呼び出し、ナビゲーション ライフサイクル イベントをリッスンする方法を示しています。
Kotlin
class WebNavigationManager(private val webView: WebView) {
// Track the navigation instance returned by the API
private var currentNavigation: Navigation? = null
init {
// 1. Define listener to observe navigation lifecycle events
val listener = object : NavigationListener {
override fun onNavigationStarted(navigation: Navigation) {
if (navigation == currentNavigation) {
// Navigation started
}
}
override fun onNavigationRedirected(navigation: Navigation) {
if (navigation == currentNavigation) {
// Navigation encountered a redirect
}
}
override fun onNavigationCompleted(navigation: Navigation) {
if (navigation == currentNavigation) {
if (navigation.didCommit()) {
// Navigation committed successfully
} else if (navigation.didCommitErrorPage()) {
// Navigation committed an error page
val statusCode = navigation.statusCode
val error = navigation.webResourceError
}
}
}
override fun onFirstContentfulPaintMillis(page: Page, durationMillis: Long) {
// Match page with current navigation
if (page == currentNavigation?.page) {
// Page rendering started (First Contentful Paint achieved)
}
}
}
// 2. Register listener on the main thread
WebViewCompat.addNavigationListener(webView, listener)
}
@UiThread
fun navigateToPage(url: String) {
// Check feature availability
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
// Fall back to standard loadUrl if navigate API is unavailable
webView.loadUrl(url)
return
}
// 3. Configure navigation parameters
val params = NavigationParameters.Builder()
.setShouldReplaceCurrentEntry(true)
.addAdditionalHeaders(
mapOf("X-Test-Navigate-Header" to "TestValue")
)
.build()
// 4. Initiate navigation on the UI thread
currentNavigation = WebViewCompat.navigate(webView, url, params)
}
}
Java
public class WebNavigationManager {
private Navigation mCurrentNavigation;
private final WebView mWebView;
public WebNavigationManager(@NonNull WebView webView) {
mWebView = webView;
setupListener();
}
private void setupListener() {
// 1. Define listener to observe navigation lifecycle events
NavigationListener listener = new NavigationListener() {
@Override
public void onNavigationStarted(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
// Navigation started
}
}
@Override
public void onNavigationRedirected(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
// Navigation encountered a redirect
}
}
@Override
public void onNavigationCompleted(@NonNull Navigation navigation) {
if (navigation.equals(mCurrentNavigation)) {
if (navigation.didCommit()) {
// Navigation committed successfully
} else if (navigation.didCommitErrorPage()) {
// Navigation committed an error page
int statusCode = navigation.getStatusCode();
WebResourceErrorCompat error = navigation.getWebResourceError();
}
}
}
@Override
public void onFirstContentfulPaintMillis(@NonNull Page page, long durationMillis) {
if (mCurrentNavigation != null && page.equals(mCurrentNavigation.getPage())) {
// Page rendering started (First Contentful Paint achieved)
}
}
};
// 2. Register listener on the main thread
WebViewCompat.addNavigationListener(mWebView, listener);
}
@UiThread
public void navigateToPage(@NonNull String url) {
// Check feature availability
if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
// Fall back to standard loadUrl if navigate API is unavailable
mWebView.loadUrl(url);
return;
}
// 3. Configure navigation parameters
NavigationParameters params = new NavigationParameters.Builder()
.setShouldReplaceCurrentEntry(true)
.addAdditionalHeaders(Collections.singletonMap(
"X-Test-Navigate-Header", "TestValue"
))
.build();
// 4. Initiate navigation on the UI thread
mCurrentNavigation = WebViewCompat.navigate(mWebView, url, params);
}
}
障害モードとエラー処理
WebViewCompat.navigate API は、構成エラーとランタイム ナビゲーション エラーを処理するための個別のメカニズムを提供します。
無効な引数の例外
無効な引数を渡すと、同期 IllegalArgumentException がトリガーされます。一般的な原因は次のとおりです。
- 必須の非 null パラメータ(
webView、url、params)にnullを渡している。 - サポートされていない URL スキーム(
javascript:など)を指定している。 - RFC 2616 仕様に準拠していない形式の HTTP ヘッダーキーまたは値を渡す。
ナビゲーション プロセスのエラー
ネットワーク リクエストまたはページ読み込み中にエラー(HTTP 404 ステータス コード、DNS の解決の失敗、SSL エラーなど)が発生した場合でも、WebViewCompat.navigate は有効な Navigation オブジェクトを返します。
ナビゲーションが完了したら、onNavigationCompleted コールバック内の Navigation インスタンスで次のメソッドを調べて、障害を診断します。
getStatusCode: HTTP レスポンス ステータス コード(404、500など)を返します。getWebResourceError: 接続タイムアウトやホストルックアップの失敗などのネットワーク エラーの詳細を示すWebResourceErrorCompatオブジェクトを返します。didCommitErrorPage:WebViewがコミットされ、ユーザーにエラーページが表示されたかどうかを示します。didCommit: ナビゲーションが中止されずにターゲット ページに正常にコミットされたかどうかを示します。
状態バンドルの保存の管理
NavigationParameters で追加のヘッダーを渡すと、WebView はこれらのヘッダーを保存された状態バンドルに保存し、状態の復元時に再利用できるようにします。ただし、ヘッダーのコレクションが大きいと、保存された状態 Bundle のサイズが大幅に増加する可能性があります。
Android の状態保存中に TransactionTooLargeException が発生しないようにバンドルサイズを制限する必要がある場合は、WebViewCompat.saveState を使用します。このメソッドを使用すると、バンドルの最大サイズの上限をバイト単位で設定し、必要に応じて履歴の進むアイテムを除外できます。
Kotlin
// Save state with a maximum bundle size limit (for example, 64 KB)
val maxSizeBytes = 64 * 1024
val includeForwardState = false
val outState = Bundle()
WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState)
Java
// Save state with a maximum bundle size limit (for example, 64 KB)
int maxSizeBytes = 64 * 1024;
boolean includeForwardState = false;
Bundle outState = new Bundle();
WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState);
結果として得られるバンドルは、標準の WebView.restoreState メソッドと互換性があります。
移行と実装に関する推奨事項
WebView でのナビゲーションのパフォーマンスと安定性を最適化するには、次の推奨事項に沿って操作してください。
loadUrlからnavigateに移行する: すべてのレガシーWebView.loadUrl呼び出しをWebViewCompat.navigateに移行します。これにより、履歴管理が統一され、ヘッダーが常に保存された状態の一部として保存されます。常に機能のサポートを確認する: API を呼び出す前に、
WebViewFeature.isFeatureSupportedでランタイム サポートを確認して、古い WebView バージョンから保護します。ナビゲーション インスタンスを関連付ける: 複数の
WebViewインスタンスを管理する際に、返されたNavigationオブジェクトを使用して、同時ナビゲーションを区別したり、コールバックをフィルタしたりします。初期化時にリスナーを 1 回登録する:
WebViewCompat.addNavigationListenerは既存のリスナーを置き換えるのではなく、リスナーを追加するため、WebViewの設定時にNavigationListenerを 1 回登録して、メモリリークと、後続のナビゲーションでのコールバックの重複実行を回避します。保存状態のサイズをモニタリングする: 大きなヘッダー ペイロードを渡す場合は、明示的なサイズ境界を持つ
WebViewCompat.saveStateを使用して、過剰な状態データの保存を回避します。
参考情報
埋め込みウェブ機能とパフォーマンスの最適化の詳細については、次のガイドをご覧ください。