Auf dieser Seite werden die verschiedenen Methoden und Best Practices zum Einrichten einer nativen Bridge (auch JavaScript-Bridge genannt) beschrieben, um die Kommunikation zwischen Webinhalten in einem WebView und einer Host-Android-App zu ermöglichen.
So können Webentwickler mit JavaScript auf native Plattformfunktionen wie die Kamera, das Dateisystem oder erweiterte Hardwaresensoren zugreifen, die von Standard-Web-APIs normalerweise nicht bereitgestellt werden.
Anwendungsfälle
Eine JavaScript-Bridge-Implementierung ermöglicht verschiedene Integrationsszenarien, in denen Webinhalte einen tieferen Zugriff auf das Android-Betriebssystem benötigen. Hier einige Beispiele:
- Plattformintegration: Auslösen nativer Android-UI-Komponenten (z. B. biometrische Aufforderungen,
BottomSheetDialog) über eine Webseite. - Leistung: Auslagern rechenintensiver Aufgaben an nativen Java- oder Kotlin-Code.
- Datenpersistenz: Zugriff auf lokale verschlüsselte Datenbanken oder gemeinsame Einstellungen.
- Übertragung großer Datenmengen: Übertragung von Mediendateien oder komplexen Datenstrukturen zwischen der App und dem Web-Renderer.
Kommunikationsmechanismen
Android bietet drei Hauptgenerationen von APIs, um eine native Brücke zu schaffen. Sie sind zwar alle noch verfügbar, unterscheiden sich aber erheblich in Bezug auf Sicherheit, Nutzerfreundlichkeit und Leistung.
addWebMessageListener verwenden (empfohlen)
addWebMessageListener ist der modernste und empfohlene Ansatz für die Kommunikation zwischen Webinhalten und systemeigenem App-Code. Es kombiniert die Benutzerfreundlichkeit der JavaScript-Schnittstelle mit der Sicherheit des Messaging-Systems.
So funktioniert es: Die App fügt einen Listener mit einem bestimmten Namen und einer Reihe von Regeln für zulässige Herkünfte hinzu. Die WebView sorgt dann dafür, dass das JavaScript-Objekt ab dem Moment, in dem die Seite geladen wird, im globalen Bereich (window.objectName) vorhanden ist.
Initialisierung: Damit das JavaScript-Objekt von WebView eingefügt wird, bevor ein Skript ausgeführt wird, müssen Sie addWebMessageListener aufrufen, bevor Sie zur Seite navigieren (z. B. durch Aufrufen von WebViewCompat.navigate oder loadUrl).
Wichtige Funktionen:
Sicherheit und Vertrauen: Im Gegensatz zu Legacy-APIs ist für diese Methode bei der Initialisierung ein
Set<String>vonallowedOriginRuleserforderlich. Dies ist der primäre Mechanismus zum Aufbau von Vertrauen.Wenn Sie einen vertrauenswürdigen Ursprung wie
https://example.comangeben, sorgt die WebView dafür, dass die eingefügten JavaScript-Objekte nur für Webseiten verfügbar sind, die von diesem Ursprung geladen werden.Der Callback des nativen Listeners empfängt mit jeder Nachricht einen
sourceOrigin-Parameter. So können Sie den genauen Ursprung des Absenders überprüfen, wenn Ihre Bridge mehrere zulässige Ursprünge unterstützt.Da diese Ursprungsprüfungen auf Plattformebene streng durch WebView erzwungen werden, kann sich Ihre App in der Regel darauf verlassen, dass Nachrichten, die von einem vertrauenswürdigen
sourceOriginempfangen werden, wahrheitsgemäß sind. In den meisten Standardimplementierungen ist daher keine strenge Nutzlastvalidierung erforderlich.- WebView vergleicht Regeln mit dem Schema (HTTP/HTTPS), dem Host und dem Port.
- WebView ignoriert Pfade. Beispiel:
https://example.comerlaubthttps://example.com/loginundhttps://example.com/home. - In WebView sind Platzhalter für Subdomains streng auf den Anfang des Hosts beschränkt. Beispielsweise führt
https://*.example.comzu Übereinstimmungen mithttps://foo.example.com, aber nicht mithttps://example.com. Wenn Sie sowohlhttps://example.comals auch die zugehörigen Subdomains abgleichen möchten, müssen Sie jede Ursprungsregel separat zur Zulassungsliste hinzufügen (z. B."https://example.com", "https://*.example.com"). Sie können keine Platzhalter für das Schema oder in der Mitte einer Domain verwenden.
Dadurch wird die Bridge auf verifizierte Domains beschränkt und verhindert, dass nicht autorisierte Inhalte von Dritten oder eingefügte iFrames nativen Code ausführen.
Unterstützung mehrerer Frames: Funktioniert in allen Frames, die den Ursprungsregeln entsprechen.
Threading: Der Listener-Callback wird im Hauptthread (UI) der Anwendung ausgeführt. Wenn Ihre Bridge komplexe Datenverarbeitung, JSON-Parsing oder Datenbankabfragen ausführen muss, müssen Sie diese Arbeit auf einen Hintergrundthread auslagern, um zu verhindern, dass die Benutzeroberfläche der Anwendung mit einem ANR-Fehler („App antwortet nicht“) einfriert.
Bidirektional: Wenn die Webseite eine Nachricht sendet, empfängt die App ein
JavaScriptReplyProxy, mit dem sie Nachrichten an diesen bestimmten Frame zurücksenden kann. Sie können diesesreplyProxy-Objekt beibehalten und jederzeit verwenden, um eine beliebige Anzahl von Nachrichten an die Seite zu senden. Sie müssen also nicht auf jede einzelne Nachricht antworten, die die Seite sendet. Wenn der ursprüngliche Frame navigiert oder zerstört wird, werden Nachrichten, die mitpostMessage()auf dem Proxy gesendet werden, stillschweigend ignoriert.Initiierung auf App-Seite: Der Kommunikationskanal mit der App muss zwar immer von der Webseite initiiert werden, die systemeigene App kann die Webseite jedoch einseitig auffordern, diesen Prozess zu starten. Die systemeigene App kann über
addDocumentStartJavaScript()(zum Auswerten von JavaScript vor dem Laden der Seite) oderevaluateJavaScript()(zum Auswerten von JavaScript nach dem Laden der Seite) mit der Webseite kommunizieren.
Einschränkung: Diese API sendet Daten entweder als Strings oder als byte[]-Arrays. Bei komplexeren Datenstrukturen wie JSON-Objekten müssen Sie diese in eines dieser Formate serialisieren und dann auf der anderen Seite deserialisieren, um die Datenstruktur zu rekonstruieren.
Verwendungsbeispiel:
Die Ereignisse eines bidirektionalen Nachrichtenaustauschs laufen in dieser Reihenfolge ab:
- Initiierung (App): Die systemeigene App registriert den Listener mit
addWebMessageListenerund initiiert die Seitennavigation (z. B. mitWebViewCompat.navigateoderloadUrl). - Nachricht senden (Web): Die JavaScript-Aufrufe der Webseite
myObject.postMessage(message)initiieren die Kommunikation. - Nachricht empfangen und beantworten (App): Die App empfängt die Nachricht im Listener-Callback und antwortet mit dem bereitgestellten
replyProxy.postMessage(). - Antwort empfangen (Web): Die Webseite empfängt die asynchrone Antwort in der Callback-Funktion
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);
}
Das folgende JavaScript zeigt die clientseitige Implementierung von addWebMessageListener. So können die Webinhalte Nachrichten von der systemeigenen App empfangen und eigene Nachrichten über den myObject-Proxy senden.
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
postWebMessage (Alternative) verwenden
Android hat diese Funktion eingeführt, um eine asynchrone, auf Messaging basierende Alternative ähnlich dem window.postMessage im Web bereitzustellen.
Funktionsweise: Die App verwendet WebViewCompat.postWebMessage, um eine Nutzlast an den Hauptframe der Webseite zu senden. Um einen bidirektionalen Kommunikationskanal einzurichten, können Sie ein WebMessageChannel erstellen und einen seiner Ports mit der Nachricht an den Webinhalt übergeben.
Merkmale:
- Asynchron: Wie bei
addWebMessageListenerwird bei dieser Methode asynchrones Messaging verwendet. Dadurch bleibt die Webseite für Nutzerinteraktionen reaktionsfähig, während die App Daten im Hintergrund verarbeitet. - Ursprungsbezogen: Sie können einen
targetOriginangeben, damit die WebView Daten nur an eine vertrauenswürdige Website sendet.
Beschränkungen:
- Bereich: Diese API beschränkt die Kommunikation auf den Hauptframe. Es unterstützt nicht das direkte Ansprechen oder Senden von Nachrichten an iFrames.
- URI-Einschränkungen: Diese Methode kann nicht für Inhalte verwendet werden, die über
data:-URIs,file:-URIs oderloadData()geladen werden, es sei denn, Sie geben „*“ als Zielursprung an. So kann die Nachricht auf jeder Seite empfangen werden. - Identitätsrisiko: Es gibt keine eindeutige Möglichkeit, die Identität des Absenders anhand der Webinhalte zu überprüfen. Eine Nachricht, die die Webseite empfängt, kann von Ihrer systemeigenen App oder einem anderen iFrame stammen.
Verwenden Sie diese Methode, wenn Sie einen einfachen, asynchronen Kanal für stringbasierte Daten in älteren Android-Versionen benötigen, die addWebMessageListener nicht unterstützen.
addJavascriptInterface (Legacy) verwenden
Bei der ältesten Methode wird eine Instanz eines nativen Objekts direkt in die WebView eingefügt.
Funktionsweise: Sie definieren eine Kotlin- oder Java-Klasse, versehen die zulässigen Methoden mit der Annotation @JavascriptInterface und fügen dem WebView mit addJavascriptInterface(Object, String) eine Instanz der Klasse hinzu.
Merkmale:
- Synchron: Die JavaScript-Ausführungsumgebung wird blockiert, bis die Methode in Ihrem Android-Code zurückgegeben wird.
- Threadsicherheit: Das System ruft Methoden in einem Hintergrundthread auf. Daher ist eine sorgfältige Synchronisierung auf der Kotlin- oder Java-Seite erforderlich.
- Sicherheitsrisiko: Standardmäßig ist
addJavascriptInterfacefür jeden Frame innerhalb der WebView verfügbar, auch für iFrames. Es fehlt die ursprungsbasierte Zugriffssteuerung. Aufgrund des asynchronen Verhaltens von WebView ist es nicht möglich, die URL des Frames, der Ihr Interface aufruft, sicher zu ermitteln. Sie dürfen sich nicht auf Methoden wieWebView.getUrl()zur Sicherheitsüberprüfung verlassen, da sie nicht garantiert genau sind und nicht angeben, welcher Frame die Anfrage gestellt hat.
Datentyp-Konvertierungen und ‑Umwandlungen
Wenn Sie addJavascriptInterface verwenden, werden Datentypen von der Chromium-basierten Java Bridge zwischen der JavaScript-Laufzeit und Ihrem Android-App-Code konvertiert.
Für Methodenparameter und Rückgabewerte gelten die folgenden Regeln für die Typumwandlung.
Zuordnung von Parametertypen (JavaScript zu Java)
Wenn JavaScript Argumente an eine annotierte Java- oder Kotlin-Methode übergibt, wandelt die Bridge JavaScript-Werte in die entsprechenden Java-Parametertypen um:
| Java-Parametertyp | JavaScript-Argumentwert | Verhalten bei Erzwingen |
|---|---|---|
byte, short, int, long |
Zahl (Ganzzahl) | Werte werden in den Ziel-Ganzzahltyp umgewandelt. Werte außerhalb des zulässigen Bereichs werden gemäß den Standardregeln für die numerische Typumwandlung umgebrochen. |
byte, short, int, long |
NaN |
Wird zu 0 konvertiert. |
byte, short, int, long |
Infinity |
Wird für byte und short in -1 umgewandelt oder für int und long in Integer.MAX_VALUE und Long.MAX_VALUE. |
float, double |
Zahl | Wird in den entsprechenden Java-Gleitkommawert umgewandelt. |
float, double |
NaN/Infinity |
Wird in Float.NaN, Double.NaN, Float.POSITIVE_INFINITY oder Double.POSITIVE_INFINITY umgewandelt. |
char |
Zahl (Ganzzahl) | In den entsprechenden Unicode-Codepunkt konvertiert. |
char |
Nicht ganzzahlig, NaN, Infinity |
Wird zu \u0000 konvertiert. |
boolean |
true/false |
Wird in Java true oder false umgewandelt. |
boolean |
Zahl, String, Objekt | Wird in false umgewandelt (einschließlich nicht leerer Strings und Zahlen ungleich null). |
String |
String | Der Stringwert wird beibehalten. |
String |
Zahl, boolescher Wert | Als Stringdarstellung formatiert (z. B. "42", "true", "false"). |
String |
null/undefined |
null wird zu Java-null konvertiert; undefined wird zum Literalstring "undefined" konvertiert. |
String |
Object, ArrayBuffer, TypedArray | Wird in den Literalstring "undefined" umgewandelt. |
Einfaches Array (z. B. int[], byte[], boolean[]) oder String[] |
Array ([...]) |
Konvertiert in ein eindimensionales Java-Array des Zielelementtyps. In dünn besetzten Arrays werden nicht zugewiesene Indexe mit Standardwerten (0, false, null) gefüllt. |
Einfaches Array (z. B. int[], byte[]) |
TypedArray (Int8Array, Uint8Array, Int32Array, Float64Array) |
Elemente werden in das entsprechende einfache Java-Array umgewandelt. |
Mehrdimensionales Array (z. B. int[][]) |
Verschachteltes Array ([[...]]) |
Nicht unterstützt. Mehrdimensionale Array-Parameter werden mit null ausgewertet. |
ArrayBuffer, DataView |
ArrayBuffer, DataView |
Nicht als Arrays unterstützt. ArrayBuffer- und DataView-Instanzen werden als null ausgewertet. |
Object oder benutzerdefinierte Klasse |
JavaScript-Objekt ({...}) |
Nicht unterstützt. Beliebige JavaScript-Objektliterale ergeben in Java null. |
Object oder benutzerdefinierte Klasse |
Eingefügter Java-Objekt-Wrapper | Unterstützt (Roundtrip) Übergibt die zugrunde liegende Java-Instanz an die Java-Methode. Löst eine JavaScript-Ausnahme aus, wenn der Java-Typ nicht mit der Parametersignatur übereinstimmt. |
Boxed-Typen (z. B. Integer, Double, Boolean) |
Zahl, boolescher Wert | Nicht unterstützt. Geboxten primitiven Typen werden als undurchsichtige Objekte behandelt und ergeben null. |
| Beliebiger primitiver Typ | null/undefined |
Wird auf Standardwerte (0, 0.0, \u0000, false) umgestellt. |
Object, String, Array |
null |
Wird in Java null umgewandelt. |
Zuordnung von Rückgabetypen (Java zu JavaScript)
Wenn eine annotierte Java- oder Kotlin-Methode einen Wert zurückgibt, wird dieser von der Bridge in einen JavaScript-Typ konvertiert:
| Java-Rückgabetyp | JavaScript-Wert | JavaScript typeof |
|---|---|---|
boolean |
true/false |
"boolean" |
byte, short, int, long, float, double |
Zahl | "number" |
char |
Zahl (Unicode-Codepunkt) | "number" |
String (nicht null) |
Stringwert | "string" |
String (null) |
undefined |
"undefined" |
void |
undefined |
"undefined" |
Java-Array (z. B. int[], String[]) |
undefined |
"undefined". Array-Rückgabewerte werden nicht unterstützt. Die Java-Methode wird nicht ausgeführt und undefined wird zurückgegeben, ohne dass eine Ausnahme ausgelöst wird. |
| Java-Objekt / benutzerdefinierter Typ (nicht null) | Objekt-Wrapper | "object": Erstellt einen JavaScript-Wrapper für die Java-Instanz. JavaScript-Code kann jede öffentliche Methode für dieses Objekt aufrufen, die mit @JavascriptInterface annotiert ist. |
Java-Objekt / benutzerdefinierter Typ (null) |
null |
"object" |
Gepackter primitiver Typ (z. B. Integer, Double) |
Objekt-Wrapper | "object": Wird als undurchsichtiger Java-Objekt-Wrapper ohne zugängliche @JavascriptInterface-Methoden zurückgegeben, sodass der Wert in JavaScript nicht verwendet werden kann. |
Methoden- und Mitgliederzugänglichkeit
Die JavaScript-Bridge erzwingt strenge Regeln für den Zugriff auf Elemente und die Sichtbarkeit, um vor unbeabsichtigter Code-Ausführung zu schützen:
- Felder werden nicht verfügbar gemacht: Auf Java-Felder (einschließlich
public- undpublic final-Felder) kann nicht über JavaScript zugegriffen werden. Sie werden alsundefinedausgewertet. - Anforderung für Anmerkungen: Nur Methoden, die explizit mit
@JavascriptInterfaceannotiert sind, werden für JavaScript verfügbar gemacht. - Sichtbarkeitseinschränkungen: Methoden müssen
publicsein. Die Methodenprivateundprotectedwerden nie für JavaScript verfügbar gemacht, auch wenn sie die Annotation@JavascriptInterfaceenthalten. - Statische Methoden: Statische Methoden, die mit
@JavascriptInterfaceannotiert sind, können über JavaScript aufgerufen werden. - Übernahme und Überschreiben:
@JavascriptInterface-Annotationen werden nicht übernommen, wenn eine Unterklasse eine Methode überschreibt. Wenn eine Unterklasse eine annotierte Methode aus einer Basisklasse überschreibt, muss die Unterklasse die@JavascriptInterface-Annotation explizit in die überschriebene Methode einfügen, um sie für JavaScript verfügbar zu machen. Nicht überschriebene öffentliche Methoden, die von einer Basisklasse übernommen wurden, bleiben zugänglich, wenn sie in der Basisklasse annotiert sind. - Schutz vor Reflection: Standardmäßige Java-Reflection-Methoden (z. B.
getClass()) werden blockiert und lösen eine JavaScript-Ausnahme aus, um Sicherheitslücken bei der Remote-Codeausführung zu verhindern. - Methodenüberladung: Überladene Java-Methoden werden unterstützt. Die Methode „bridge“ löst Methodenaufrufe nur anhand der Anzahl der übergebenen Argumente auf und berücksichtigt keine Argumenttypen. Wenn Sie eine überladene Methode mit einer ungültigen Anzahl von Argumenten aufrufen, wird eine JavaScript-Ausnahme ausgelöst. Wenn zwei Überladungen dieselbe Anzahl von Argumenten haben, wird eine davon beliebig ausgewählt.
Zusammenfassung der Mechanismen
Die folgende Tabelle enthält einen kurzen Vergleich der drei primären nativen Bridge-Implementierungsmechanismen:
| Methode | addWebMessageListener |
postWebMessage |
addJavascriptInterface |
|---|---|---|---|
| Implementierung | Asynchron (Listener im Hauptthread) | Asynchron | Synchron |
| Sicherheit | Höchstwert (zulassungslistenbasiert) | Hoch (ursprungsbezogen) | Niedrig (keine Ursprungsüberprüfungen) |
| Komplexität | Moderat | Moderat | Einfach |
| Richtung | Bidirektional | Bidirektional | Web-zu-App |
| WebView-Mindestversion | Version 82 (und Jetpack Webkit 1.3.0) | Version 45 (und Jetpack Webkit 1.1.0) | Alle Versionen |
| Empfohlen | Ja | Nein | Nein |
Große Datenübertragungen verarbeiten
Sie müssen den Arbeitsspeicher sorgfältig verwalten, wenn Sie große Nutzlasten wie Strings mit mehreren Megabyte oder Binärdateien übertragen, um ANR-Fehler (App antwortet nicht) oder Abstürze auf 32-Bit-Geräten zu vermeiden. In diesem Abschnitt werden die verschiedenen Techniken und Einschränkungen im Zusammenhang mit der Übertragung großer Datenmengen zwischen der Hostanwendung und Webinhalten behandelt.
Binärdaten mit Bytearrays übertragen
Mit der Klasse WebMessageCompat können Sie byte[]-Arrays direkt senden, anstatt Binärdaten in Base64-Strings zu serialisieren. Da Base64 die Datengröße um etwa 33% erhöht, ist diese Methode deutlich speichereffizienter und schneller.
- Binärvorteil: Binärdaten wie Bild- oder Audiodateien zwischen Ihrer systemeigenen App und Webinhalten übertragen.
- Einschränkung: Auch bei Byte-Arrays kopiert das System Daten über die IPC-Grenze (Inter-Process Communication) zwischen der App und dem isolierten Prozess, den WebView zum Rendern der Webinhalte verwendet. Bei sehr großen Dateien wird dadurch aber immer noch viel Arbeitsspeicher belegt.
Die folgenden Codebeispiele zeigen, wie Sie addWebMessageListener auf der Seite der systemeigenen App einrichten, um mit WebMessageCompat.TYPE_ARRAY_BUFFER gekennzeichnete Nachrichten zu empfangen und optional mit Binärdaten zu antworten, indem Sie nach WebViewFeature.MESSAGE_ARRAY_BUFFER suchen.
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
);
}
}
Der folgende JavaScript-Code zeigt die clientseitige Implementierung von addWebMessageListener. Damit können die Webinhalte mithilfe des im vorherigen Beispiel eingefügten window.myBridge-Proxys Binärdaten (ArrayBuffer) an die systemeigene App senden und von ihr empfangen.
// 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]);
}
};
}
Effizientes Laden großer Datenmengen
Verwenden Sie für sehr große Dateien (> 10 MB) die Methode shouldInterceptRequest, um Daten zu streamen:
- Die Webseite initiiert einen
fetch()-Aufruf an eine benutzerdefinierte Platzhalter-URL. Beispiel:https://app.local/large-file - Die Android-App fängt diese Anfrage in
WebViewClient.shouldInterceptRequest ab. - Die App gibt die Daten als
InputStreamzurück.
Dadurch können Daten in Chunks gestreamt werden, anstatt die gesamte Nutzlast auf einmal in den Arbeitsspeicher zu laden.
Die folgende JavaScript-Funktion zeigt den clientseitigen Code zum effizienten Laden einer großen Binärdatei aus der nativen Anwendung mit einem Standardaufruf von fetch() an eine benutzerdefinierte Platzhalter-URL.
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);
}
}
Die folgenden Codebeispiele zeigen die Seite der systemeigenen App. Dabei wird die Methode WebViewClient.shouldInterceptRequest in Kotlin und Java verwendet, um eine große Binärdatei zu streamen, indem eine benutzerdefinierte Platzhalter-URL abgefangen wird, die vom Webinhalt angefordert wird.
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);
}
});
Sicherheitsempfehlungen befolgen
Beachten Sie die folgenden Richtlinien, um Ihre Anwendung und Nutzerdaten zu schützen, wenn Sie eine Bridge implementieren:
HTTPS erzwingen: Damit schädliche Inhalte von Dritten nicht die native Logik Ihrer Anwendung aufrufen können, sollten Sie nur die Kommunikation mit sicheren Ursprüngen zulassen.
Auf Ursprungsregeln verlassen: Die beste Methode, um mit Vertrauen umzugehen, besteht darin, Ihre
allowedOriginRulesstreng zu definieren und die im Nachrichten-Callback bereitgestelltesourceOriginzu prüfen. Vermeiden Sie es, den vollständigen Platzhalter (*), der allen Ursprüngen entspricht, als einzige Ursprungsregel zu verwenden, es sei denn, dies ist unbedingt erforderlich. Die Verwendung von Platzhaltern für Subdomains (z. B.*.example.com) ist weiterhin gültig und sicher, um mehrere Subdomains (z. B.foo.example.com,bar.example.com) abzugleichen.Hinweis: Ursprungsregeln schützen zwar vor schädlichen Websites von Drittanbietern und verborgenen iFrames, aber nicht vor Cross-Site-Scripting-Schwachstellen (XSS) in Ihrer eigenen vertrauenswürdigen Domain. Wenn auf Ihrer Webseite beispielsweise von Nutzern erstellte Inhalte angezeigt werden und sie anfällig für gespeichertes XSS ist, könnte ein Angreifer ein Script ausführen, das als vertrauenswürdiger Ursprung fungiert. Erwägen Sie, die Nachrichtennutzlasten zu validieren, bevor Sie sensible native Plattformvorgänge ausführen.
Angriffsfläche minimieren: Stellen Sie nur die Methoden oder Daten bereit, die die Webseite benötigt.
Funktionen zur Laufzeit prüfen: Die aktuellen Bridge-APIs, einschließlich
addWebMessageListener, sind Teil der Jetpack Webkit-Bibliothek. Prüfen Sie also immer, ob Sie überWebViewFeature.isFeatureSupported()Support erhalten können, bevor Sie anrufen.