Questa pagina illustra i vari metodi e le best practice per stabilire un
bridge nativo, noto anche come bridge JavaScript, per facilitare la comunicazione
tra i contenuti web in un WebView e un'app per Android host.
In questo modo, gli sviluppatori web possono utilizzare JavaScript per accedere alle funzionalità della piattaforma nativa, come la fotocamera, il file system o i sensori hardware avanzati, che le API web standard normalmente non forniscono.
Casi d'uso
L'implementazione di un bridge JavaScript consente vari scenari di integrazione in cui i contenuti web richiedono un accesso più approfondito al sistema operativo Android. Di seguito sono riportati alcuni esempi:
- Integrazione della piattaforma: attivazione di componenti UI nativi di Android (ad esempio, prompt biometrici,
BottomSheetDialog) da una pagina web. - Prestazioni: delega di attività di calcolo pesanti a codice Java o Kotlin nativo.
- Persistenza dei dati: accesso a database criptati locali o preferenze condivise.
- Trasferimenti di grandi quantità di dati: passaggio di file multimediali o strutture di dati complesse tra l'app e il renderer web.
Meccanismi di comunicazione
Android offre tre generazioni principali di API per stabilire un bridge nativo. Sebbene siano ancora tutti disponibili, differiscono in modo significativo per sicurezza, usabilità e prestazioni.
Utilizza addWebMessageListener (consigliato)
addWebMessageListener è l'approccio più moderno e consigliato per la comunicazione tra i contenuti web e il codice dell'app nativa. Combina la facilità
d'uso dell'interfaccia JavaScript con la sicurezza del sistema di messaggistica.
Come funziona: l'app aggiunge un listener con un nome specifico e un insieme di regole di origine consentite. Il componente WebView garantisce quindi che l'oggetto JavaScript sia
presente nell'ambito globale (window.objectName) dal momento in cui la pagina
inizia a caricarsi.
Inizializzazione: per assicurarti che WebView inserisca l'oggetto JavaScript prima dell'esecuzione di qualsiasi script, devi chiamare addWebMessageListener prima di passare alla pagina (ad esempio chiamando WebViewCompat.navigate o loadUrl).
Funzionalità principali:
Sicurezza e affidabilità: a differenza delle API legacy, questo metodo richiede un
Set<String>diallowedOriginRulesdurante l'inizializzazione. Questo è il meccanismo principale per stabilire la fiducia.Quando specifichi un'origine attendibile, ad esempio
https://example.com, il componente WebView garantisce che esponga gli oggetti JavaScript inseriti solo alle pagine web caricate da quell'origine esatta.Il callback del listener nativo riceve un parametro
sourceOrigincon ogni messaggio. Puoi utilizzarlo per verificare l'origine esatta del mittente se il tuo bridge supporta più origini consentite.Poiché WebView applica rigorosamente questi controlli dell'origine a livello di piattaforma, la tua app può generalmente fare affidamento sui messaggi ricevuti da un
sourceOriginattendibile come veritieri, eliminando la necessità di una rigorosa convalida del payload nella maggior parte delle implementazioni standard.- WebView confronta le regole con lo schema (HTTP/HTTPS), l'host e la porta.
- WebView ignora i percorsi. Ad esempio,
https://example.comconsentehttps://example.com/loginehttps://example.com/home. - WebView limita rigorosamente i caratteri jolly all'inizio dell'host per
i sottodomini. Ad esempio,
https://*.example.comcorrisponde ahttps://foo.example.com, ma non ahttps://example.com. Se devi corrispondere sia ahttps://example.comsia ai relativi sottodomini, devi aggiungere ogni regola di origine separatamente all'elenco consentito (ad esempio,"https://example.com", "https://*.example.com"). Non puoi utilizzare caratteri jolly per lo schema o al centro di un dominio.
In questo modo, il bridge è limitato ai domini verificati, impedendo l'esecuzione di codice nativo da parte di contenuti di terze parti non autorizzati o iframe inseriti.
Supporto multi-frame: funziona su tutti i frame che corrispondono alle regole di origine.
Threading: il callback del listener viene eseguito sul thread principale (UI) dell'applicazione. Se il bridge deve gestire l'elaborazione di dati complessi, l'analisi JSON o le ricerche nel database, devi trasferire il lavoro su un thread in background per evitare il blocco dell'interfaccia utente dell'applicazione con un errore "L'app non risponde" (ANR).
Bidirezionale: quando la pagina web invia un messaggio, l'app riceve un
JavaScriptReplyProxyche può utilizzare per inviare messaggi a quel frame specifico. Puoi conservare questo oggettoreplyProxye utilizzarlo in qualsiasi momento per inviare un numero qualsiasi di messaggi alla pagina, non solo per rispondere a ogni singolo messaggio inviato dalla pagina. Se il frame di origine esce dalla navigazione o viene eliminato, i messaggi inviati utilizzandopostMessage()sul proxy vengono ignorati automaticamente.Avvio lato app: sebbene la pagina web debba sempre avviare il canale di comunicazione con l'app, l'app nativa può richiedere unilateralmente alla pagina web di iniziare questa procedura. L'app nativa può comunicare con la pagina web con
addDocumentStartJavaScript()(per valutare JavaScript prima del caricamento della pagina) oevaluateJavaScript()(per valutare JavaScript dopo il caricamento della pagina).
Limitazione: questa API invia i dati come stringhe o array byte[]. Per
strutture di dati più complesse, ad esempio oggetti JSON, devi serializzarle
in uno di questi formati e poi deserializzarle dall'altra parte per ricostruire
la struttura dei dati.
Esempio di utilizzo:
Per comprendere la sequenza completa di uno scambio di messaggi bidirezionale, gli eventi si susseguono in questo ordine:
- Avvio (app): l'app nativa registra il listener con
addWebMessageListenere avvia la navigazione della pagina (ad esempio conWebViewCompat.navigateoloadUrl). - Invio di messaggi (web): le chiamate JavaScript della pagina web
myObject.postMessage(message)per avviare la comunicazione. - Ricezione e risposta ai messaggi (app): l'app riceve il messaggio nel
callback del listener e risponde utilizzando
replyProxy.postMessage()fornito. - Ricezione risposta (web): la pagina web riceve la risposta asincrona nella funzione di callback
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);
}
Il seguente codice JavaScript mostra l'implementazione lato client di
addWebMessageListener, consentendo ai contenuti web di ricevere messaggi dall'app
nativa e di inviare i propri messaggi tramite il proxy myObject.
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
Usa postWebMessage (alternativa)
Android ha introdotto questa funzionalità per fornire un'alternativa asincrona basata sulla messaggistica
simile a window.postMessage del web.
Come funziona: l'app utilizza WebViewCompat.postWebMessage per inviare un payload
al frame principale della pagina web. Per stabilire un canale di comunicazione bidirezionale, puoi creare un WebMessageChannel e passare una delle sue porte con il messaggio ai contenuti web.
Caratteristiche:
- Asincrono: come
addWebMessageListener, questo metodo utilizza la messaggistica asincrona, che garantisce che la pagina web rimanga reattiva alle interazioni degli utenti mentre l'app elabora i dati in background. - Riconoscimento dell'origine: puoi specificare un
targetOriginper assicurarti che WebView fornisca i dati solo a un sito web attendibile.
Limitazioni:
- Ambito: questa API limita la comunicazione al frame principale. Non supporta l'indirizzamento diretto o l'invio di messaggi agli iframe.
- Limitazioni URI: non puoi utilizzare questo metodo per i contenuti caricati utilizzando
URI
data:, URIfile:oloadData(), a meno che tu non specifichi "*" come origine di destinazione. In questo modo, qualsiasi pagina può ricevere il messaggio. - Rischio di identità: non esiste un modo chiaro per i contenuti web di verificare l'identità del mittente. Un messaggio ricevuto dalla pagina web potrebbe provenire dalla tua app nativa o da un altro iframe.
Utilizza questo metodo quando hai bisogno di un canale asincrono semplice per i dati basati su stringhe nelle versioni precedenti di Android che non supportano addWebMessageListener.
Usa addJavascriptInterface (legacy)
Il metodo più vecchio prevede l'inserimento di un'istanza di oggetto nativa direttamente in WebView.
Come funziona: definisci una classe Kotlin o Java, annota i metodi consentiti con @JavascriptInterface e aggiungi un'istanza della classe a WebView utilizzando addJavascriptInterface(Object, String).
Caratteristiche:
- Sincrono: l'ambiente di esecuzione JavaScript viene bloccato finché il metodo nel codice Android non restituisce un valore.
- Thread safety: il sistema chiama i metodi su un thread in background, richiedendo una sincronizzazione accurata sul lato Kotlin o Java.
- Rischio per la sicurezza: per impostazione predefinita,
addJavascriptInterfaceè disponibile per ogni frame all'interno di WebView, inclusi gli iframe. Manca il controllo dell'accesso basato sull'origine. A causa del comportamento asincrono di WebView, non è possibile determinare in modo sicuro l'URL del frame che chiama la tua interfaccia. Non devi fare affidamento su metodi comeWebView.getUrl()per la verifica della sicurezza, in quanto non è garantita la loro accuratezza e non indicano quale frame specifico ha effettuato la richiesta.
Conversioni e forzatura dei tipi di dati
Quando utilizzi addJavascriptInterface, il bridge Java basato su Chromium converte
i tipi di dati tra il runtime JavaScript e il codice dell'app per Android.
Ai parametri del metodo e ai valori restituiti si applicano le seguenti regole di coercizione.
Mappatura dei tipi di parametri (da JavaScript a Java)
Quando JavaScript passa argomenti a un metodo Java o Kotlin annotato, il bridge converte i valori JavaScript nei tipi di parametri Java corrispondenti:
| Tipo di parametro Java | Valore dell'argomento JavaScript | Comportamento coercitivo |
|---|---|---|
byte, short, int, long |
Numero (intero) | I valori vengono convertiti nel tipo di numero intero di destinazione. I valori fuori dai limiti vengono riportati all'interno in base alle regole standard di conversione numerica. |
byte, short, int, long |
NaN |
Forza a 0. |
byte, short, int, long |
Infinity |
Forza la conversione in -1 per byte e short oppure in Integer.MAX_VALUE e Long.MAX_VALUE per int e long. |
float, double |
Numero | Forza la conversione al valore in virgola mobile Java corrispondente. |
float, double |
NaN/Infinity |
Forza la conversione in Float.NaN, Double.NaN, Float.POSITIVE_INFINITY o Double.POSITIVE_INFINITY. |
char |
Numero (intero) | Convertito nel punto di codice Unicode corrispondente. |
char |
Non intero, NaN, Infinity |
Forza a \u0000. |
boolean |
true/false |
Forza la conversione in Java true o false. |
boolean |
Numero, stringa, oggetto | Forza il valore a false (incluse stringhe non vuote e numeri diversi da zero). |
String |
Stringa | Il valore della stringa viene conservato. |
String |
Numero, booleano | Formattato come rappresentazione di stringa (ad esempio, "42", "true", "false"). |
String |
null/undefined |
null viene forzato a Java null; undefined viene forzato alla stringa letterale "undefined". |
String |
Object, ArrayBuffer, TypedArray | Forza la conversione nella stringa letterale "undefined". |
Array primitivo (ad esempio int[], byte[], boolean[]) o String[] |
Array ([...]) |
Converte in un array Java 1D del tipo di elemento di destinazione. Gli array sparsi riempiono gli indici non assegnati con valori predefiniti (0, false, null). |
Array primitivo (ad esempio int[], byte[]) |
TypedArray (Int8Array, Uint8Array, Int32Array, Float64Array) |
Gli elementi vengono forzati nell'array primitivo Java corrispondente. |
Array multidimensionale (ad esempio int[][]) |
Array nidificato ([[...]]) |
Non supportato. I parametri di array multidimensionali restituiscono null. |
ArrayBuffer, DataView |
ArrayBuffer, DataView |
Non supportato come array. Le istanze ArrayBuffer e DataView vengono valutate come null. |
Object o classe personalizzata |
Oggetto JavaScript ({...}) |
Non supportato. I valori letterali degli oggetti JavaScript arbitrari vengono valutati come null in Java. |
Object o classe personalizzata |
Wrapper dell'oggetto Java inserito | Supportato (conversione bidirezionale). Passa l'istanza Java sottostante al metodo Java. Genera un'eccezione JavaScript se il tipo Java non corrisponde alla firma del parametro. |
Tipi di scatole (ad esempio Integer, Double, Boolean) |
Numero, booleano | Non supportato. I tipi primitivi in scatola vengono trattati come oggetti opachi e restituiscono null. |
| Qualsiasi tipo primitivo | null/undefined |
Forza i valori predefiniti (0, 0.0, \u0000, false). |
Object, String, array |
null |
Esegue la conversione in Java null. |
Mappatura dei tipi restituiti (da Java a JavaScript)
Quando un metodo Java o Kotlin annotato restituisce un valore, il bridge lo converte in un tipo JavaScript:
| Tipo restituito Java | Valore JavaScript | JavaScript typeof |
|---|---|---|
boolean |
true/false |
"boolean" |
byte, short, int, long, float, double |
Numero | "number" |
char |
Numero (punto di codice Unicode) | "number" |
String (non nullo) |
Valore stringa | "string" |
String (null) |
undefined |
"undefined" |
void |
undefined |
"undefined" |
Array Java (ad esempio int[], String[]) |
undefined |
"undefined". I valori restituiti di array non sono supportati. Il metodo Java non viene eseguito e undefined viene restituito senza generare un'eccezione. |
| Oggetto Java / tipo personalizzato (non nullo) | Wrapper oggetto | "object". Crea un wrapper JavaScript intorno all'istanza Java. Il codice JavaScript può chiamare qualsiasi metodo pubblico di questo oggetto annotato con @JavascriptInterface. |
Oggetto Java / tipo personalizzato (null) |
null |
"object" |
Primitiva boxed (ad esempio Integer, Double) |
Wrapper oggetto | "object". Restituito come wrapper di oggetti Java opaco senza metodi @JavascriptInterface accessibili, il che rende il valore inutilizzabile in JavaScript. |
Accessibilità di metodi e membri
Il bridge JavaScript applica regole rigorose di accesso e visibilità dei membri per proteggere dall'esecuzione di codice non intenzionale:
- I campi non sono esposti: i campi Java (inclusi i campi
publicepublic final) non sono accessibili da JavaScript e vengono valutati comeundefined. - Requisito di annotazione: solo i metodi annotati esplicitamente con
@JavascriptInterfacesono esposti a JavaScript. - Limitazioni di visibilità: i metodi devono essere
public. I metodiprivateeprotectednon vengono mai esposti a JavaScript, anche se contengono l'annotazione@JavascriptInterface. - Metodi statici: i metodi statici annotati con
@JavascriptInterfacesono richiamabili da JavaScript. - Ereditarietà e override: le annotazioni
@JavascriptInterfacenon vengono ereditate quando una sottoclasse esegue l'override di un metodo. Se una sottoclasse esegue l'override di un metodo annotato di una superclasse, deve includere esplicitamente l'annotazione@JavascriptInterfacenel metodo sottoposto a override per esporlo a JavaScript. I metodi pubblici non sottoposti a override ereditati da una superclasse rimangono accessibili se annotati nella superclasse. - Protezione della reflection: i metodi di reflection Java standard (ad esempio
getClass()) vengono bloccati e generano un'eccezione JavaScript per impedire vulnerabilità di esecuzione di codice da remoto. - Overloading dei metodi: sono supportati i metodi Java sovraccarichi. Il bridge risolve le chiamate di metodi in base al numero di argomenti passati e non tiene conto dei tipi di argomenti. La chiamata di un metodo sovraccarico con un numero di argomenti non valido genera un'eccezione JavaScript. Se due sovraccarichi hanno lo stesso numero di argomenti, ne verrà scelto uno in modo arbitrario.
Riepilogo dei meccanismi
La seguente tabella fornisce un rapido confronto dei tre meccanismi di implementazione del bridge nativo principali:
| Metodo | addWebMessageListener |
postWebMessage |
addJavascriptInterface |
|---|---|---|---|
| Implementazione | Asincrono (listener sul thread principale) | Asincrona | Sincrona |
| Sicurezza | Più alta (in base alla lista consentita) | Elevata (con riconoscimento dell'origine) | Bassa (nessun controllo dell'origine) |
| complessità | Moderato | Moderato | Semplice |
| Direzione | Bidirezionale | Bidirezionale | Web to app |
| Versione minima di WebView | Versione 82 (e Jetpack Webkit 1.3.0) | Versione 45 (e Jetpack Webkit 1.1.0) | Tutte le versioni |
| Consigliato | Sì | No | No |
Gestire trasferimenti di dati di grandi dimensioni
Devi gestire attentamente la memoria quando trasferisci payload di grandi dimensioni, ad esempio stringhe di più megabyte o file binari, per evitare errori "L'applicazione non risponde" (ANR) o arresti anomali sui dispositivi a 32 bit. Questa sezione descrive le varie tecniche e limitazioni associate al trasferimento di grandi quantità di dati tra l'applicazione host e i contenuti web.
Trasferire dati binari con array di byte
Con la classe WebMessageCompat, puoi inviare direttamente array byte[]
anziché serializzare i dati binari in stringhe Base64. Poiché Base64 aggiunge
un sovraccarico di circa il 33% alle dimensioni dei dati, questo metodo è molto più
efficiente in termini di memoria e più veloce.
- Vantaggio binario: trasferisci dati binari come file immagine o audio tra la tua app nativa e i contenuti web.
- Limitazione: anche con gli array di byte, il sistema copia i dati oltre il limite di comunicazione interprocesso (IPC) tra l'app e il processo isolato che WebView utilizza per il rendering dei contenuti web. Ciò comporta comunque un consumo di memoria significativo per i file di grandi dimensioni.
I seguenti esempi di codice mostrano come configurare addWebMessageListener sul lato dell'app nativa per ricevere messaggi contrassegnati con WebMessageCompat.TYPE_ARRAY_BUFFER e, facoltativamente, rispondere con dati binari controllando WebViewFeature.MESSAGE_ARRAY_BUFFER.
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
);
}
}
Il seguente codice JavaScript mostra l'implementazione lato client di
addWebMessageListener, consentendo ai contenuti web di inviare e ricevere dati
binari (ArrayBuffer) da e verso l'app nativa utilizzando il proxy
window.myBridge inserito nell'esempio precedente.
// 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]);
}
};
}
Caricamento efficiente di dati su larga scala
Per file molto grandi (> 10 MB), utilizza il metodo shouldInterceptRequest per
trasmettere i dati in streaming:
- La pagina web avvia una chiamata
fetch()a un URL personalizzato segnaposto. Ad esempio,https://app.local/large-file. - L'app per Android intercetta questa richiesta in
WebViewClient.shouldInterceptRequest. - L'app restituisce i dati come
InputStream.
Ciò consente di trasmettere i dati in blocchi anziché caricare l'intero payload in memoria contemporaneamente.
La seguente funzione JavaScript mostra il codice lato client per
caricare in modo efficiente un file binario di grandi dimensioni dall'applicazione nativa utilizzando una
chiamata fetch() standard a un URL segnaposto personalizzato.
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);
}
}
I seguenti esempi di codice mostrano il lato dell'app nativa, utilizzando il metodo WebViewClient.shouldInterceptRequest sia in Kotlin che in Java, per trasmettere in streaming un file binario di grandi dimensioni intercettando un URL segnaposto personalizzato richiesto dai contenuti web.
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);
}
});
Seguire i consigli per la sicurezza
Per proteggere l'applicazione e i dati degli utenti, segui queste linee guida quando implementi un bridge:
Applica HTTPS: per assicurarti che i contenuti di terze parti dannosi non possano richiamare la logica nativa della tua applicazione, consenti solo la comunicazione con origini sicure.
Affidati alle regole di origine: il modo migliore per gestire l'attendibilità è definire rigorosamente il tuo
allowedOriginRulese controllare ilsourceOriginfornito nel callback del messaggio. Evita di utilizzare il carattere jolly completo (*), che corrisponde a tutte le origini, come unica regola di origine, a meno che non sia assolutamente necessario. L'utilizzo di caratteri jolly per i sottodomini (ad esempio,*.example.com) rimane valido e sicuro per la corrispondenza con più sottodomini (ad esempio,foo.example.com,bar.example.com).Nota: mentre le regole di origine proteggono da siti web di terze parti dannosi e da iframe nascosti, non possono proteggere dalle vulnerabilità cross-site scripting (XSS) all'interno del tuo dominio attendibile. Ad esempio, se la tua pagina web mostra contenuti generati dagli utenti ed è vulnerabile a XSS memorizzato, un malintenzionato potrebbe eseguire uno script che funge da origine attendibile. Valuta la possibilità di applicare la convalida ai payload dei messaggi prima di eseguire operazioni sensibili della piattaforma nativa.
Ridurre al minimo la superficie di attacco: esponi solo i metodi o i dati specifici richiesti dalla pagina web.
Controlla le funzionalità in fase di runtime: le API bridge recenti, tra cui
addWebMessageListener, fanno parte della libreria Jetpack Webkit. Pertanto, controlla sempre se è disponibile l'assistenza tramiteWebViewFeature.isFeatureSupported()prima di chiamare.