このページでは、WebView 内のウェブ コンテンツとホスト Android アプリケーション間の通信を容易にするために、ネイティブ ブリッジ(JavaScript ブリッジとも呼ばれます)を確立するためのさまざまな方法とベスト プラクティスについて説明します。
これにより、ウェブ デベロッパーは JavaScript を使用して、標準のウェブ API では通常提供されないカメラ、ファイル システム、高度なハードウェア センサーなどのネイティブ プラットフォーム機能にアクセスできます。
ユースケース
JavaScript ブリッジの実装により、ウェブ コンテンツが Android オペレーティング システムへのより深いアクセスを必要とするさまざまな統合シナリオが可能になります。以下に、いくつかの例を示します。
- プラットフォーム統合: ウェブページからネイティブの Android UI コンポーネント(生体認証プロンプト、
BottomSheetDialogなど)をトリガーします。 - パフォーマンス: 重い計算タスクをネイティブの Java または Kotlin コードにオフロードします。
- データの永続性: ローカルの暗号化されたデータベースまたは共有設定にアクセスします。
- 大規模なデータ転送: アプリとウェブ レンダラの間でメディア ファイルや複雑なデータ構造を渡す。
コミュニケーション メカニズム
Android には、ネイティブ ブリッジを確立するための 3 つの主要な世代の API が用意されています。いずれも引き続き利用できますが、セキュリティ、ユーザビリティ、パフォーマンスは大きく異なります。
addWebMessageListener を使用する(推奨)
addWebMessageListener は、ウェブ コンテンツとネイティブ アプリコード間の通信に最も適した最新のアプローチです。JavaScript インターフェースの使いやすさとメッセージング システムのセキュリティを組み合わせたものです。
仕組み: アプリは、特定の名前と一連の許可されたオリジンルールのリスナーを追加します。WebView は、ページの読み込みが開始された瞬間から、JavaScript オブジェクトがグローバル スコープ(window.objectName)に存在するようにします。
初期化: スクリプトが実行される前に WebView が JavaScript オブジェクトを挿入するようにするには、ページに移動する前に(WebViewCompat.navigate や loadUrl を呼び出すなど)、addWebMessageListener を呼び出す必要があります。
主な機能:
セキュリティと信頼性: 以前の API とは異なり、このメソッドでは初期化時に
allowedOriginRulesのSet<String>が必要です。これは、信頼を確立するための主要なメカニズムです。https://example.comなどの信頼できるオリジンを指定すると、WebView は、挿入された JavaScript オブジェクトをそのオリジンから読み込まれたウェブページにのみ公開することを保証します。ネイティブ リスナー コールバックは、すべてのメッセージで
sourceOriginパラメータを受け取ります。ブリッジが複数の許可されたオリジンをサポートしている場合、これを使用して送信者の正確なオリジンを確認できます。WebView はプラットフォーム レベルでこれらのオリジン チェックを厳密に実施するため、アプリは一般的に、信頼できる
sourceOriginから受信したメッセージを信頼できます。これにより、ほとんどの標準実装で厳格なペイロード検証を行う必要がなくなります。- WebView は、スキーム(HTTP/HTTPS)、ホスト、ポートに対してルールを照合します。
- WebView はパスを無視します。たとえば、
https://example.comはhttps://example.com/loginとhttps://example.com/homeを許可します。 - WebView では、サブドメインのホストの先頭にワイルドカードを厳密に制限しています。たとえば、
https://*.example.comはhttps://foo.example.comと一致しますが、https://example.comとは一致しません。https://example.comとそのサブドメインの両方を照合する必要がある場合は、各オリジンルールを個別に許可リストに追加する必要があります(例:"https://example.com", "https://*.example.com")。スキームやドメインの中間でワイルドカードを使用することはできません。
これにより、ブリッジが検証済みのドメインに制限され、不正な第三者のコンテンツや挿入された iframe がネイティブ コードを実行するのを防ぐことができます。
マルチフレーム サポート: オリジン ルールに一致するすべてのフレームで動作します。
スレッド処理: リスナー コールバックは、アプリのメイン(UI)スレッドで実行されます。ブリッジで複雑なデータ処理、JSON 解析、データベース ルックアップを処理する必要がある場合は、その処理をバックグラウンド スレッドにオフロードして、アプリケーション UI が ANR(アプリケーション応答なし)エラーでフリーズしないようにする必要があります。
双方向: ウェブページがメッセージを送信すると、アプリは を受け取ります。この を使用して、特定のフレームにメッセージを送信し返すことができます。
JavaScriptReplyProxyこのreplyProxyオブジェクトは保持でき、いつでも使用して、ページが送信する個々のメッセージに返信するだけでなく、ページに任意の数のメッセージを送信できます。元のフレームが移動または破棄された場合、プロキシのpostMessage()を使用して送信されたメッセージは無視されます。アプリ側からの開始: ウェブページは常にアプリとの通信チャネルを開始する必要がありますが、ネイティブ アプリは一方的にウェブページにこのプロセスの開始を促すことができます。ネイティブ アプリは、
addDocumentStartJavaScript()(ページが読み込まれる前に JavaScript を評価する)またはevaluateJavaScript()(ページが読み込まれた後に JavaScript を評価する)を使用して、ウェブページと通信できます。
制限事項: この API は、文字列または byte[] 配列としてデータを送信します。JSON オブジェクトなどのより複雑なデータ構造の場合は、これらの形式のいずれかにシリアル化してから、反対側で逆シリアル化してデータ構造を再構築する必要があります。
使用例:
双方向メッセージ交換の完全なシーケンスを理解するには、イベントが次の順序で進行します。
- 開始(アプリ): ネイティブ アプリが
addWebMessageListenerでリスナーを登録し、ページ ナビゲーションを開始します(WebViewCompat.navigateやloadUrlなど)。 - メッセージ送信(ウェブ): ウェブページの JavaScript が
myObject.postMessage(message)を呼び出して通信を開始します。 - メッセージの受信と返信(アプリ): アプリはリスナー コールバックでメッセージを受信し、提供された
replyProxy.postMessage()を使用して返信します。 - 返信の受信(ウェブ): ウェブページは、
myObject.onmessage()コールバック関数で非同期の返信を受信します。
Kotlin
val myListener = WebViewCompat.WebMessageListener { _, _, _, _, replyProxy ->
// Handle the message from JS
replyProxy.postMessage("Acknowledged!")
}
// Check whether the WebView version supports the feature.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
val allowedOrigins = setOf("https://www.example.com")
WebViewCompat.addWebMessageListener(webView, "myObject", allowedOrigins, myListener)
}
Java
WebMessageListener myListener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
// Handle the message from JS
replyProxy.postMessage("Acknowledged!");
};
// Check whether the WebView version supports the feature.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
Set<String> allowedOrigins = Set.of("https://www.example.com");
WebViewCompat.addWebMessageListener(webView, "myObject", allowedOrigins, myListener);
}
次の JavaScript は、addWebMessageListener のクライアントサイドの実装を示しています。これにより、ウェブ コンテンツはネイティブ アプリからメッセージを受信し、myObject プロキシを介して独自のメッセージを送信できます。
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
postWebMessage(代替)を使用する
Android は、ウェブの window.postMessage と同様の非同期メッセージ ベースの代替手段を提供するために、これを導入しました。
仕組み: アプリは WebViewCompat.postWebMessage を使用して、ウェブページのメインフレームにペイロードを送信します。双方向通信チャネルを確立するには、WebMessageChannel を作成し、そのポートの 1 つをメッセージとともにウェブ コンテンツに渡します。
特徴:
- 非同期:
addWebMessageListenerと同様に、このメソッドは非同期メッセージングを使用します。これにより、アプリがバックグラウンドでデータを処理している間も、ウェブページはユーザー操作に応答し続けることができます。 - オリジン認識:
targetOriginを指定して、WebView が信頼できるウェブサイトにのみデータを配信するようにできます。
制限事項:
- スコープ: この API は、通信をメインフレームに制限します。iframe への直接のアドレス指定やメッセージの送信はサポートされていません。
- URI の制限: ターゲットのオリジンとして「*」を指定しない限り、
data:URI、file:URI、またはloadData()を使用して読み込まれたコンテンツに対してこのメソッドを使用することはできません。これにより、どのページでもメッセージを受信できるようになります。 - ID リスク: ウェブ コンテンツで送信者の ID を確認する明確な方法がない。ウェブページが受信するメッセージは、ネイティブ アプリまたは別の iframe から送信された可能性があります。
addWebMessageListener をサポートしていない以前の Android バージョンで、文字列ベースのデータ用のシンプルな非同期チャネルが必要な場合は、このメソッドを使用します。
addJavascriptInterface(レガシー)を使用する
最も古い方法は、ネイティブ オブジェクト インスタンスを WebView に直接挿入する方法です。
仕組み: Kotlin または Java クラスを定義し、許可するメソッドに @JavascriptInterface アノテーションを付け、addJavascriptInterface(Object, String) を使用してクラスのインスタンスを WebView に追加します。
特徴:
- 同期: Android コードのメソッドが戻るまで、JavaScript 実行環境がブロックされます。
- スレッド安全性: システムはバックグラウンド スレッドでメソッドを呼び出すため、Kotlin または Java 側で慎重な同期が必要です。
- セキュリティ リスク: デフォルトでは、
addJavascriptInterfaceは WebView 内のすべてのフレーム(iframe を含む)で使用できます。オリジンベースのアクセス制御がありません。WebView の非同期動作により、インターフェースを呼び出すフレームの URL を安全に特定することはできません。WebView.getUrl()などのメソッドは、正確性が保証されておらず、リクエストを行った特定のフレームを示すものでもないため、セキュリティ検証に利用してはなりません。
データ型の変換と強制型変換
addJavascriptInterface を使用すると、Chromium ベースの Java ブリッジが JavaScript ランタイムと Android アプリコードの間でデータ型を変換します。
メソッドのパラメータと戻り値には、次の強制変換ルールが適用されます。
パラメータの型マッピング(JavaScript から Java)
JavaScript がアノテーション付きの Java または Kotlin メソッドに引数を渡すと、ブリッジは JavaScript の値を対応する Java パラメータ型に強制変換します。
| Java パラメータの型 | JavaScript 引数値 | 強制行為 |
|---|---|---|
byte、short、int、long |
数値(整数) | 値はターゲットの整数型にキャストされます。範囲外の値は、標準の数値キャスト規則に従って折り返されます。 |
byte、short、int、long |
NaN |
0 に強制変換します。 |
byte、short、int、long |
Infinity |
byte と short の場合は -1 に、int と long の場合は Integer.MAX_VALUE と Long.MAX_VALUE に強制変換します。 |
float、double |
数値 | 対応する Java 浮動小数点値に強制変換します。 |
float、double |
NaN / Infinity |
Float.NaN、Double.NaN、Float.POSITIVE_INFINITY、または Double.POSITIVE_INFINITY に強制変換します。 |
char |
数値(整数) | 対応する Unicode コードポイントに変換されます。 |
char |
整数以外、NaN、Infinity |
\u0000 に強制変換します。 |
boolean |
true / false |
Java の true または false に強制変換します。 |
boolean |
Number、String、Object | false に強制変換されます(空でない文字列やゼロ以外の数値など)。 |
String |
文字列 | 文字列値は保持されます。 |
String |
数値、ブール値 | 文字列表現("42"、"true"、"false" など)としてフォーマットされます。 |
String |
null / undefined |
null は Java の null に強制変換され、undefined はリテラル文字列 "undefined" に強制変換されます。 |
String |
Object、ArrayBuffer、TypedArray | リテラル文字列 "undefined" に強制変換します。 |
プリミティブ配列(int[]、byte[]、boolean[] など)または String[] |
配列([...]) |
ターゲット要素型の 1 次元 Java 配列に変換します。スパース配列では、割り当てられていないインデックスはデフォルト値(0、false、null)で埋められます。 |
プリミティブ配列(int[]、byte[] など) |
TypedArray(Int8Array、Uint8Array、Int32Array、Float64Array) |
要素は対応する Java プリミティブ配列に強制変換されます。 |
多次元配列(int[][] など) |
ネストされた配列([[...]]) |
サポート対象外多次元配列パラメータは null と評価されます。 |
ArrayBuffer、DataView |
ArrayBuffer、DataView |
配列は対象外です。ArrayBuffer インスタンスと DataView インスタンスは null に評価されます。 |
Object またはカスタムクラス |
JavaScript オブジェクト({...}) |
サポート対象外任意の JavaScript オブジェクト リテラルは、Java で null と評価されます。 |
Object またはカスタムクラス |
挿入された Java オブジェクト ラッパー | サポート対象(ラウンドトリップ)。基盤となる Java インスタンスを Java メソッドに渡します。Java 型がパラメータ シグネチャと一致しない場合は、JavaScript 例外をスローします。 |
ボックス型(Integer、Double、Boolean など) |
数値、ブール値 | サポート対象外ボックス化されたプリミティブ型は不透明なオブジェクトとして扱われ、null に評価されます。 |
| 任意のプリミティブ型 | null / undefined |
デフォルト値(0、0.0、\u0000、false)に強制変換します。 |
Object、String、配列 |
null |
Java の null に強制変換します。 |
戻り値の型のマッピング(Java から JavaScript)
アノテーション付きの Java または Kotlin メソッドが値を返すと、ブリッジはそれを JavaScript 型に変換します。
| Java の戻り値の型 | JavaScript 値 | JavaScript typeof |
|---|---|---|
boolean |
true / false |
"boolean" |
byte、short、int、long、float、double |
数値 | "number" |
char |
数値(Unicode コードポイント) | "number" |
String(null 以外) |
文字列値 | "string" |
String(null) |
undefined |
"undefined" |
void |
undefined |
"undefined" |
Java 配列(int[]、String[] など) |
undefined |
"undefined"。配列の戻り値はサポートされていません。Java メソッドは実行されず、例外を発生させることなく undefined が返されます。 |
| Java オブジェクト / カスタム型(null 以外) | オブジェクト ラッパー | "object"。Java インスタンスの JavaScript ラッパーを作成します。JavaScript コードは、@JavascriptInterface でアノテーションが付けられたこのオブジェクトの任意の公開メソッドを呼び出すことができます。 |
Java オブジェクト / カスタム型(null) |
null |
"object" |
ボックス化されたプリミティブ(Integer、Double など) |
オブジェクト ラッパー | "object"。アクセス可能な @JavascriptInterface メソッドのない不透明な Java オブジェクト ラッパーとして返されます。これにより、JavaScript で値を使用できなくなります。 |
メソッドとメンバーのアクセシビリティ
JavaScript ブリッジは、意図しないコード実行を防ぐために、厳格なメンバー アクセスと可視性ルールを適用します。
- フィールドが公開されていない: Java フィールド(
publicフィールドとpublic finalフィールドを含む)は JavaScript からアクセスできず、undefinedに評価されます。 - アノテーションの要件:
@JavascriptInterfaceで明示的にアノテーションが付けられたメソッドのみが JavaScript に公開されます。 - 公開設定の制限: メソッドは
publicである必要があります。privateメソッドとprotectedメソッドは、@JavascriptInterfaceアノテーションが付いていても、JavaScript に公開されることはありません。 - 静的メソッド:
@JavascriptInterfaceでアノテーションが付けられた静的メソッドは、JavaScript から呼び出すことができます。 - 継承とオーバーライド: サブクラスがメソッドをオーバーライドする場合、
@JavascriptInterfaceアノテーションは継承されません。サブクラスがスーパークラスのアノテーション付きメソッドをオーバーライドする場合、サブクラスはオーバーライドされたメソッドに@JavascriptInterfaceアノテーションを明示的に含めて、JavaScript に公開する必要があります。スーパークラスでアノテーションが付けられている場合、スーパークラスから継承されたオーバーライドされていないパブリック メソッドはアクセス可能なままになります。 - リフレクション保護: 標準の Java リフレクション メソッド(
getClass()など)がブロックされ、JavaScript 例外がスローされて、リモートコード実行の脆弱性が防止されます。 - メソッドのオーバーロード: オーバーロードされた Java メソッドがサポートされています。ブリッジは、渡された引数の数のみに基づいてメソッド呼び出しを解決し、引数の型は考慮しません。引数の数が無効な状態でオーバーロードされたメソッドを呼び出すと、JavaScript 例外が発生します。2 つのオーバーロードの引数の数が同じ場合、1 つが任意に選択されます。
メカニズムの概要
次の表は、3 つの主なネイティブ ブリッジ実装メカニズムを簡単に比較したものです。
| メソッド | addWebMessageListener |
postWebMessage |
addJavascriptInterface |
|---|---|---|---|
| 実装 | 非同期(メインスレッドのリスナー) | 非同期 | 同期 |
| セキュリティ | 最高(許可リストに基づく) | 高(オリジン認識) | 低(オリジン チェックなし) |
| 複雑さ | 中 | 中 | シンプル |
| 方向 | 双方向 | 双方向 | ウェブからアプリへ |
| WebView の最小バージョン | バージョン 82(および Jetpack Webkit 1.3.0) | バージョン 45(および Jetpack Webkit 1.1.0) | すべてのバージョン |
| 推奨 | ○ | いいえ | いいえ |
大規模なデータ転送を処理する
数メガバイトの文字列やバイナリ ファイルなどの大きなペイロードを転送する場合は、メモリを慎重に管理して、32 ビット デバイスでのアプリケーション応答なし(ANR)エラーやクラッシュを回避する必要があります。このセクションでは、ホストアプリとウェブ コンテンツ間で大量のデータを転送する際のさまざまな手法と制限事項について説明します。
バイト配列でバイナリデータを転送する
WebMessageCompat クラスを使用すると、バイナリデータを Base64 文字列にシリアル化する代わりに、byte[] 配列を直接送信できます。Base64 ではデータサイズに約 33% のオーバーヘッドが追加されるため、この方法はメモリ効率が大幅に向上し、処理速度も速くなります。
- バイナリの利点: 画像ファイルや音声などのバイナリデータを、ネイティブ アプリとウェブ コンテンツの間で転送します。
- 制限事項: バイト配列を使用した場合でも、システムは、アプリと WebView がウェブ コンテンツのレンダリングに使用する分離プロセスとの間のプロセス間通信(IPC)境界を越えてデータをコピーします。ただし、非常に大きなファイルの場合、かなりのメモリが消費されます。
次のコード例は、WebMessageCompat.TYPE_ARRAY_BUFFER でマークされたメッセージを受信し、WebViewFeature.MESSAGE_ARRAY_BUFFER をチェックしてバイナリデータで任意に返信するよう、ネイティブ アプリ側で addWebMessageListener を設定する方法を示しています。
Kotlin
fun setupWebView(webView: WebView) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
val listener = WebViewCompat.WebMessageListener { view, message, sourceOrigin, isMainFrame, replyProxy ->
// Check if the received message is an ArrayBuffer
if (message.type == WebMessageCompat.TYPE_ARRAY_BUFFER) {
val binaryData: ByteArray = message.arrayBuffer
// Process your binary data (image, audio, etc.)
println("Received bytes: ${binaryData.size}")
// Optional: Send a binary reply back to JavaScript.
// This example sends a 3-byte array for simplicity.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
val replyBytes = byteArrayOf(0x01, 0x02, 0x03)
replyProxy.postMessage(replyBytes)
}
}
}
// "myBridge" matches the window.myBridge in JavaScript
WebViewCompat.addWebMessageListener(
webView,
"myBridge",
setOf("https://example.com"), // Security: restrict origins
listener
)
}
}
Java
public void setupWebView(WebView webView) {
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
WebViewCompat.WebMessageListener listener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
// Check if the received message is an ArrayBuffer
if (message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
byte[] binaryData = message.getArrayBuffer();
// Process your binary data (image, audio, etc.)
System.out.println("Received bytes: " + binaryData.length);
// Optional: Send a binary reply back to JavaScript.
// This example sends a 3-byte array for simplicity.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
byte[] replyBytes = new byte[]{0x01, 0x02, 0x03};
replyProxy.postMessage(replyBytes);
}
}
};
// "myBridge" matches the window.myBridge in JavaScript
WebViewCompat.addWebMessageListener(
webView,
"myBridge",
Set.of("https://example.com"), // Security: restrict origins
listener
);
}
}
次の JavaScript コードは、addWebMessageListener のクライアントサイド実装を示しています。これにより、ウェブ コンテンツは、前の例で挿入された window.myBridge プロキシを使用して、ネイティブ アプリとの間でバイナリデータ(ArrayBuffer)を送受信できます。
// Function to send an image or binary buffer to the app
async function sendBinaryToApp() {
const response = await fetch('image.jpg');
const buffer = await response.arrayBuffer();
// Check if the injected bridge object exists
if (window.myBridge) {
// You can send the ArrayBuffer directly
window.myBridge.postMessage(buffer);
}
}
// Receiving binary data from the app
if (window.myBridge) {
window.myBridge.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
console.log('Received binary data from App, length:', event.data.byteLength);
// Process the binary data (for example, as a Uint8Array)
const bytes = new Uint8Array(event.data);
console.log('First byte:', bytes[0]);
}
};
}
効率的な大規模データ読み込み
非常に大きなファイル(10 MB 超)の場合は、shouldInterceptRequest メソッドを使用してデータをストリーミングします。
- ウェブページが、カスタムのプレースホルダ URL への
fetch()呼び出しを開始します。例:https://app.local/large-file - Android アプリは、
WebViewClient.shouldInterceptRequest でこのリクエストをインターセプトします。 - アプリはデータを
InputStreamとして返します。
これにより、ペイロード全体を一度にメモリに読み込むのではなく、データをチャンク単位でストリーミングできます。
次の JavaScript 関数は、カスタムのプレースホルダ URL への標準の fetch() 呼び出しを使用して、ネイティブ アプリケーションから大きなバイナリファイルを効率的に読み込むためのクライアントサイド コードを示しています。
async function fetchBinaryFromApp() {
try {
// This URL doesn't need to exist on the internet
const response = await fetch('https://app.local/data/large-file.bin');
if (!response.ok) throw new Error('Network response was not okay');
// For raw binary data:
const arrayBuffer = await response.arrayBuffer();
console.log('Received binary data, size:', arrayBuffer.byteLength);
// Process buffer (for example, new Uint8Array(arrayBuffer))
/*
// OR for an image:
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
document.getElementById('myImage').src = imageUrl;
*/
} catch (error) {
console.error('Fetch error:', error);
}
}
次のコード例は、Kotlin と Java の両方で WebViewClient.shouldInterceptRequest メソッドを使用して、ウェブ コンテンツからリクエストされたカスタム プレースホルダ URL をインターセプトすることで、大きなバイナリ ファイルをストリーミングするネイティブ アプリ側の処理を示しています。
Kotlin
webView.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
val url = request?.url ?: return null
// Check if this is our custom placeholder URL
if (url.host == "app.local" && url.path == "/data/large-file.bin") {
try {
// 1. Get your data as an InputStream
// (from Assets, Files, or a generated byte stream)
val inputStream: InputStream = context.assets.open("my_data.pb")
// 2. Define Response Headers (Crucial for CORS/Fetch)
val headers = mutableMapOf<String, String>()
headers["Access-Control-Allow-Origin"] = "*" // Allow fetch from any origin
// 3. Return the response
return WebResourceResponse(
"application/octet-stream", // MIME type (for example, image/jpeg)
"UTF-8", // Encoding
200, // Status Code
"OK", // Reason Phrase
headers, // Custom Headers
inputStream // The actual data stream
)
} catch (e: Exception) {
// Handle exception
}
}
return super.shouldInterceptRequest(view, request)
}
}
Java
webView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String urlPath = request.getUrl().getPath();
String host = request.getUrl().getHost();
// Check if this is our custom placeholder URL
if ("app.local".equals(host) && "/data/large-file.bin".equals(urlPath)) {
try {
// 1. Get your data as an InputStream
// (from Assets, Files, or a generated byte stream)
InputStream inputStream = getContext().getAssets().open("my_data.pb");
// 2. Define Response Headers (Crucial for CORS/Fetch)
Map<String, String> headers = new HashMap<>();
headers.put("Access-Control-Allow-Origin", "*"); // Allow fetch from any origin
// 3. Return the response
return new WebResourceResponse(
"application/octet-stream", // MIME type (for example, image/jpeg)
"UTF-8", // Encoding
200, // Status Code
"OK", // Reason Phrase
headers, // Custom Headers
inputStream // The actual data stream
);
} catch (Exception e) {
// Handle exception
}
}
return super.shouldInterceptRequest(view, request);
}
});
セキュリティに関する推奨事項に沿って対応する
アプリケーションとユーザーデータを保護するには、ブリッジを実装する際に次のガイドラインに従ってください。
HTTPS を適用: 悪意のある第三者のコンテンツがアプリのネイティブ ロジックを呼び出せないように、安全なオリジンとの通信のみを許可します。
オリジンルールに依存する: 信頼を処理する最善の方法は、
allowedOriginRulesを厳密に定義し、メッセージ コールバックで提供されるsourceOriginを確認することです。絶対に必要な場合を除き、すべてのオリジンに一致する完全なワイルドカード(*)をオリジン ルールとして使用しないでください。サブドメイン(*.example.comなど)にワイルドカードを使用すると、複数のサブドメイン(foo.example.com、bar.example.comなど)を安全に照合できます。注: オリジン ルールは悪意のあるサードパーティのウェブサイトや非表示の iframe から保護しますが、信頼できるドメイン内のクロスサイト スクリプティング(XSS)の脆弱性から保護することはできません。たとえば、ウェブページにユーザー作成コンテンツが表示され、保存型 XSS の脆弱性がある場合、攻撃者は信頼できるオリジンとして機能するスクリプトを実行する可能性があります。機密性の高いネイティブ プラットフォーム オペレーションを実行する前に、メッセージ ペイロードに検証を適用することを検討してください。
サーフェス領域を最小限に抑える: ウェブページに必要な特定のメソッドまたはデータのみを公開します。
実行時に機能を確認する:
addWebMessageListenerなどの最近のブリッジ API は、Jetpack Webkit ライブラリの一部です。そのため、電話をかける前に必ずWebViewFeature.isFeatureSupported()を使用してサポートを確認してください。