JavaScript ブリッジを使用してネイティブ API にアクセスする

このページでは、 ネイティブ ブリッジ(JavaScript ブリッジとも呼ばれます)を確立して、 ウェブ コンテンツとホストの Android アプリケーション間の通信を容易にするためのさまざまな方法とベスト プラクティスについて説明します。WebView

これにより、ウェブ デベロッパーは JavaScript を使用して、標準のウェブ API では通常提供されないネイティブ プラットフォームの機能(カメラ、ファイル システム、高度なハードウェア センサーなど)にアクセスできます。

ユースケース

JavaScript ブリッジを実装すると、ウェブ コンテンツが Android オペレーティング システムへのより深いアクセスを必要とするさまざまな統合シナリオが可能になります。以下に、いくつかの例を示します。

  • プラットフォームの統合: ウェブページからネイティブの Android UI コンポーネント( 生体認証プロンプト、BottomSheetDialogなど)をトリガーします。
  • パフォーマンス: 計算負荷の高いタスクをネイティブの Java または Kotlin コードにオフロードします。
  • データの永続性: ローカルの暗号化されたデータベースまたは共有 設定にアクセスします。
  • 大量のデータ転送: メディア ファイルや複雑なデータ構造を アプリとウェブ レンダラの間で渡します。

通信メカニズム

Android には、ネイティブ ブリッジを確立するための 3 つの主要な世代の API が用意されています。 これらはすべて現在も利用可能ですが、セキュリティ、使いやすさ、パフォーマンスが大きく異なります。

addWebMessageListener を使用する(推奨)

addWebMessageListener は、ウェブ コンテンツとネイティブ アプリコード間の通信に推奨される最新の方法です。JavaScript インターフェースの使いやすさとメッセージング システムのセキュリティを兼ね備えています。

仕組み: アプリは、特定の名前と一連の 許可されたオリジン ルールを持つリスナーを追加します。WebView は、ページの読み込みが開始された時点から、JavaScript オブジェクトがグローバル スコープ(window.objectName)に存在するようにします。

初期化: スクリプトが実行される前に WebView が JavaScript オブジェクトを挿入するようにするには、ページに移動する前に addWebMessageListener を呼び出す必要があります(WebViewCompat.navigateloadUrl を呼び出すなど)。

主な機能:

  • セキュリティと信頼: 従来の API とは異なり、このメソッドでは初期化時に Set<String>allowedOriginRules が必要です。これは、信頼を確立するための主要なメカニズムです。

    https://example.com などの信頼できるオリジンを指定すると、WebView は、挿入された JavaScript オブジェクトを、そのオリジンから読み込まれたウェブページにのみ公開することを保証します。

    ネイティブ リスナー コールバックは、すべてのメッセージとともに sourceOrigin パラメータを受け取ります。ブリッジが複数の許可されたオリジンをサポートしている場合は、これを使用して送信元の正確なオリジンを確認できます。

    WebView は、プラットフォーム レベルでこれらのオリジン チェックを厳密に適用するため、アプリは通常、信頼できる sourceOrigin から受信したメッセージを真実として信頼できます。これにより、ほとんどの標準実装で厳格なペイロード検証を行う必要がなくなります。

    • WebView は、スキーム(HTTP/HTTPS)、ホスト、ポートに対してルールを照合します。
    • WebView はパスを無視します。たとえば、https://example.com では https://example.com/loginhttps://example.com/home が許可されます。
    • WebView は、サブドメインのホストの先頭にワイルドカードを厳密に制限します。たとえば、https://*.example.comhttps://foo.example.com と一致しますが、https://example.com とは一致しません。https://example.com とそのサブドメインの両方を一致させる必要がある場合は、各 オリジン ルールを許可リストに個別に追加する必要があります(例: "https://example.com", "https://*.example.com")。スキームまたはドメインの中間にはワイルドカードを使用できません。

    これにより、ブリッジは検証済みのドメインに制限され、許可されていない第三者のコンテンツや挿入された iframe がネイティブ コードを実行するのを防ぐことができます。

  • マルチフレームのサポート: オリジン ルールに一致するすべてのフレームで動作します。

  • スレッド: リスナー コールバックは、アプリケーションのメイン(UI) スレッドで実行されます。ブリッジで複雑なデータ処理、JSON 解析、データベース検索を行う必要がある場合は、その処理をバックグラウンド スレッドにオフロードして、「アプリが応答していません」(ANR)エラーでアプリケーション UI がフリーズしないようにする必要があります。

  • 双方向: ウェブページがメッセージを送信すると、アプリは JavaScriptReplyProxyを受け取ります。これを使用して、その 特定のフレームにメッセージを返信できます。この replyProxy オブジェクトを保持し、いつでも使用して、ページが送信する個々のメッセージに返信するだけでなく、任意の数のメッセージをページに送信できます。元のフレームが移動または破棄された場合、プロキシで postMessage() を使用して送信されたメッセージは無視されます。

  • アプリ側からの開始: ウェブページは常にアプリとの 通信チャネルを開始する必要がありますが、ネイティブ アプリは一方的に ウェブページにこのプロセスを開始するよう求めることができます。ネイティブ アプリは、 ウェブページと addDocumentStartJavaScript()(ページの読み込み前に JavaScript を評価)または evaluateJavaScript()(ページの読み込み後に JavaScript を評価)を使用して通信できます。

制限事項: この API は、データを文字列または byte[] 配列として送信します。JSON オブジェクトなどの複雑なデータ構造の場合は、これらの形式のいずれかにシリアル化し、反対側で逆シリアル化してデータ構造を再構築する必要があります。

使用例:

双方向メッセージ交換の完全なシーケンスを理解するには、イベントが次の順序で進行します。

  1. 開始(アプリ): ネイティブ アプリはリスナーを addWebMessageListener で登録し、ページ ナビゲーションを開始します( WebViewCompat.navigateloadUrl など)。
  2. メッセージ送信(ウェブ): ウェブページの JavaScript が myObject.postMessage(message)を呼び出して通信を開始します。
  3. メッセージの受信と返信(アプリ): アプリは リスナー コールバックでメッセージを受信し、提供された replyProxy.postMessage() を使用して返信します。
  4. 返信の受信(ウェブ): ウェブページは、 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 アノテーションを付け、クラスのインスタンスを WebView に addJavascriptInterface(Object, String) を使用して追加します。

特徴:

  • 同期: Android コードの メソッドが戻るまで、JavaScript 実行環境がブロックされます。
  • スレッド セーフティ: システムはバックグラウンド スレッドでメソッドを呼び出すため、 Kotlin または Java 側で慎重に同期する必要があります。
  • セキュリティ リスク: デフォルトでは、addJavascriptInterface は iframe を含む WebView 内の すべてのフレームで使用できます。オリジン ベースのアクセス制御はありません。WebView の非同期動作により、インターフェースを呼び出すフレームの URL を安全に特定することはできません。セキュリティ検証に WebView.getUrl() などのメソッドを使用しないでください。正確であることが保証されておらず、リクエストを行った特定のフレームを示すものではありません。

データ型の変換と強制型変換

addJavascriptInterface を使用する場合、Chromium ベースの Java ブリッジは、JavaScript ランタイムと Android アプリコードの間でデータ型を変換します。

メソッドのパラメータと戻り値には、次の強制型変換ルールが適用されます。

パラメータ型のマッピング(JavaScript から Java)

JavaScript がアノテーション付きの Java または Kotlin メソッドに引数を渡すと、ブリッジは JavaScript の値を対応する Java パラメータ型に強制的に変換します。

Java パラメータ型 JavaScript 引数値 強制型変換の動作
byteshortintlong 数値(整数) 値はターゲットの整数型にキャストされます。範囲外の値は、標準の数値キャスト ルールに従って折り返されます。
byteshortintlong NaN 0 に強制的に変換されます。
byteshortintlong Infinity byteshort の場合は -1intlong の場合は Integer.MAX_VALUELong.MAX_VALUE に強制的に変換されます。
floatdouble 数値 対応する Java 浮動小数点値に強制的に変換されます。
floatdouble NaN / Infinity Float.NaNDouble.NaNFloat.POSITIVE_INFINITYDouble.POSITIVE_INFINITY に強制的に変換されます。
char 数値(整数) 対応する Unicode コードポイントに変換されます。
char 整数以外、NaNInfinity \u0000 に強制的に変換されます。
boolean true / false Java の true または false に強制的に変換されます。
boolean 数値、文字列、オブジェクト false に強制的に変換されます(空でない文字列とゼロ以外の数値を含む)。
String 文字列 文字列値は保持されます。
String 数値、ブール値 文字列表現としてフォーマットされます("42""true""false" など)。
String null / undefined null は Java の null に強制的に変換されます。undefined はリテラル文字列 "undefined" に強制的に変換されます。
String オブジェクト、ArrayBuffer、TypedArray リテラル文字列 "undefined" に強制的に変換されます。
プリミティブ配列(int[]byte[]boolean[] など)または String[] 配列([...] ターゲット要素型の 1 次元 Java 配列に変換されます。スパース配列では、割り当てられていないインデックスがデフォルト値(0falsenull)で埋められます。
プリミティブ配列(int[]byte[] など) TypedArray(Int8ArrayUint8ArrayInt32ArrayFloat64Array 要素は対応する Java プリミティブ配列に強制的に変換されます。
多次元配列(int[][] など) ネストされた配列([[...]] サポート対象外 。多次元配列パラメータは null に評価されます。
ArrayBufferDataView ArrayBufferDataView 配列としては対象外です。ArrayBuffer インスタンスと DataView インスタンスは null に評価されます。
Object またはカスタムクラス JavaScript オブジェクト({...} サポート対象外 。任意の JavaScript オブジェクト リテラルは Java で null に評価されます。
Object またはカスタムクラス 挿入された Java オブジェクト ラッパー サポート対象(ラウンド トリップ)。基になる Java インスタンスを Java メソッドに渡します。Java 型がパラメータ シグネチャと一致しない場合は、JavaScript 例外をスローします。
ボックス化された型(IntegerDoubleBoolean など) 数値、ブール値 サポート対象外 。ボックス化されたプリミティブ型は不透明なオブジェクトとして扱われ、null に評価されます。
任意のプリミティブ型 null / undefined デフォルト値(00.0\u0000false)に強制的に変換されます。
ObjectString、配列 null Java の null に強制的に変換されます。
戻り値の型のマッピング(Java から JavaScript)

アノテーション付きの Java または Kotlin メソッドが値を返すと、ブリッジはその値を JavaScript 型に変換します。

Java 戻り値の型 JavaScript 値 JavaScript typeof
boolean true / false "boolean"
byteshortintlongfloatdouble 数値 "number"
char 数値(Unicode コードポイント) "number"
String(null 以外) 文字列値 "string"
Stringnull undefined "undefined"
void undefined "undefined"
Java 配列(int[]String[] など) undefined "undefined"。「配列の戻り値はサポートされていません。」Java メソッドは実行されず、例外を発生させることなく undefined が返されます。
Java オブジェクト / カスタム型(null 以外) オブジェクト ラッパー "object"。「object」Java インスタンスの周囲に JavaScript ラッパーを作成します。JavaScript コードは、@JavascriptInterface でアノテーションが付けられたこのオブジェクトの任意の public メソッドを呼び出すことができます。
Java オブジェクト / カスタム型(null null "object"
ボックス化されたプリミティブ(IntegerDouble など) オブジェクト ラッパー "object"。アクセス可能な @JavascriptInterface メソッドがない不透明な Java オブジェクト ラッパーとして返されるため、JavaScript で値を使用できません。

メソッドとメンバーのアクセシビリティ

JavaScript ブリッジは、意図しないコード実行を防ぐために、厳格なメンバー アクセスと可視性ルールを適用します。

  • フィールドは公開されない: Java フィールド(public フィールドと public final フィールドを含む)は JavaScript からアクセスできず、 undefined に評価されます。
  • アノテーションの要件: @JavascriptInterface で明示的にアノテーションが付けられたメソッドのみが JavaScript に公開されます。
  • 可視性の制限: メソッドは public である必要があります。private メソッドと protected メソッドは、@JavascriptInterface アノテーションが付いていても、JavaScript に公開されることはありません。
  • 静的メソッド: @JavascriptInterface でアノテーションが付けられた静的メソッドは、JavaScript から 呼び出すことができます。
  • 継承とオーバーライド: @JavascriptInterface アノテーションは、サブクラスがメソッドをオーバーライドしても 継承されません。サブクラスがスーパークラスのアノテーション付きメソッドをオーバーライドする場合、サブクラスはオーバーライドされたメソッドに @JavascriptInterface アノテーションを明示的に含めて、JavaScript に公開する必要があります。スーパークラスから継承されたオーバーライドされていない public メソッドは、スーパークラスでアノテーションが付けられている場合は引き続きアクセスできます。
  • リフレクション保護: 標準の Java リフレクション メソッド( getClass()など)はブロックされ、リモート コード実行の脆弱性を防ぐために JavaScript 例外がスローされます。
  • メソッドのオーバーロード: オーバーロードされた Java メソッドがサポートされています。ブリッジは、渡された引数の数のみに基づいてメソッド呼び出しを解決し、引数の型は考慮しません。引数の数が無効なオーバーロードされたメソッドを呼び出すと、JavaScript 例外が発生します。2 つのオーバーロードの引数の数が同じ場合、いずれかが任意に選択されます。

メカニズムの概要

次の表は、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 メソッドを使用して データをストリーミングします。

  1. ウェブページは、カスタムのプレースホルダ URL に対して fetch() 呼び出しを開始します。例: https://app.local/large-file
  2. Android アプリは、 WebViewClient.shouldInterceptRequest でこのリクエストをインターセプトします。
  3. アプリはデータを 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.combar.example.com など)に一致させる場合は有効で安全です。

    : オリジン ルールは、悪意のあるサードパーティのウェブサイト や非表示の iframe から保護しますが、信頼できるドメイン内のクロスサイト スクリプティング(XSS) の脆弱性から保護することはできません。たとえば、ウェブページにユーザー作成コンテンツが表示され、保存された XSS に対して脆弱性がある場合、攻撃者は信頼できるオリジンとして動作するスクリプトを実行する可能性があります。機密性の高いネイティブ プラットフォーム オペレーションを実行する前に、メッセージ ペイロードに検証を適用することを検討してください。

  • サーフェス領域を最小限に抑える: ウェブページに必要な特定のメソッドまたはデータのみを公開します。

  • 実行時に機能を確認する: addWebMessageListener などの最新のブリッジ API は、Jetpack Webkit ライブラリの一部です。そのため、呼び出す前に WebViewFeature.isFeatureSupported() を使用してサポートを確認してください。