Halaman ini membahas berbagai metode dan praktik terbaik untuk membuat jembatan native, yang juga dikenal sebagai jembatan JavaScript, untuk memfasilitasi komunikasi antara konten web di WebView dan aplikasi Android host.
Hal ini memungkinkan developer web menggunakan JavaScript untuk mengakses fitur platform native, seperti kamera, sistem file, atau sensor hardware canggih, yang biasanya tidak disediakan oleh API web standar.
Kasus penggunaan
Implementasi jembatan JavaScript memungkinkan berbagai skenario integrasi saat konten web memerlukan akses yang lebih dalam ke sistem operasi Android. Berikut beberapa contohnya:
- Integrasi platform: Memicu komponen UI Android native (misalnya, dialog Biometrik,
BottomSheetDialog) dari halaman web. - Performa: Mengurangi beban tugas komputasi berat ke kode Java atau Kotlin native.
- Persistensi data: Mengakses database terenkripsi lokal atau preferensi bersama.
- Transfer data besar: Meneruskan file media atau struktur data yang kompleks antara aplikasi dan perender web.
Mekanisme komunikasi
Android menawarkan tiga generasi utama API untuk membuat jembatan native. Meskipun semuanya masih tersedia, ketiganya memiliki perbedaan signifikan dalam keamanan, kegunaan, dan performa.
Gunakan addWebMessageListener (Direkomendasikan)
addWebMessageListener adalah pendekatan paling modern dan direkomendasikan untuk
komunikasi antara konten web dan kode aplikasi native. API ini menggabungkan kemudahan penggunaan antarmuka JavaScript dengan keamanan sistem pesan.
Cara kerjanya: Aplikasi menambahkan pemroses dengan nama tertentu dan serangkaian aturan asal yang diizinkan. Kemudian, WebView memastikan objek JavaScript ada dalam cakupan global (window.objectName) sejak halaman mulai dimuat.
Inisialisasi: Untuk memastikan WebView menyuntikkan objek JavaScript sebelum
skrip apa pun berjalan, Anda harus memanggil addWebMessageListener sebelum membuka
halaman (seperti memanggil WebViewCompat.navigate atau loadUrl).
Fitur utama:
Keamanan dan kepercayaan: Tidak seperti API lama, metode ini memerlukan
Set<String>allowedOriginRulesselama inisialisasi. Ini adalah mekanisme utama untuk membangun kepercayaan.Saat Anda menentukan asal tepercaya, seperti
https://example.com, WebView menjamin bahwa WebView hanya mengekspos objek JavaScript yang disisipkan ke halaman web yang dimuat dari asal yang sama persis tersebut.Callback pemroses native menerima parameter
sourceOrigindengan setiap pesan. Anda dapat menggunakan ini untuk memverifikasi asal pengirim yang tepat jika jembatan Anda mendukung beberapa asal yang diizinkan.Karena WebView secara ketat menerapkan pemeriksaan asal ini di tingkat platform, aplikasi Anda umumnya dapat mengandalkan pesan yang diterima dari
sourceOrigintepercaya sebagai benar, sehingga tidak perlu validasi payload yang ketat dalam sebagian besar penerapan standar.- WebView mencocokkan aturan dengan skema (HTTP/HTTPS), host, dan port.
- WebView mengabaikan jalur. Misalnya,
https://example.commengizinkanhttps://example.com/logindanhttps://example.com/home. - WebView membatasi kartu pengganti secara ketat di awal host untuk subdomain. Misalnya,
https://*.example.comcocok denganhttps://foo.example.com, tetapi tidak denganhttps://example.com. Jika Anda perlu mencocokkanhttps://example.comdan subdomainnya, Anda harus menambahkan setiap aturan asal secara terpisah ke daftar yang diizinkan (misalnya,"https://example.com", "https://*.example.com"). Anda tidak dapat menggunakan karakter pengganti untuk skema atau di tengah domain.
Hal ini membatasi jembatan ke domain terverifikasi, sehingga mencegah konten pihak ketiga yang tidak sah atau iframe yang disisipkan agar tidak mengeksekusi kode native.
Dukungan multi-frame: Berfungsi di semua frame yang cocok dengan aturan asal.
Threading: Callback pemroses berjalan di thread utama (UI) aplikasi. Jika jembatan Anda perlu menangani pemrosesan data yang kompleks, parsing JSON, atau pencarian database, Anda harus memindahkan pekerjaan tersebut ke thread latar belakang untuk mencegah pembekuan UI aplikasi dengan error "aplikasi tidak merespons" (ANR).
Bidireksional: Saat halaman web mengirim pesan, aplikasi menerima
JavaScriptReplyProxyyang dapat digunakan untuk mengirim pesan kembali ke frame tertentu tersebut. Anda dapat mempertahankan objekreplyProxyini dan menggunakannya kapan saja untuk mengirim sejumlah pesan ke halaman, bukan hanya untuk membalas setiap pesan individual yang dikirim halaman. Jika frame asal keluar atau dihancurkan, pesan yang dikirim menggunakanpostMessage()di proxy akan diabaikan tanpa pemberitahuan.Inisiasi sisi aplikasi: Meskipun halaman web harus selalu memulai saluran komunikasi dengan aplikasi, aplikasi native dapat secara sepihak meminta halaman web untuk memulai proses ini. Aplikasi native dapat berkomunikasi dengan halaman web dengan
addDocumentStartJavaScript()(untuk mengevaluasi JavaScript sebelum halaman dimuat) atauevaluateJavaScript()(untuk mengevaluasi JavaScript setelah halaman dimuat).
Batasan: API ini mengirim data sebagai string atau array byte[]. Untuk
struktur data yang lebih rumit, seperti objek JSON, Anda harus melakukan serialisasi
ke salah satu format tersebut, lalu melakukan deserialisasi di sisi lain untuk merekonstruksi
struktur data.
Contoh penggunaan:
Untuk memahami urutan lengkap pertukaran pesan dua arah, peristiwa berlanjut dalam urutan ini:
- Inisiasi (aplikasi): Aplikasi native mendaftarkan pemroses dengan
addWebMessageListenerdan memulai navigasi halaman (seperti denganWebViewCompat.navigateatauloadUrl). - Pengiriman pesan (web): JavaScript halaman web memanggil
myObject.postMessage(message)untuk memulai komunikasi. - Penerimaan dan balasan pesan (aplikasi): Aplikasi menerima pesan di callback
listener dan membalas menggunakan
replyProxy.postMessage()yang diberikan. - Penerimaan balasan (web): Halaman web menerima balasan asinkron dalam
fungsi 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);
}
JavaScript berikut menunjukkan penerapan addWebMessageListener di sisi klien, yang memungkinkan konten web menerima pesan dari aplikasi native dan mengirim pesannya sendiri melalui proxy myObject.
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
Menggunakan postWebMessage (Alternatif)
Android memperkenalkan ini untuk menyediakan alternatif berbasis pesan asinkron
yang mirip dengan window.postMessage web.
Cara kerjanya: Aplikasi menggunakan WebViewCompat.postWebMessage untuk mengirim payload
ke frame utama halaman web. Untuk membuat saluran komunikasi dua arah, Anda dapat membuat WebMessageChannel dan meneruskan salah satu port-nya dengan pesan ke konten web.
Karakteristik:
- Asinkron: Seperti
addWebMessageListener, metode ini menggunakan pesan asinkron, yang memastikan halaman web tetap responsif terhadap interaksi pengguna saat aplikasi memproses data di latar belakang. - Mengenali asal: Anda dapat menentukan
targetOriginuntuk memastikan WebView hanya mengirimkan data ke situs tepercaya.
Batasan:
- Cakupan: API ini membatasi komunikasi ke frame utama. Fitur ini tidak mendukung pengiriman pesan atau pengalamatan langsung ke iframe.
- Batasan URI: Anda tidak dapat menggunakan metode ini untuk konten yang dimuat menggunakan URI
data:, URIfile:, atauloadData(), kecuali jika Anda menentukan "*" sebagai asal target. Dengan melakukannya, halaman mana pun dapat menerima pesan. - Risiko identitas: Tidak ada cara yang jelas bagi konten web untuk memverifikasi identitas pengirim. Pesan yang diterima halaman web dapat berasal dari aplikasi native Anda atau iframe lain.
Gunakan metode ini jika Anda memerlukan saluran asinkron sederhana untuk data berbasis string di
versi Android sebelumnya yang tidak mendukung addWebMessageListener.
Menggunakan addJavascriptInterface (Lama)
Metode terlama melibatkan penyuntikan instance objek native langsung ke WebView.
Cara kerjanya: Anda menentukan class Kotlin atau Java, memberi anotasi pada metode yang diizinkan dengan @JavascriptInterface, dan menambahkan instance class ke WebView menggunakan addJavascriptInterface(Object, String).
Karakteristik:
- Sinkron: Lingkungan eksekusi JavaScript diblokir hingga metode dalam kode Android Anda ditampilkan.
- Keamanan thread: Sistem memanggil metode di thread latar belakang, yang memerlukan sinkronisasi yang cermat di sisi Kotlin atau Java.
- Risiko keamanan: Secara default,
addJavascriptInterfacetersedia untuk setiap frame dalam WebView, termasuk iframe. Tidak memiliki kontrol akses berbasis origin. Karena perilaku asinkron WebView, Anda tidak dapat menentukan URL frame yang memanggil antarmuka Anda dengan aman. Anda tidak boleh mengandalkan metode sepertiWebView.getUrl()untuk verifikasi keamanan, karena metode tersebut tidak dijamin akurat dan tidak menunjukkan frame tertentu yang membuat permintaan.
Konversi dan paksaan jenis data
Saat menggunakan addJavascriptInterface, Java Bridge berbasis Chromium mengonversi
tipe data antara runtime JavaScript dan kode aplikasi Android Anda.
Aturan paksaan berikut berlaku untuk parameter metode dan nilai yang ditampilkan.
Pemetaan jenis parameter (JavaScript ke Java)
Saat JavaScript meneruskan argumen ke metode Java atau Kotlin yang diberi anotasi, jembatan akan memaksa nilai JavaScript menjadi jenis parameter Java yang sesuai:
| Jenis parameter Java | Nilai argumen JavaScript | Perilaku pemaksaan |
|---|---|---|
byte, short, int, long |
Angka (bilangan bulat) | Nilai ditransmisikan ke jenis bilangan bulat target. Nilai di luar batas akan di-wrap sesuai dengan aturan transmisi numerik standar. |
byte, short, int, long |
NaN |
Memaksa ke 0. |
byte, short, int, long |
Infinity |
Memaksa ke -1 untuk byte dan short, atau Integer.MAX_VALUE dan Long.MAX_VALUE untuk int dan long. |
float, double |
Angka | Melakukan paksaan ke nilai floating point Java yang sesuai. |
float, double |
NaN/Infinity |
Memaksa ke Float.NaN, Double.NaN, Float.POSITIVE_INFINITY, atau Double.POSITIVE_INFINITY. |
char |
Angka (bilangan bulat) | Dikonversi ke poin kode Unicode yang sesuai. |
char |
Non-integer, NaN, Infinity |
Memaksa ke \u0000. |
boolean |
true/false |
Memaksa ke Java true atau false. |
boolean |
Angka, String, Objek | Memaksa ke false (termasuk string yang tidak kosong dan angka bukan nol). |
String |
String | Nilai string dipertahankan. |
String |
Angka, Boolean | Diformat sebagai representasi string (misalnya, "42", "true", "false"). |
String |
null/undefined |
null dipaksa menjadi null Java; undefined dipaksa menjadi string literal "undefined". |
String |
Object, ArrayBuffer, TypedArray | Memaksa ke string literal "undefined". |
Array primitif (seperti int[], byte[], boolean[]) atau String[] |
Array ([...]) |
Dikonversi menjadi array Java 1D dari jenis elemen target. Array sparse mengisi indeks yang tidak ditetapkan dengan nilai default (0, false, null). |
Array primitif (seperti int[], byte[]) |
TypedArray (Int8Array, Uint8Array, Int32Array, Float64Array) |
Elemen dipaksa menjadi array primitif Java yang sesuai. |
Array multi-dimensi (seperti int[][]) |
Array bertingkat ([[...]]) |
Tidak didukung. Parameter array multidimensi dievaluasi menjadi null. |
ArrayBuffer, DataView |
ArrayBuffer, DataView |
Tidak didukung sebagai array. Instance ArrayBuffer dan DataView dievaluasi ke null. |
Object atau class kustom |
Objek JavaScript ({...}) |
Tidak didukung. Literal objek JavaScript arbitrer dievaluasi menjadi null di Java. |
Object atau class kustom |
Wrapper objek Java yang disuntikkan | Didukung (Round-tripping). Meneruskan instance Java pokok ke metode Java. Memunculkan pengecualian JavaScript jika jenis Java tidak cocok dengan tanda tangan parameter. |
Jenis kotak (seperti Integer, Double, Boolean) |
Angka, Boolean | Tidak didukung. Jenis primitif yang di-boxing diperlakukan sebagai objek buram dan dievaluasi menjadi null. |
| Jenis primitif apa pun | null/undefined |
Memaksa ke nilai default (0, 0.0, \u0000, false). |
Object, String, array |
null |
Memaksa ke null Java. |
Pemetaan jenis nilai yang ditampilkan (Java ke JavaScript)
Saat metode Java atau Kotlin yang dianotasi menampilkan nilai, jembatan akan mengonversinya ke jenis JavaScript:
| Jenis nilai yang ditampilkan Java | Nilai JavaScript | JavaScript typeof |
|---|---|---|
boolean |
true/false |
"boolean" |
byte, short, int, long, float, double |
Angka | "number" |
char |
Angka (titik kode Unicode) | "number" |
String (non-null) |
Nilai string | "string" |
String (null) |
undefined |
"undefined" |
void |
undefined |
"undefined" |
Array Java (seperti int[], String[]) |
undefined |
"undefined". Nilai yang ditampilkan array tidak didukung. Metode Java tidak dieksekusi, dan undefined ditampilkan tanpa memunculkan pengecualian. |
| Objek Java / jenis kustom (non-null) | Wrapper objek | "object". Membuat wrapper JavaScript di sekitar instance Java. Kode JavaScript dapat memanggil metode publik apa pun pada objek ini yang diberi anotasi dengan @JavascriptInterface. |
Objek Java / jenis kustom (null) |
null |
"object" |
Primitif kotak (seperti Integer, Double) |
Wrapper objek | "object". Ditampilkan sebagai wrapper objek Java buram tanpa metode @JavascriptInterface yang dapat diakses, sehingga nilai tidak dapat digunakan di JavaScript. |
Metode dan aksesibilitas anggota
Bridge JavaScript menerapkan aturan akses dan visibilitas anggota yang ketat untuk melindungi dari eksekusi kode yang tidak diinginkan:
- Kolom tidak diekspos: Kolom Java (termasuk kolom
publicdanpublic final) tidak dapat diakses dari JavaScript dan dievaluasi keundefined. - Persyaratan anotasi: Hanya metode yang dianotasi secara eksplisit dengan
@JavascriptInterfaceyang diekspos ke JavaScript. - Pembatasan visibilitas: Metode harus
public. Metodeprivatedanprotectedtidak pernah diekspos ke JavaScript, meskipun metode tersebut memiliki anotasi@JavascriptInterface. - Metode statis: Metode statis yang dianotasi dengan
@JavascriptInterfacedapat dipanggil dari JavaScript. - Pewarisan dan penggantian: Anotasi
@JavascriptInterfacetidak diwariskan saat subclass mengganti metode. Jika subclass mengganti metode beranotasi dari superclass, subclass harus menyertakan anotasi@JavascriptInterfacesecara eksplisit pada metode yang diganti untuk mengeksposnya ke JavaScript. Metode publik yang tidak diganti dan diwarisi dari superclass tetap dapat diakses jika dianotasi di superclass. - Perlindungan refleksi: Metode refleksi Java standar (seperti
getClass()) diblokir dan memunculkan pengecualian JavaScript untuk mencegah kerentanan eksekusi kode jarak jauh. - Overloading metode: Metode Java yang kelebihan muatan didukung. Metode bridge resolves memanggil metode berdasarkan jumlah argumen yang diteruskan saja, dan tidak memperhitungkan jenis argumen. Memanggil metode yang di-overload dengan jumlah argumen yang tidak valid akan memunculkan pengecualian JavaScript. Jika dua kelebihan beban memiliki jumlah argumen yang sama, salah satunya akan dipilih secara acak.
Ringkasan mekanisme
Tabel berikut memberikan perbandingan singkat dari tiga mekanisme implementasi jembatan native utama:
| Metode | addWebMessageListener |
postWebMessage |
addJavascriptInterface |
|---|---|---|---|
| Penerapan | Asinkron (Listener di thread utama) | Asinkron | Sinkron |
| Keamanan | Tertinggi (Berdasarkan daftar yang diizinkan) | Tinggi (Mengetahui asal) | Rendah (Tidak ada pemeriksaan asal) |
| Kompleksitas | Sedang | Sedang | Sederhana |
| Arah | Dua arah | Dua arah | Web ke aplikasi |
| Versi WebView minimum | Versi 82 (dan Jetpack Webkit 1.3.0) | Versi 45 (dan Jetpack Webkit 1.1.0) | Semua versi |
| Direkomendasikan | Ya | Tidak | Tidak |
Menangani transfer data berukuran besar
Anda harus mengelola memori dengan cermat saat mentransfer payload besar, seperti string multi-megabyte atau file biner, untuk menghindari error Aplikasi Tidak Merespons (ANR) atau error pada perangkat 32-bit. Bagian ini membahas berbagai teknik dan batasan yang terkait dengan transfer data dalam jumlah besar antara aplikasi host dan konten web.
Mentransfer data biner dengan array byte
Dengan class WebMessageCompat, Anda dapat mengirim array byte[] secara langsung
alih-alih melakukan serialisasi data biner ke dalam string Base64. Karena Base64 menambahkan overhead sekitar 33% ke ukuran data, hal ini jauh lebih efisien dan cepat.
- Keunggulan biner: Transfer data biner seperti file gambar atau audio antara aplikasi native dan konten web Anda.
- Batasan: Bahkan dengan array byte, sistem menyalin data di seluruh batas komunikasi antar-proses (IPC) antara aplikasi dan proses terisolasi yang digunakan WebView untuk merender konten web. Cara ini masih menggunakan memori yang signifikan untuk file yang sangat besar.
Contoh kode berikut menunjukkan cara menyiapkan addWebMessageListener di sisi aplikasi native untuk menerima pesan yang ditandai dengan WebMessageCompat.TYPE_ARRAY_BUFFER dan secara opsional membalas dengan data biner dengan memeriksa 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
);
}
}
Kode JavaScript berikut menunjukkan implementasi sisi klien
addWebMessageListener, yang memungkinkan konten web mengirim dan menerima data
biner (ArrayBuffer) ke dan dari aplikasi native menggunakan proxy
window.myBridge yang disuntikkan dalam contoh sebelumnya.
// 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]);
}
};
}
Pemuatan data berskala besar yang efisien
Untuk file yang sangat besar (>10 MB), gunakan metode shouldInterceptRequest untuk melakukan streaming data:
- Halaman web memulai panggilan
fetch()ke URL placeholder kustom. Contoh,https://app.local/large-file. - Aplikasi Android mencegat permintaan ini di
WebViewClient.shouldInterceptRequest. - Aplikasi menampilkan data sebagai
InputStream.
Hal ini memungkinkan streaming data dalam potongan, bukan memuat seluruh payload ke dalam memori sekaligus.
Fungsi JavaScript berikut menunjukkan kode sisi klien untuk
memuat file biner besar secara efisien dari aplikasi native menggunakan panggilan
fetch() standar ke URL placeholder kustom.
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);
}
}
Contoh kode berikut menunjukkan sisi aplikasi native, menggunakan metode
WebViewClient.shouldInterceptRequest di Kotlin dan Java, untuk melakukan streaming
file biner besar dengan mencegat URL placeholder kustom yang diminta oleh
konten 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);
}
});
Mengikuti rekomendasi keamanan
Untuk melindungi aplikasi dan data pengguna Anda, ikuti panduan berikut saat menerapkan jembatan:
Terapkan HTTPS: Untuk memastikan bahwa konten pihak ketiga yang berbahaya tidak dapat memanggil logika native aplikasi Anda, izinkan hanya komunikasi dengan origin yang aman.
Mengandalkan aturan asal: Cara terbaik untuk menangani kepercayaan adalah dengan menentukan
allowedOriginRulessecara ketat dan memeriksasourceOriginyang diberikan dalam callback pesan. Hindari penggunaan karakter pengganti penuh (*), yang cocok dengan semua situs asal, sebagai satu-satunya aturan asal Anda kecuali benar-benar diperlukan. Penggunaan karakter pengganti untuk subdomain (misalnya,*.example.com) tetap valid dan aman untuk mencocokkan beberapa subdomain (misalnya,foo.example.com,bar.example.com).Catatan: Meskipun aturan asal melindungi dari situs pihak ketiga yang berbahaya dan iframe tersembunyi, aturan ini tidak dapat melindungi dari kerentanan pembuatan skrip lintas situs (XSS) dalam domain tepercaya Anda sendiri. Misalnya, jika halaman web Anda menampilkan konten buatan pengguna dan rentan terhadap XSS tersimpan, penyerang dapat menjalankan skrip yang bertindak sebagai asal tepercaya Anda. Pertimbangkan untuk menerapkan validasi pada payload pesan sebelum menjalankan operasi platform native yang sensitif.
Minimalkan area permukaan: Hanya ekspos metode atau data tertentu yang diperlukan halaman web.
Memeriksa fitur saat runtime: API jembatan terbaru, termasuk
addWebMessageListener, adalah bagian dari library Jetpack Webkit. Jadi, selalu periksa apakah ada dukungan menggunakanWebViewFeature.isFeatureSupported()sebelum menelepon mereka.