หน้านี้จะกล่าวถึงวิธีการต่างๆ และแนวทางปฏิบัติแนะนำสำหรับการสร้าง Native Bridge หรือที่เรียกว่า JavaScript Bridge เพื่ออำนวยความสะดวกในการสื่อสารระหว่างเนื้อหาเว็บใน WebView กับแอปพลิเคชัน Android โฮสต์
ซึ่งจะช่วยให้นักพัฒนาเว็บใช้ JavaScript เพื่อเข้าถึงฟีเจอร์แพลตฟอร์มแบบเนทีฟ เช่น กล้อง ระบบไฟล์ หรือเซ็นเซอร์ฮาร์ดแวร์ขั้นสูง ซึ่งโดยปกติแล้ว API เว็บมาตรฐานจะไม่มีให้
กรณีการใช้งาน
การใช้งาน JavaScript Bridge ช่วยให้เกิดสถานการณ์การผสานรวมต่างๆ ที่เนื้อหาเว็บต้องเข้าถึงระบบปฏิบัติการ Android ในระดับที่ลึกขึ้น ตัวอย่างเช่น
- การผสานรวมแพลตฟอร์ม: การทริกเกอร์คอมโพเนนต์ UI แบบเนทีฟของ Android (เช่น
พรอมต์ไบโอเมตริก
BottomSheetDialog) จากหน้าเว็บ - ประสิทธิภาพ: การโอนงานการคำนวณที่ซับซ้อนไปยังโค้ด Java หรือ Kotlin แบบเนทีฟ
- การคงอยู่ของข้อมูล: การเข้าถึงฐานข้อมูลที่เข้ารหัสไว้ในเครื่องหรือค่ากำหนดที่แชร์
- การโอนข้อมูลขนาดใหญ่: การส่งไฟล์สื่อหรือโครงสร้างข้อมูลที่ซับซ้อน ระหว่างแอปกับตัวแสดงผลเว็บ
กลไกการสื่อสาร
Android มี API 3 รุ่นหลักๆ สำหรับสร้าง Native Bridge แม้ว่า API ทั้งหมดจะยังคงใช้งานได้ แต่ก็มีความแตกต่างกันอย่างมากในด้านความปลอดภัย ความสามารถในการใช้งาน และประสิทธิภาพ
ใช้ addWebMessageListener (แนะนำ)
addWebMessageListener เป็นแนวทางที่ทันสมัยที่สุดและแนะนำให้ใช้สำหรับการสื่อสารระหว่างเนื้อหาเว็บกับโค้ดแอปที่มาพร้อมเครื่อง โดยจะรวมความสะดวกในการใช้งานของอินเทอร์เฟซ JavaScript เข้ากับความปลอดภัยของระบบการรับส่งข้อความ
วิธีการทำงาน: แอปจะเพิ่ม Listener ที่มีชื่อเฉพาะและชุดกฎต้นทางที่
อนุญาต จากนั้น WebView จะตรวจสอบว่าออบเจ็กต์ JavaScript อยู่ในขอบเขตส่วนกลาง (window.objectName) ตั้งแต่เริ่มโหลดหน้าเว็บ
การเริ่มต้น: หากต้องการให้ WebView แทรกออบเจ็กต์ JavaScript ก่อน
ที่สคริปต์จะทำงาน คุณต้องเรียกใช้ addWebMessageListener ก่อนเรียกใช้
loadUrl()
ฟีเจอร์หลัก:
ความปลอดภัยและความน่าเชื่อถือ: วิธีนี้กำหนดให้มี
Set<String>ของallowedOriginRulesในระหว่างการเริ่มต้น ซึ่งแตกต่างจาก API เดิม และเป็นกลไกหลักในการสร้างความน่าเชื่อถือเมื่อคุณระบุต้นทางที่เชื่อถือได้ เช่น
https://example.comWebView จะรับประกันว่าจะแสดงออบเจ็กต์ JavaScript ที่แทรกไว้เฉพาะกับหน้าเว็บที่โหลดจากต้นทางนั้นๆ เท่านั้นCallback ของ Listener แบบเนทีฟจะได้รับพารามิเตอร์
sourceOriginพร้อมกับทุกข้อความ คุณสามารถใช้พารามิเตอร์นี้เพื่อยืนยันต้นทางที่แน่นอนของผู้ส่งได้หาก Bridge รองรับต้นทางที่อนุญาตหลายรายการเนื่องจาก WebView บังคับใช้การตรวจสอบต้นทางเหล่านี้อย่างเข้มงวดในระดับแพลตฟอร์ม โดยทั่วไปแล้วแอปของคุณจึงเชื่อถือข้อความที่ได้รับจาก
sourceOriginที่เชื่อถือได้ ซึ่งช่วยลดความจำเป็นในการตรวจสอบความถูกต้องของเพย์โหลดอย่างเข้มงวดในการใช้งานมาตรฐานส่วนใหญ่- WebView จะจับคู่กฎกับรูปแบบ (HTTP/HTTPS), โฮสต์ และพอร์ต
- WebView จะไม่สนใจเส้นทาง เช่น
https://example.comอนุญาตhttps://example.com/loginและhttps://example.com/home - WebView จำกัดไวลด์การ์ดอย่างเข้มงวดไว้ที่จุดเริ่มต้นของโฮสต์สำหรับโดเมนย่อย เช่น
https://*.example.comจะตรงกับhttps://foo.example.comแต่ไม่ตรงกับhttps://example.comหากต้องการจับคู่ทั้งhttps://example.comและโดเมนย่อย คุณต้องเพิ่มกฎต้นทางแต่ละรายการลงในรายการที่อนุญาตแยกกัน (เช่น"https://example.com", "https://*.example.com") คุณไม่สามารถใช้ไวลด์การ์ดสำหรับรูปแบบหรือตรงกลางโดเมน
ซึ่งจะจำกัด Bridge ไว้ที่โดเมนที่ยืนยันแล้ว เพื่อป้องกันไม่ให้เนื้อหาของบุคคลที่สามที่ไม่ได้รับอนุญาตหรือ iframe ที่แทรกไว้เรียกใช้โค้ดแบบเนทีฟ
การรองรับหลายเฟรม: ทำงานในทุกเฟรมที่ตรงกับกฎต้นทาง
การทำงานแบบเธรด: Callback ของ Listener จะทำงานในเธรดหลัก (UI) ของแอปพลิเคชัน หาก Bridge ต้องจัดการการประมวลผลข้อมูลที่ซับซ้อน การแยกวิเคราะห์ JSON หรือการค้นหาฐานข้อมูล คุณต้องลดภาระงานดังกล่าวไปยังเทรดเบื้องหลังเพื่อป้องกันไม่ให้ UI ของแอปพลิเคชันหยุดทำงานเนื่องจากข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR)
แบบ 2 ทิศทาง: เมื่อหน้าเว็บส่งข้อความ แอปจะได้รับ
JavaScriptReplyProxyซึ่งใช้ส่งข้อความกลับไปยัง เฟรมนั้นๆ ได้ คุณสามารถเก็บออบเจ็กต์replyProxyนี้ไว้และใช้ได้ทุกเมื่อเพื่อส่งข้อความจำนวนเท่าใดก็ได้ไปยังหน้าเว็บ ไม่ใช่แค่การตอบกลับข้อความแต่ละรายการที่หน้าเว็บส่งมา หากเฟรมต้นทางออกจากหน้าเว็บหรือถูกทำลาย ระบบจะละเว้นข้อความที่ส่งโดยใช้postMessage()ในพร็อกซีโดยไม่มีการแจ้งเตือนการเริ่มต้นจากฝั่งแอป: แม้ว่าหน้าเว็บจะต้องเริ่มต้นช่องทางการสื่อสารกับแอปเสมอ แต่แอปที่มาพร้อมเครื่องก็สามารถแจ้งให้หน้าเว็บเริ่มกระบวนการนี้ได้โดยฝ่ายเดียว แอปที่มาพร้อมเครื่องสามารถสื่อสารกับหน้าเว็บได้ด้วย
addDocumentStartJavaScript()(เพื่อประเมิน JavaScript ก่อนที่หน้าเว็บจะโหลด) หรือevaluateJavaScript()(เพื่อประเมิน JavaScript หลังจากที่หน้าเว็บโหลดแล้ว)
ข้อจำกัด: API นี้จะส่งข้อมูลเป็นสตริงหรืออาร์เรย์ byte[] สำหรับโครงสร้างข้อมูลที่ซับซ้อนมากขึ้น เช่น ออบเจ็กต์ JSON คุณต้องซีเรียลไลซ์ข้อมูลนี้เป็นรูปแบบใดรูปแบบหนึ่ง แล้วจึงดีซีเรียลไลซ์ในอีกด้านหนึ่งเพื่อสร้างโครงสร้างข้อมูลใหม่
ตัวอย่างการใช้งาน:
หากต้องการทำความเข้าใจลำดับทั้งหมดของการแลกเปลี่ยนข้อความแบบ 2 ทิศทาง เหตุการณ์จะเกิดขึ้นตามลำดับต่อไปนี้
- การเริ่มต้น (แอป): แอปที่มาพร้อมเครื่องจะลงทะเบียน Listener ด้วย
addWebMessageListenerและโหลดหน้าเว็บด้วยloadUrl() - การส่งข้อความ (เว็บ): JavaScript ของหน้าเว็บจะเรียกใช้
myObject.postMessage(message)เพื่อเริ่มต้นการสื่อสาร - การรับและตอบกลับข้อความ (แอป): แอปจะได้รับข้อความใน
Callback ของ Listener และตอบกลับโดยใช้
replyProxy.postMessage()ที่ให้ไว้ - การรับการตอบกลับ (เว็บ): หน้าเว็บจะได้รับการตอบกลับแบบอะซิงโครนัสใน
myObject.onmessage()ฟังก์ชัน Callback
Kotlin
val myListener = WebViewCompat.WebMessageListener { _, _, _, _, replyProxy ->
// Handle the message from JS
replyProxy.postMessage("Acknowledged!")
}
// Check whether the WebView version supports the feature.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
val allowedOrigins = setOf("https://www.example.com")
WebViewCompat.addWebMessageListener(webView, "myObject", allowedOrigins, myListener)
}
Java
WebMessageListener myListener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
// Handle the message from JS
replyProxy.postMessage("Acknowledged!");
};
// Check whether the WebView version supports the feature.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
Set<String> allowedOrigins = Set.of("https://www.example.com");
WebViewCompat.addWebMessageListener(webView, "myObject", allowedOrigins, myListener);
}
JavaScript ต่อไปนี้แสดงการใช้งาน addWebMessageListener ฝั่งไคลเอ็นต์ ซึ่งช่วยให้เนื้อหาเว็บรับข้อความจากแอปที่มาพร้อมเครื่องและส่งข้อความของตัวเองผ่านพร็อกซี myObject ได้
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
ใช้ postWebMessage (ทางเลือกอื่น)
Android ได้เปิดตัววิธีนี้เพื่อเป็นทางเลือกแบบอะซิงโครนัสที่ใช้การรับส่งข้อความ ซึ่งคล้ายกับ window.postMessage ของเว็บ
วิธีการทำงาน: แอปจะใช้ WebViewCompat.postWebMessage เพื่อส่งเพย์โหลด
ไปยังเฟรมหลักของหน้าเว็บ หากต้องการสร้างช่องทางการสื่อสารแบบ 2 ทิศทาง
คุณสามารถสร้าง WebMessageChannel และส่งพอร์ตพอร์ตหนึ่ง
พร้อมกับข้อความไปยังเนื้อหาเว็บ
ลักษณะ:
- อะซิงโครนัส: เช่นเดียวกับ
addWebMessageListenerวิธีนี้ใช้ การรับส่งข้อความแบบอะซิงโครนัส ซึ่งช่วยให้หน้าเว็บยังคงตอบสนองต่อ การโต้ตอบของผู้ใช้ในขณะที่แอปประมวลผลข้อมูลในเบื้องหลัง - รับรู้ต้นทาง: คุณสามารถระบุ
targetOriginเพื่อให้ WebView ส่งข้อมูลไปยังเว็บไซต์ที่เชื่อถือได้เท่านั้น
ข้อจำกัด:
- ขอบเขต: API นี้จำกัดการสื่อสารไว้ที่เฟรมหลัก โดยไม่รองรับการกำหนดเป้าหมายหรือการส่งข้อความไปยัง iframe โดยตรง
- ข้อจำกัด URI: คุณไม่สามารถใช้วิธีนี้กับเนื้อหาที่โหลดโดยใช้
data:URI,file:URI หรือloadData()เว้นแต่จะระบุ "*" เป็น ต้นทางเป้าหมาย การดำเนินการนี้จะทำให้ทุกหน้าเว็บได้รับข้อความ - ความเสี่ยงด้านข้อมูลประจำตัว: เนื้อหาเว็บไม่มีวิธีที่ชัดเจนในการยืนยัน ข้อมูลประจำตัวของผู้ส่ง ข้อความที่หน้าเว็บได้รับอาจมาจากแอปที่มาพร้อมเครื่องหรือ iframe อื่น
ใช้วิธีนี้เมื่อคุณต้องการช่องทางแบบไม่พร้อมกันที่เรียบง่ายสำหรับข้อมูลที่อิงตามสตริงใน Android เวอร์ชันก่อนหน้าซึ่งไม่รองรับ addWebMessageListener
ใช้ addJavascriptInterface (เดิม)
วิธีที่เก่าที่สุดคือการแทรกอินสแตนซ์ออบเจ็กต์แบบเนทีฟลงใน WebView โดยตรง
วิธีการทำงาน: คุณกำหนดคลาส Kotlin หรือ Java ใส่คำอธิบายประกอบในเมธอดที่อนุญาตด้วย @JavascriptInterface และเพิ่มอินสแตนซ์ของคลาสลงใน WebView โดยใช้ addJavascriptInterface(Object, String)
ลักษณะ:
- พร้อมกัน: สภาพแวดล้อมการดำเนินการ JavaScript จะถูกบล็อกจนกว่า เมธอดในโค้ด Android จะแสดงผล
- ความปลอดภัยของเธรด: ระบบจะเรียกใช้เมธอดในเธรดเบื้องหลัง ซึ่งต้องมีการซิงโครไนซ์อย่างระมัดระวังในฝั่ง Kotlin หรือ Java
- ความเสี่ยงด้านความปลอดภัย: โดยค่าเริ่มต้น
addJavascriptInterfaceจะพร้อมใช้งานสำหรับ ทุกเฟรมภายใน WebView รวมถึง iframe และไม่มีการควบคุมการเข้าถึงตามต้นทาง เนื่องจากลักษณะการทำงานแบบอะซิงโครนัสของ WebView จึงไม่สามารถกำหนด URL ของเฟรมที่เรียกใช้อินเทอร์เฟซของคุณได้อย่างปลอดภัย คุณต้องไม่พึ่งพาเมธอดอย่างWebView.getUrl()สำหรับการยืนยันความปลอดภัย เนื่องจากเมธอดเหล่านี้ไม่รับประกันความถูกต้องและไม่ได้ระบุว่าเฟรมใดเป็นผู้ส่งคำขอ
สรุปกลไก
ตารางต่อไปนี้แสดงการเปรียบเทียบกลไกการใช้งาน Native Bridge หลัก 3 รายการโดยย่อ
| วิธีการ | addWebMessageListener |
postWebMessage |
addJavascriptInterface |
|---|---|---|---|
| การใช้งาน | อะซิงโครนัส (Listener ในเธรดหลัก) | อะซิงโครนัส | พร้อมกัน |
| ความปลอดภัย | สูงสุด (อิงตามรายการที่อนุญาต) | สูง (รับรู้ต้นทาง) | ต่ำ (ไม่มีการตรวจสอบต้นทาง) |
| ความซับซ้อน | ปานกลาง | ปานกลาง | เรียบง่าย |
| ทิศทาง | แบบ 2 ทิศทาง | แบบ 2 ทิศทาง | เว็บไปยังแอป |
| WebView เวอร์ชันต่ำสุด | เวอร์ชัน 82 (และ Jetpack Webkit 1.3.0) | เวอร์ชัน 45 (และ Jetpack Webkit 1.1.0) | ทุกเวอร์ชัน |
| แนะนำ | ใช่ | ไม่ใช่ | ไม่ใช่ |
จัดการการโอนข้อมูลขนาดใหญ่
คุณต้องจัดการหน่วยความจำอย่างระมัดระวังเมื่อโอนเพย์โหลดขนาดใหญ่ เช่น สตริงหรือไฟล์ไบนารีขนาดหลายเมกะไบต์ เพื่อหลีกเลี่ยงข้อผิดพลาด "แอปพลิเคชันไม่ตอบสนอง" (ANR) หรือการขัดข้องในอุปกรณ์ 32 บิต ส่วนนี้จะกล่าวถึงเทคนิคและข้อจำกัดต่างๆ ที่เกี่ยวข้องกับการโอนข้อมูลจำนวนมากระหว่างแอปพลิเคชันโฮสต์กับเนื้อหาเว็บ
โอนข้อมูลไบนารีด้วยอาร์เรย์ไบต์
คลาส WebMessageCompat ช่วยให้คุณส่งอาร์เรย์ byte[] ได้โดยตรง แทนการซีเรียลไลซ์ข้อมูลไบนารีเป็นสตริง Base64 เนื่องจาก Base64 เพิ่มค่าใช้จ่ายประมาณ 33% ให้กับขนาดข้อมูล วิธีนี้จึงมีประสิทธิภาพด้านหน่วยความจำมากกว่าและเร็วกว่าอย่างมาก
- ข้อดีของไบนารี: โอนข้อมูลไบนารี เช่น ไฟล์รูปภาพหรือเสียง ระหว่าง แอปที่มาพร้อมเครื่อง กับเนื้อหาเว็บ
- ข้อจำกัด: แม้จะใช้อาร์เรย์ไบต์ แต่ระบบจะคัดลอกข้อมูลข้าม ขอบเขตการสื่อสารระหว่างกระบวนการ (IPC) ระหว่างแอปกับกระบวนการที่แยกต่างหาก ซึ่ง WebView ใช้ในการแสดงผลเนื้อหาเว็บ ซึ่งยังคงใช้หน่วยความจำจำนวนมากสำหรับไฟล์ขนาดใหญ่มาก
ตัวอย่างโค้ดต่อไปนี้แสดงวิธีตั้งค่า addWebMessageListener ในฝั่งแอปที่มาพร้อมเครื่องเพื่อรับข้อความที่ทำเครื่องหมายด้วย WebMessageCompat.TYPE_ARRAY_BUFFER และเลือกที่จะตอบกลับด้วยข้อมูลไบนารีโดยตรวจสอบ 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
);
}
}
โค้ด JavaScript ต่อไปนี้แสดงการใช้งาน addWebMessageListener ฝั่งไคลเอ็นต์ ซึ่งช่วยให้เนื้อหาเว็บส่งและรับข้อมูลไบนารี (ArrayBuffer) ไปยังและจากแอปที่มาพร้อมเครื่องโดยใช้พร็อกซี window.myBridge ที่แทรกไว้ในตัวอย่างก่อนหน้า
// Function to send an image or binary buffer to the app
async function sendBinaryToApp() {
const response = await fetch('image.jpg');
const buffer = await response.arrayBuffer();
// Check if the injected bridge object exists
if (window.myBridge) {
// You can send the ArrayBuffer directly
window.myBridge.postMessage(buffer);
}
}
// Receiving binary data from the app
if (window.myBridge) {
window.myBridge.onmessage = function(event) {
if (event.data instanceof ArrayBuffer) {
console.log('Received binary data from App, length:', event.data.byteLength);
// Process the binary data (for example, as a Uint8Array)
const bytes = new Uint8Array(event.data);
console.log('First byte:', bytes[0]);
}
};
}
การโหลดข้อมูลขนาดใหญ่อย่างมีประสิทธิภาพ
สำหรับไฟล์ขนาดใหญ่มาก (>10 MB) ให้ใช้เมธอด shouldInterceptRequest เพื่อ
สตรีมข้อมูล โดยทำดังนี้
- หน้าเว็บจะเริ่มต้นการเรียก
fetch()ไปยัง URL ที่กำหนดเองซึ่งเป็น URL ตัวยึดตำแหน่ง เช่นhttps://app.local/large-file - แอป Android จะสกัดกั้นคำขอนี้ใน
WebViewClient.shouldInterceptRequest - แอปจะแสดงผลข้อมูลเป็น
InputStream
ซึ่งจะช่วยให้สตรีมข้อมูลเป็นชิ้นๆ แทนที่จะโหลดเพย์โหลดทั้งหมดลงในหน่วยความจำพร้อมกัน
ฟังก์ชัน JavaScript ต่อไปนี้แสดงโค้ดฝั่งไคลเอ็นต์สำหรับการโหลดไฟล์ไบนารีขนาดใหญ่จากแอปพลิเคชันแบบเนทีฟอย่างมีประสิทธิภาพโดยใช้การเรียก fetch() มาตรฐานไปยัง URL ที่กำหนดเองซึ่งเป็น 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);
}
}
ตัวอย่างโค้ดต่อไปนี้แสดงฝั่งแอปที่มาพร้อมเครื่องโดยใช้เมธอด WebViewClient.shouldInterceptRequest ในทั้ง Kotlin และ Java เพื่อสตรีมไฟล์ไบนารีขนาดใหญ่โดยการสกัดกั้น URL ตัวยึดตำแหน่งที่กำหนดเองซึ่งเนื้อหาเว็บขอ
Kotlin
webView.webViewClient = object : WebViewClient() {
override fun shouldInterceptRequest(
view: WebView?,
request: WebResourceRequest?
): WebResourceResponse? {
val url = request?.url ?: return null
// Check if this is our custom placeholder URL
if (url.host == "app.local" && url.path == "/data/large-file.bin") {
try {
// 1. Get your data as an InputStream
// (from Assets, Files, or a generated byte stream)
val inputStream: InputStream = context.assets.open("my_data.pb")
// 2. Define Response Headers (Crucial for CORS/Fetch)
val headers = mutableMapOf<String, String>()
headers["Access-Control-Allow-Origin"] = "*" // Allow fetch from any origin
// 3. Return the response
return WebResourceResponse(
"application/octet-stream", // MIME type (for example, image/jpeg)
"UTF-8", // Encoding
200, // Status Code
"OK", // Reason Phrase
headers, // Custom Headers
inputStream // The actual data stream
)
} catch (e: Exception) {
// Handle exception
}
}
return super.shouldInterceptRequest(view, request)
}
}
Java
webView.setWebViewClient(new WebViewClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
String urlPath = request.getUrl().getPath();
String host = request.getUrl().getHost();
// Check if this is our custom placeholder URL
if ("app.local".equals(host) && "/data/large-file.bin".equals(urlPath)) {
try {
// 1. Get your data as an InputStream
// (from Assets, Files, or a generated byte stream)
InputStream inputStream = getContext().getAssets().open("my_data.pb");
// 2. Define Response Headers (Crucial for CORS/Fetch)
Map<String, String> headers = new HashMap<>();
headers.put("Access-Control-Allow-Origin", "*"); // Allow fetch from any origin
// 3. Return the response
return new WebResourceResponse(
"application/octet-stream", // MIME type (for example, image/jpeg)
"UTF-8", // Encoding
200, // Status Code
"OK", // Reason Phrase
headers, // Custom Headers
inputStream // The actual data stream
);
} catch (Exception e) {
// Handle exception
}
}
return super.shouldInterceptRequest(view, request);
}
});
ปฏิบัติตามคำแนะนำด้านความปลอดภัย
ปฏิบัติตามหลักเกณฑ์ต่อไปนี้เมื่อใช้งาน Bridge เพื่อปกป้องแอปพลิเคชันและข้อมูลผู้ใช้
บังคับใช้ HTTPS: อนุญาตให้สื่อสารกับต้นทางที่ปลอดภัยเท่านั้น เพื่อให้มั่นใจว่าเนื้อหาของบุคคลที่สามที่เป็นอันตรายจะไม่สามารถ เรียกใช้ตรรกะแบบเนทีฟของแอปพลิเคชันได้
ใช้กฎต้นทาง: วิธีที่ดีที่สุดในการจัดการความน่าเชื่อถือคือการ กำหนด
allowedOriginRulesอย่างเข้มงวดและตรวจสอบsourceOriginที่ระบุไว้ใน Callback ของข้อความ หลีกเลี่ยงการใช้ไวลด์การ์ดแบบเต็ม (*) ซึ่งตรงกับต้นทางทั้งหมดเป็นกฎต้นทางเดียว เว้นแต่จะจำเป็นจริงๆ การใช้ไวลด์การ์ดสำหรับโดเมนย่อย (เช่น*.example.com) ยังคงใช้ได้และปลอดภัยสำหรับการจับคู่โดเมนย่อยหลายรายการ (เช่นfoo.example.com,bar.example.com)หมายเหตุ: แม้ว่ากฎต้นทางจะช่วยป้องกันเว็บไซต์ของบุคคลที่สามที่เป็นอันตราย และ iframe ที่ซ่อนอยู่ แต่ก็ไม่สามารถป้องกันช่องโหว่ Cross-site Scripting (XSS) ภายในโดเมนที่เชื่อถือได้ของคุณเอง เช่น หากหน้าเว็บแสดงเนื้อหาที่ผู้ใช้สร้างขึ้นและมีช่องโหว่ XSS ที่จัดเก็บไว้ ผู้โจมตีอาจเรียกใช้สคริปต์ที่ทำหน้าที่เป็นต้นทางที่เชื่อถือได้ของคุณ พิจารณาใช้การตรวจสอบความถูกต้องกับเพย์โหลดของข้อความก่อนที่จะดำเนินการแพลตฟอร์มแบบเนทีฟที่ละเอียดอ่อน
ลดพื้นที่ผิว: แสดงเฉพาะเมธอดหรือข้อมูลที่ หน้าเว็บต้องการ
ตรวจสอบฟีเจอร์ขณะรันไทม์: API ของ Bridge ล่าสุด รวมถึง
addWebMessageListenerเป็นส่วนหนึ่งของไลบรารี Jetpack Webkit ดังนั้น ให้ตรวจสอบการรองรับโดยใช้WebViewFeature.isFeatureSupported()เสมอก่อนที่จะเรียกใช้