JavaScript köprüsüyle yerel API'lere erişme

Bu sayfada, WebView içindeki web içeriği ile ana Android uygulaması arasındaki iletişimi kolaylaştırmak için JavaScript köprüsü olarak da bilinen yerel köprü oluşturmaya yönelik çeşitli yöntemler ve en iyi uygulamalar ele alınmaktadır.

Bu sayede web geliştiriciler, standart web API'lerinin normalde sağlamadığı yerel platform özelliklerine (ör. kamera, dosya sistemi veya gelişmiş donanım sensörleri) erişmek için JavaScript'i kullanabilir.

Kullanım alanları

JavaScript köprüsü uygulaması, web içeriğinin Android işletim sistemine daha derin erişim gerektirdiği çeşitli entegrasyon senaryolarına olanak tanır. Aşağıda bazı örnekler verilmiştir:

  • Platform entegrasyonu: Bir web sayfasından yerel Android kullanıcı arayüzü bileşenlerini (ör. biyometri istemleri, BottomSheetDialog) tetikleme.
  • Performans: Ağır hesaplama görevlerini yerel Java veya Kotlin koduna aktarma.
  • Veri kalıcılığı: Yerel şifrelenmiş veritabanlarına veya paylaşılan tercihlere erişme.
  • Büyük veri aktarımları: Uygulama ile web oluşturucu arasında medya dosyaları veya karmaşık veri yapıları aktarılması.

İletişim mekanizmaları

Android, yerel bir köprü oluşturmak için üç temel API nesli sunar. Bu uygulamaların tümü hâlâ kullanılabilir olsa da güvenlik, kullanılabilirlik ve performans açısından önemli farklılıklar vardır.

addWebMessageListener kullanın (önerilir)

addWebMessageListener, web içeriği ile yerel uygulama kodu arasındaki iletişim için en modern ve önerilen yaklaşımdır. JavaScript arayüzünün kullanım kolaylığı ile mesajlaşma sisteminin güvenliğini birleştirir.

İşleyiş şekli: Uygulama, belirli bir ada ve bir dizi izin verilen kaynak kuralına sahip bir dinleyici ekler. Ardından WebView, sayfa yüklenmeye başladığı andan itibaren JavaScript nesnesinin genel kapsamda (window.objectName) bulunduğundan emin olur.

Başlatma: WebView'ın JavaScript nesnesini herhangi bir komut dosyası çalıştırılmadan önce eklemesini sağlamak için sayfaya gitmeden önce addWebMessageListener işlevini çağırmanız gerekir (ör. WebViewCompat.navigate veya loadUrl işlevini çağırma).

Temel özellikler:

  • Güvenlik ve güven: Bu yöntem, eski API'lerin aksine başlatma sırasında Set<String> allowedOriginRules gerektirir. Bu, güven oluşturmanın temel mekanizmasıdır.

    https://example.com gibi güvenilir bir kaynak belirttiğinizde WebView, yerleştirilen JavaScript nesnelerini yalnızca bu kaynaktan yüklenen web sayfalarına gösterdiğini garanti eder.

    Yerel dinleyici geri çağırma işlevi, her iletiyle birlikte bir sourceOrigin parametresi alır. Köprünüz birden fazla izin verilen kaynağı destekliyorsa gönderenin tam kaynağını doğrulamak için bu özelliği kullanabilirsiniz.

    WebView, bu kaynak kontrollerini platform düzeyinde sıkı bir şekilde uyguladığından uygulamanız genellikle güvenilir bir sourceOrigin kaynağından alınan mesajların doğru olduğuna güvenebilir. Bu sayede, çoğu standart uygulamada yükün titiz bir şekilde doğrulanması gerekmez.

    • WebView, kuralları şemaya (HTTP/HTTPS), ana makineye ve bağlantı noktasına göre eşleştirir.
    • WebView, yolları yoksayar. Örneğin, https://example.com, https://example.com/login ve https://example.com/home'ye izin veriyor.
    • WebView, alt alan adları için joker karakterleri ana makinenin başlangıcıyla kesin olarak sınırlar. Örneğin, https://*.example.com, https://foo.example.com ile eşleşir ancak https://example.com ile eşleşmez. Hem https://example.com hem de alt alan adlarını eşleştirmeniz gerekiyorsa her kaynak kuralını izin verilenler listesine ayrı ayrı eklemeniz gerekir (örneğin, "https://example.com", "https://*.example.com"). Şema için veya bir alanın ortasında joker karakterler kullanamazsınız.

    Bu, köprüyü doğrulanmış alanlarla kısıtlayarak yetkisiz üçüncü taraf içeriğinin veya yerleştirilmiş iframe'lerin yerel kodu yürütmesini engeller.

  • Çoklu çerçeve desteği: Kaynak kurallarıyla eşleşen tüm çerçevelerde çalışır.

  • İş parçacığı oluşturma: Dinleyici geri çağırma işlemi, uygulamanın ana (kullanıcı arayüzü) iş parçacığında çalışır. Köprünüzün karmaşık veri işleme, JSON ayrıştırma veya veritabanı aramaları yapması gerekiyorsa uygulamayı "uygulama yanıt vermiyor" (ANR) hatasıyla dondurmamak için bu işi arka plan iş parçacığına aktarmanız gerekir.

  • Çift yönlü: Web sayfası mesaj gönderdiğinde uygulama, bu belirli çerçeveye mesaj göndermek için kullanabileceği bir JavaScriptReplyProxy alır. Bu replyProxy nesnesini saklayabilir ve sayfaya gönderilen her iletiye tek tek yanıt vermek yerine, sayfaya istediğiniz zaman istediğiniz sayıda ileti göndermek için kullanabilirsiniz. Kaynak çerçeve başka bir yere giderse veya yok edilirse proxy'de postMessage() kullanılarak gönderilen iletiler sessizce yoksayılır.

  • Uygulama tarafında başlatma: Web sayfası her zaman uygulama ile iletişim kanalını başlatmalıdır. Ancak yerel uygulama, web sayfasını bu süreci başlatmaya tek taraflı olarak yönlendirebilir. Yerel uygulama, addDocumentStartJavaScript() (sayfa yüklenmeden önce JavaScript'i değerlendirmek için) veya evaluateJavaScript() (sayfa yüklendikten sonra JavaScript'i değerlendirmek için) ile web sayfasıyla iletişim kurabilir.

Sınırlama: Bu API, verileri dizeler veya byte[] dizileri olarak gönderir. JSON nesneleri gibi daha karmaşık veri yapıları için bu verileri biçimlerden birine seri hale getirmeniz ve ardından veri yapısını yeniden oluşturmak için diğer tarafta seri halini kaldırmanız gerekir.

Kullanım örneği:

İki yönlü mesaj alışverişinin tüm sırasını anlamak için etkinlikler şu sırayla gerçekleşir:

  1. Başlatma (uygulama): Yerel uygulama, dinleyiciyi addWebMessageListener ile kaydeder ve sayfa gezinme işlemini başlatır (ör. WebViewCompat.navigate veya loadUrl ile).
  2. Mesaj gönderme (web): İletişimi başlatmak için web sayfasının JavaScript çağrıları myObject.postMessage(message) kullanılır.
  3. Mesaj alma ve yanıtlama (uygulama): Uygulama, mesajı dinleyici geri çağırma işlevinde alır ve sağlanan replyProxy.postMessage() ile yanıtlar.
  4. Yanıt alma (web): Web sayfası, eşzamansız yanıtı myObject.onmessage() geri çağırma işlevinde alır.

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

Aşağıdaki JavaScript, addWebMessageListener öğesinin istemci tarafı uygulamasını gösterir. Bu uygulama, web içeriğinin yerel uygulamadan mesaj almasına ve myObject proxy'si üzerinden kendi mesajlarını göndermesine olanak tanır.

myObject.onmessage = function(event) {
    console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");

postWebMessage (Alternatif) kullanın

Android, web'deki window.postMessage'ye benzer şekilde eşzamansız ve mesajlaşmaya dayalı bir alternatif sunmak için bu özelliği kullanıma sundu.

Nasıl çalışır? Uygulama, web sayfasının ana çerçevesine bir yük göndermek için WebViewCompat.postWebMessage kullanır. Çift yönlü bir iletişim kanalı oluşturmak için WebMessageChannel oluşturabilir ve bağlantı noktalarından birini mesajla birlikte web içeriğine iletebilirsiniz.

Özellikler:

  • Eşzamansız: addWebMessageListener gibi bu yöntem de eşzamansız mesajlaşmayı kullanır. Bu sayede uygulama verileri arka planda işlerken web sayfası kullanıcı etkileşimlerine yanıt vermeye devam eder.
  • Kaynak bilgisi: WebView'ın verileri yalnızca güvenilir bir web sitesine iletmesini sağlamak için targetOrigin belirtebilirsiniz.

Sınırlamalar:

  • Kapsam: Bu API, iletişimi ana çerçeveyle sınırlar. Doğrudan adresleme veya iFrame'lere mesaj gönderme desteklenmez.
  • URI kısıtlamaları: Hedef kaynak olarak "*" belirtmediğiniz sürece bu yöntemi data: URI'leri, file: URI'leri veya loadData() kullanılarak yüklenen içerikler için kullanamazsınız. Bu işlem, herhangi bir sayfanın mesaj almasına olanak tanır.
  • Kimlik riski: Web içeriğinin gönderenin kimliğini doğrulayabileceği net bir yöntem yoktur. Web sayfasının aldığı bir ileti, yerel uygulamanızdan veya başka bir iFrame'den gelmiş olabilir.

addWebMessageListener'yı desteklemeyen eski Android sürümlerinde dize tabanlı veriler için basit ve eşzamansız bir kanal gerektiğinde bu yöntemi kullanın.

addJavascriptInterface (Eski) kullanma

En eski yöntemde, yerel bir nesne örneği doğrudan WebView'a yerleştirilir.

İşleyiş şekli: Bir Kotlin veya Java sınıfı tanımlar, izin verilen yöntemleri @JavascriptInterface ile ek açıklama olarak belirtir ve addJavascriptInterface(Object, String) kullanarak sınıfın bir örneğini WebView'a eklersiniz.

Özellikler:

  • Eşzamanlı: Android kodunuzdaki yöntem döndürülene kadar JavaScript yürütme ortamı engellenir.
  • İş parçacığı güvenliği: Sistem, arka plan iş parçacığında yöntemleri çağırır. Bu nedenle, Kotlin veya Java tarafında dikkatli bir senkronizasyon gerekir.
  • Güvenlik riski: addJavascriptInterface, varsayılan olarak iFrame'ler dahil olmak üzere WebView'daki her çerçeve için kullanılabilir. Kaynak tabanlı erişim denetimi yoktur. WebView'ın eşzamansız davranışı nedeniyle, arayüzünüzü çağıran çerçevenin URL'sini güvenli bir şekilde belirlemek mümkün değildir. WebView.getUrl() gibi yöntemler, doğru oldukları garanti edilmediği ve isteği hangi belirli çerçevenin yaptığı belirtilmediği için güvenlik doğrulaması amacıyla kullanılmamalıdır.

Veri türü dönüştürmeleri ve zorlama

addJavascriptInterface kullanılırken Chromium tabanlı Java Bridge, JavaScript çalışma zamanı ile Android uygulama kodunuz arasındaki veri türlerini dönüştürür.

Yöntem parametreleri ve dönüş değerleri için aşağıdaki zorlama kuralları geçerlidir.

Parametre türü eşleme (JavaScript - Java)

JavaScript, açıklama eklenmiş bir Java veya Kotlin yöntemine bağımsız değişkenler ilettiğinde köprü, JavaScript değerlerini karşılık gelen Java parametre türlerine zorlar:

Java parametre türü JavaScript bağımsız değişken değeri Zorlama davranışı
byte, short, int, long Sayı (tam sayı) Değerler, hedef tam sayı türüne dönüştürülür. Sınırların dışındaki değerler, standart sayısal yayın kurallarına göre sarılır.
byte, short, int, long NaN 0 değerine zorlanır.
byte, short, int, long Infinity byte ve short için -1, int ve long için Integer.MAX_VALUE ve Long.MAX_VALUE olarak zorlanır.
float, double Sayı İlgili Java kayan nokta değerine zorlar.
float, double NaN/Infinity Float.NaN, Double.NaN, Float.POSITIVE_INFINITY veya Double.POSITIVE_INFINITY değerine zorlanır.
char Sayı (tam sayı) İlgili Unicode kod noktasına dönüştürülür.
char Tam sayı değil, NaN, Infinity \u0000 değerine zorlanır.
boolean true/false Java true veya false olarak zorlanır.
boolean Sayı, Dize, Nesne false (boş olmayan dizeler ve sıfır olmayan sayılar dahil) değerine zorlar.
String Dize Dize değeri korunur.
String Sayı, Boole Dize gösterimi olarak biçimlendirilir (örneğin, "42", "true", "false").
String null/undefined null, Java'ya null; undefined, değişmez dizeye "undefined" zorlanır.
String Object, ArrayBuffer, TypedArray Değişmez dize "undefined" olarak zorlar.
Temel dizi (int[], byte[], boolean[] gibi) veya String[] Dizi ([...]) Hedef öğe türünün 1 boyutlu Java dizisine dönüştürür. Seyrek diziler, atanmamış dizinleri varsayılan değerlerle (0, false, null) doldurur.
Temel dizi (ör. int[], byte[]) TypedArray (Int8Array, Uint8Array, Int32Array, Float64Array) Öğeler, ilgili Java temel dizisine dönüştürülür.
Çok boyutlu dizi (ör. int[][]) İç içe yerleştirilmiş dizi ([[...]]) Desteklenmez. Çok boyutlu dizi parametreleri null olarak değerlendiriliyor.
ArrayBuffer, DataView ArrayBuffer, DataView Dizi olarak desteklenmez. ArrayBuffer ve DataView örnekleri null olarak değerlendirilir.
Object veya özel sınıf JavaScript nesnesi ({...}) Desteklenmez. Rastgele JavaScript nesne değişmezleri Java'da null olarak değerlendirilir.
Object veya özel sınıf Yerleştirilmiş Java nesne sarmalayıcısı Desteklenir (gidiş-dönüş). Temel alınan Java örneğini Java yöntemine iletir. Java türü parametre imzasıyla eşleşmiyorsa bir JavaScript istisnası oluşturur.
Kutulu türler (ör. Integer, Double, Boolean) Sayı, Boole Desteklenmez. Sarmalanmış temel türler, opak nesneler olarak değerlendirilir ve null olarak hesaplanır.
Tüm temel türler null/undefined Varsayılan değerlere (0, 0.0, \u0000, false) zorlanır.
Object, String, dizi null Java'ya zorlar null.
Dönüş türü eşlemesi (Java'dan JavaScript'e)

Açıklama eklenmiş bir Java veya Kotlin yöntemi değer döndürdüğünde köprü, bu değeri bir JavaScript türüne dönüştürür:

Java dönüş türü JavaScript değeri JavaScript typeof
boolean true/false "boolean"
byte, short, int, long, float, double Sayı "number"
char Sayı (Unicode kod noktası) "number"
String (boş olmayan) Dize değeri "string"
String (null) undefined "undefined"
void undefined "undefined"
Java dizisi (ör. int[], String[]) undefined "undefined". Dizi döndürme değerleri desteklenmez. Java yöntemi yürütülmez ve istisna oluşturulmadan undefined döndürülür.
Java nesnesi / özel tür (null olmayan) Nesne sarmalayıcısı "object". Java örneği etrafında bir JavaScript sarmalayıcısı oluşturur. JavaScript kodu, bu nesnede @JavascriptInterface ile açıklama eklenmiş herhangi bir genel yöntemi çağırabilir.
Java nesnesi / özel tür (null) null "object"
Kutulu ilkel (ör. Integer, Double) Nesne sarmalayıcısı "object". Erişilebilir @JavascriptInterface yöntemleri olmayan opak bir Java nesne sarmalayıcısı olarak döndürülür. Bu nedenle, değer JavaScript'te kullanılamaz.

Yöntem ve üye erişimi

JavaScript köprüsü, istenmeyen kod yürütülmesine karşı koruma sağlamak için katı üye erişimi ve görünürlük kurallarını zorunlu kılar:

  • Alanlar kullanıma sunulmuyor: Java alanlarına (public ve public final alanları dahil) JavaScript'ten erişilemez ve undefined olarak değerlendirilir.
  • Açıklama şartı: Yalnızca @JavascriptInterface ile açıkça açıklama eklenmiş yöntemler JavaScript'e sunulur.
  • Görünürlük kısıtlamaları: Yöntemler public olmalıdır. private ve protected yöntemleri, @JavascriptInterface ek açıklamasını taşısalar bile JavaScript'e hiçbir zaman sunulmaz.
  • Statik yöntemler: @JavascriptInterface ile açıklama eklenmiş statik yöntemler JavaScript'ten çağrılabilir.
  • Devralma ve geçersiz kılma: Bir alt sınıf bir yöntemi geçersiz kıldığında @JavascriptInterface ek açıklamaları devralınmaz. Bir alt sınıf, üst sınıftaki açıklama eklenmiş bir yöntemi geçersiz kılıyorsa alt sınıf, yöntemi JavaScript'e sunmak için geçersiz kılınan yönteme @JavascriptInterface açıklamasını açıkça eklemelidir. Bir üst sınıftan devralınan ve geçersiz kılınmayan herkese açık yöntemler, üst sınıfta açıklama eklenmişse erişilebilir olmaya devam eder.
  • Yansıma koruması: Uzaktan kod çalıştırma güvenlik açıklarını önlemek için standart Java yansıma yöntemleri (ör. getClass()) engellenir ve JavaScript istisnası oluşturulur.
  • Yöntem aşırı yüklemesi: Aşırı yüklenmiş Java yöntemleri desteklenir. Köprü, yöntem çağrılarını yalnızca iletilen bağımsız değişkenlerin sayısına göre çözer ve bağımsız değişken türlerini dikkate almaz. Aşırı yüklenmiş bir yöntemi geçersiz bağımsız değişken sayısı ile çağırmak JavaScript istisnasına neden olur. İki aşırı yüklemenin aynı sayıda bağımsız değişkeni varsa biri rastgele seçilir.

Mekanizmaların özeti

Aşağıdaki tabloda, üç temel yerel köprü uygulama mekanizmasının kısa bir karşılaştırması verilmiştir:

Yöntem addWebMessageListener postWebMessage addJavascriptInterface
Uygulama Eşzamansız (ana iş parçacığında dinleyici) Eşzamansız Senkronize
Güvenlik En yüksek (izin verilenler listesine dayalı) Yüksek (kaynak bilgisiyle) Düşük (Kaynak kontrolü yok)
Karmaşıklık Orta Orta Basit
Yön İki yönlü İki yönlü Web'den uygulamaya
Minimum WebView sürümü Sürüm 82 (ve Jetpack Webkit 1.3.0) Sürüm 45 (ve Jetpack Webkit 1.1.0) Tüm sürümler
Önerilen Evet Hayır Hayır

Büyük veri aktarımlarını işleme

32 bit cihazlarda "Uygulama Yanıt Vermiyor" (ANR) hatalarını veya kilitlenmeleri önlemek için çok megabaytlık dizeler ya da ikili dosyalar gibi büyük yükleri aktarırken belleği dikkatli bir şekilde yönetmeniz gerekir. Bu bölümde, ana makine uygulaması ile web içeriği arasında önemli miktarda veri aktarımıyla ilişkili çeşitli teknikler ve sınırlamalar ele alınmaktadır.

Bayt dizileriyle ikili veri aktarma

WebMessageCompat sınıfıyla, ikili verileri Base64 dizelerine serileştirmek yerine doğrudan byte[] dizileri gönderebilirsiniz. Base64, veri boyutuna yaklaşık% 33 ek yük getirdiğinden bu yöntem, bellek açısından çok daha verimli ve daha hızlıdır.

  • İkili avantaj: Resim dosyaları veya ses gibi ikili verileri yerel uygulamanız ile web içeriğiniz arasında aktarın.
  • Sınırlama: Bayt dizileriyle bile sistem, uygulama ile WebView'ın web içeriğini oluşturmak için kullandığı yalıtılmış işlem arasındaki işlemler arası iletişim (IPC) sınırı boyunca verileri kopyalar. Bu yöntem, çok büyük dosyalar için önemli miktarda bellek kullanmaya devam eder.

Aşağıdaki kod örneklerinde, WebMessageCompat.TYPE_ARRAY_BUFFER işaretli mesajları almak için yerel uygulama tarafında addWebMessageListener'nın nasıl ayarlanacağı ve isteğe bağlı olarak WebViewFeature.MESSAGE_ARRAY_BUFFER kontrol edilerek ikili verilerle nasıl yanıt verileceği gösterilmektedir.

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

Aşağıdaki JavaScript kodu, addWebMessageListener öğesinin istemci tarafı uygulamasını gösterir. Bu uygulama, web içeriğinin önceki örnekte yer alan window.myBridge proxy'si kullanılarak yerel uygulamaya ve yerel uygulamadan ikili veri (ArrayBuffer) göndermesini ve almasını sağlar.

// 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]);
        }
    };
}

Büyük ölçekli verileri verimli bir şekilde yükleme

Çok büyük dosyalar (>10 MB) için verileri yayınlamak üzere shouldInterceptRequest yöntemini kullanın:

  1. Web sayfası, özel bir yer tutucu URL'ye fetch() çağrısı başlatır. Örneğin, https://app.local/large-file.
  2. Android uygulaması, bu isteğe WebViewClient.shouldInterceptRequest içinde müdahale eder.
  3. Uygulama, verileri InputStream olarak döndürür.

Bu sayede, tüm yükü aynı anda belleğe yüklemek yerine veriler parçalar halinde yayınlanabilir.

Aşağıdaki JavaScript işlevi, yerel uygulamadan büyük bir ikili program dosyasını standart bir fetch() özel yer tutucu URL'ye yapılan çağrı kullanarak verimli bir şekilde yüklemek için istemci taraflı kodu gösterir.

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

Aşağıdaki kod örnekleri, web içeriği tarafından istenen özel bir yer tutucu URL'yi yakalayarak büyük bir ikili program dosyasını yayınlamak için hem Kotlin hem de Java'da WebViewClient.shouldInterceptRequest yöntemini kullanarak yerel uygulama tarafını gösterir.

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

Güvenlik önerilerine uyma

Uygulamanızı ve kullanıcı verilerinizi korumak için köprü uygularken aşağıdaki yönergelere uyun:

  • HTTPS'yi zorunlu kılın: Kötü amaçlı üçüncü taraf içeriklerinin uygulamanızın yerel mantığını çağıramaması için yalnızca güvenli kaynaklarla iletişime izin verin.

  • Kaynak kurallarına güvenin: Güvenle ilgili sorunları çözmenin en iyi yolu, allowedOriginRules değerinizi net bir şekilde tanımlamak ve mesaj geri aramasında sağlanan sourceOrigin değerini kontrol etmektir. Kesinlikle gerekli olmadığı sürece, tüm kaynaklarla eşleşen tam joker karakteri (*) tek kaynak kuralınız olarak kullanmaktan kaçının. Alt alan adları için joker karakterler (örneğin, *.example.com) kullanmak, birden fazla alt alan adının (örneğin, foo.example.com, bar.example.com) eşleştirilmesi açısından geçerli ve güvenli olmaya devam eder.

    Not: Kaynak kuralları, kötü amaçlı üçüncü taraf web sitelerine ve gizli iFrame'lere karşı koruma sağlasa da kendi güvenilir alanınızdaki siteler arası komut dosyası çalıştırma (XSS) güvenlik açıklarına karşı koruma sağlayamaz. Örneğin, web sayfanızda kullanıcı tarafından oluşturulan içerik gösteriliyorsa ve depolanmış XSS'ye karşı savunmasızsa bir saldırgan, güvenilir kaynağınız gibi davranan bir komut dosyası yürütebilir. Hassas yerel platform işlemleri yürütülmeden önce mesaj yüklerine doğrulama uygulayabilirsiniz.

  • Yüzey alanını en aza indirin: Yalnızca web sayfasının gerektirdiği belirli yöntemleri veya verileri kullanıma sunun.

  • Çalışma zamanında özellikleri kontrol etme: addWebMessageListener dahil olmak üzere son köprü API'leri, Jetpack Webkit kitaplığının bir parçasıdır. Bu nedenle, ilgili numarayı aramadan önce WebViewFeature.isFeatureSupported() kullanarak destek alıp alamayacağınızı kontrol edin.