เข้าถึง API ดั้งเดิมด้วย JavaScript Bridge

หน้านี้จะกล่าวถึงวิธีการต่างๆ และแนวทางปฏิบัติแนะนำในการสร้าง บริดจ์เนทีฟ หรือที่เรียกว่าบริดจ์ JavaScript เพื่ออำนวยความสะดวกในการสื่อสาร ระหว่างเนื้อหาเว็บใน WebView กับแอปพลิเคชัน Android โฮสต์

ซึ่งช่วยให้นักพัฒนาเว็บใช้ JavaScript เพื่อเข้าถึงฟีเจอร์ของแพลตฟอร์มเนทีฟ เช่น กล้อง ระบบไฟล์ หรือเซ็นเซอร์ฮาร์ดแวร์ขั้นสูง ซึ่งโดยปกติแล้ว Web API มาตรฐานจะไม่มีให้

กรณีการใช้งาน

การติดตั้งใช้งานบริดจ์ JavaScript ช่วยให้เกิดสถานการณ์การผสานรวมต่างๆ ที่เนื้อหาเว็บต้องเข้าถึงระบบปฏิบัติการ Android ในระดับที่ลึกขึ้น ตัวอย่างมีดังนี้

  • การผสานรวมแพลตฟอร์ม: การเรียกใช้คอมโพเนนต์ UI ของ Android แบบเนทีฟ (เช่น ข้อความแจ้งไบโอเมตริก BottomSheetDialog) จากหน้าเว็บ
  • ประสิทธิภาพ: การส่งต่อภาระงานด้านการคำนวณที่หนักหน่วงไปยังโค้ด Java หรือ Kotlin แบบเนทีฟ
  • การคงอยู่ของข้อมูล: การเข้าถึงฐานข้อมูลที่เข้ารหัสในเครื่องหรือค่ากำหนดที่แชร์
  • การโอนข้อมูลขนาดใหญ่: การส่งไฟล์สื่อหรือโครงสร้างข้อมูลที่ซับซ้อน ระหว่างแอปกับเครื่องมือแสดงผลบนเว็บ

กลไกการสื่อสาร

Android มี API หลัก 3 รุ่นเพื่อสร้างบริดจ์เนทีฟ แม้ว่าฟีเจอร์เหล่านี้จะยังคงใช้งานได้ แต่ก็มีความแตกต่างกันอย่างมากในด้านความปลอดภัย ความสามารถในการใช้งาน และประสิทธิภาพ

ใช้ addWebMessageListener (แนะนำ)

addWebMessageListener เป็นแนวทางที่ทันสมัยที่สุดและขอแนะนำสำหรับการ สื่อสารระหว่างเนื้อหาเว็บกับโค้ดแอปเนทีฟ โดยผสานความสะดวก ในการใช้อินเทอร์เฟซ JavaScript เข้ากับความปลอดภัยของระบบการรับส่งข้อความ

วิธีการทำงาน: แอปจะเพิ่ม Listener ที่มีชื่อเฉพาะและชุด กฎของแหล่งที่มาที่อนุญาต จากนั้น WebView จะตรวจสอบว่าออบเจ็กต์ JavaScript อยู่ในขอบเขตส่วนกลาง (window.objectName) ตั้งแต่หน้าเว็บเริ่มโหลด

การเริ่มต้น: หากต้องการให้ WebView แทรกออบเจ็กต์ JavaScript ก่อนที่สคริปต์จะทำงาน คุณต้องเรียกใช้ addWebMessageListener ก่อนเรียกใช้ loadUrl()

ฟีเจอร์หลัก

  • ความปลอดภัยและความน่าเชื่อถือ: เมธอดนี้ต้องใช้ Set<String> ของ allowedOriginRules ในระหว่างการเริ่มต้น ซึ่งแตกต่างจาก API เดิม ซึ่งเป็น กลไกหลักในการสร้างความน่าเชื่อถือ

    เมื่อคุณระบุต้นทางที่เชื่อถือได้ เช่น https://example.com WebView จะรับประกันว่าระบบจะแสดงออบเจ็กต์ JavaScript ที่แทรกไปยัง หน้าเว็บที่โหลดจากต้นทางนั้นเท่านั้น

    การเรียกกลับของ Listener เนทีฟจะได้รับพารามิเตอร์ sourceOrigin พร้อมกับข้อความทุกข้อความ คุณสามารถใช้พารามิเตอร์นี้เพื่อยืนยันแหล่งที่มาที่แน่นอนของผู้ส่งได้หาก บริดจ์รองรับแหล่งที่มาที่อนุญาตหลายรายการ

    เนื่องจาก 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") คุณไม่สามารถใช้ อักขระไวด์การ์ดสำหรับรูปแบบหรือตรงกลางโดเมน

    ซึ่งจะจำกัดบริดจ์ไว้เฉพาะโดเมนที่ยืนยันแล้ว เพื่อป้องกันไม่ให้เนื้อหาของบุคคลที่สามที่ไม่ได้รับอนุญาต หรือ iframe ที่แทรกทำงานโค้ดดั้งเดิม

  • รองรับหลายเฟรม: ทำงานในทุกเฟรมที่ตรงกับกฎต้นทาง

  • การแยกเธรด: การเรียกกลับของ Listener จะทำงานในเธรดหลัก (UI) ของแอปพลิเคชัน หากบริดจ์ต้องจัดการการประมวลผลข้อมูลที่ซับซ้อน การแยกวิเคราะห์ JSON หรือการค้นหาฐานข้อมูล คุณต้องส่งต่อการทำงานดังกล่าวไปยังเธรดเบื้องหลังเพื่อป้องกันไม่ให้ UI ของแอปพลิเคชันหยุดทำงานเนื่องจากข้อผิดพลาด "แอปไม่ตอบสนอง" (ANR)

  • สองทิศทาง: เมื่อหน้าเว็บส่งข้อความ แอปจะได้รับ JavaScriptReplyProxy ที่ใช้ส่งข้อความกลับไปยังเฟรมนั้นๆ คุณสามารถเก็บออบเจ็กต์ replyProxy นี้ไว้และใช้ได้ทุกเมื่อเพื่อส่งข้อความจำนวนเท่าใดก็ได้ไปยังเพจ ไม่ใช่แค่เพื่อตอบกลับข้อความแต่ละรายการที่เพจส่งมา หากเฟรมต้นทางไปยังที่อื่น หรือถูกทำลาย ระบบจะเพิกเฉยต่อข้อความที่ส่งโดยใช้ postMessage() ในพร็อกซีโดยไม่มีการแจ้งเตือน

  • การเริ่มต้นฝั่งแอป: แม้ว่าหน้าเว็บจะต้องเริ่มต้นช่องทางการสื่อสารกับแอปเสมอ แต่แอปเนทีฟก็สามารถแจ้งให้หน้าเว็บเริ่มกระบวนการนี้ได้โดยฝ่ายเดียว แอปเนทีฟสามารถสื่อสารกับหน้าเว็บได้โดยใช้ addDocumentStartJavaScript() (เพื่อประเมิน JavaScript ก่อนที่หน้าเว็บจะโหลด) หรือ evaluateJavaScript() (เพื่อประเมิน JavaScript หลังจากที่หน้าเว็บโหลดแล้ว)

ข้อจำกัด: API นี้จะส่งข้อมูลเป็นสตริงหรืออาร์เรย์ byte[] สำหรับโครงสร้างข้อมูลที่ซับซ้อนมากขึ้น เช่น ออบเจ็กต์ JSON คุณต้องทำให้ข้อมูลนี้เป็นอนุกรมในรูปแบบใดรูปแบบหนึ่ง แล้วจึงยกเลิกการทำอนุกรมในอีกด้านหนึ่งเพื่อสร้างโครงสร้างข้อมูลขึ้นใหม่

ตัวอย่างการใช้งาน

หากต้องการทำความเข้าใจลำดับทั้งหมดของการแลกเปลี่ยนข้อความแบบ 2 ทาง เหตุการณ์ จะดำเนินการตามลำดับต่อไปนี้

  1. การเริ่มต้น (แอป): แอปเนทีฟจะลงทะเบียน Listener ด้วย addWebMessageListener และโหลดหน้าเว็บด้วย loadUrl()
  2. ส่งข้อความ (เว็บ): JavaScript ของหน้าเว็บเรียกใช้ myObject.postMessage(message) เพื่อเริ่มการสื่อสาร
  3. การรับและตอบกลับข้อความ (แอป): แอปจะรับข้อความใน การเรียกกลับของ Listener และตอบกลับโดยใช้ replyProxy.postMessage() ที่ระบุ
  4. รับการตอบกลับ (เว็บ): หน้าเว็บจะได้รับการตอบกลับแบบอะซิงโครนัสในฟังก์ชันเรียกกลับ 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 ต่อไปนี้แสดงการติดตั้งใช้งานฝั่งไคลเอ็นต์ของ 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: คุณไม่สามารถใช้วิธีนี้กับเนื้อหาที่โหลดโดยใช้ URI ของ data:, URI ของ file: หรือ loadData() เว้นแต่จะระบุ "*" เป็นต้นทางเป้าหมาย การทำเช่นนี้จะทำให้ทุกหน้าได้รับข้อความ
  • ความเสี่ยงด้านข้อมูลประจำตัว: เนื้อหาเว็บไม่มีวิธีที่ชัดเจนในการยืนยันตัวตนของผู้ส่ง ข้อความที่หน้าเว็บได้รับอาจ มาจากแอปเนทีฟหรือ iframe อื่น

ใช้วิธีนี้เมื่อต้องการช่องทางแบบอะซิงโครนัสอย่างง่ายสำหรับข้อมูลที่อิงตามสตริงใน Android เวอร์ชันก่อนหน้าที่ไม่รองรับ addWebMessageListener

ใช้ addJavascriptInterface (เดิม)

วิธีที่เก่าแก่ที่สุดคือการแทรกอินสแตนซ์ออบเจ็กต์ดั้งเดิมลงใน WebView โดยตรง

วิธีการทำงาน: คุณกำหนดคลาส Kotlin หรือ Java ใส่คำอธิบายประกอบให้กับเมธอดที่อนุญาตด้วย @JavascriptInterface และเพิ่มอินสแตนซ์ของคลาสลงใน WebView โดยใช้ addJavascriptInterface(Object, String)

ลักษณะ

  • ซิงโครนัส: สภาพแวดล้อมการเรียกใช้ JavaScript จะบล็อกจนกว่าเมธอดในโค้ด Android จะส่งคืน
  • ความปลอดภัยของเธรด: ระบบจะเรียกใช้เมธอดในเธรดเบื้องหลัง ซึ่งต้องมีการซิงค์อย่างระมัดระวังในฝั่ง Kotlin หรือ Java
  • ความเสี่ยงด้านความปลอดภัย: โดยค่าเริ่มต้น addJavascriptInterface จะพร้อมใช้งานใน ทุกเฟรมภายใน WebView รวมถึง iframe ไม่มีการควบคุมการเข้าถึงตามต้นทาง เนื่องจากลักษณะการทำงานแบบไม่พร้อมกันของ WebView จึงไม่สามารถ ระบุ URL ของเฟรมที่เรียกอินเทอร์เฟซของคุณได้อย่างปลอดภัย คุณต้องไม่ใช้วิธีการต่างๆ เช่น WebView.getUrl() เพื่อยืนยันความปลอดภัย เนื่องจากไม่มีการรับประกันความถูกต้องและไม่ได้ระบุว่าเฟรมใดเป็นผู้ส่งคำขอ

สรุปกลไก

ตารางต่อไปนี้แสดงการเปรียบเทียบโดยย่อของกลไกการติดตั้งใช้งานบริดจ์เนทีฟหลัก 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 เพื่อ สตรีมข้อมูล

  1. หน้าเว็บจะเริ่มfetch()การเรียกไปยัง URL ตัวยึดตำแหน่งที่กำหนดเอง เช่น https://app.local/large-file
  2. แอป Android จะสกัดกั้นคำขอนี้ใน WebViewClient.shouldInterceptRequest
  3. แอปจะแสดงผลข้อมูลเป็น InputStream

ซึ่งจะช่วยให้สตรีมข้อมูลเป็นก้อนๆ แทนที่จะโหลดเพย์โหลดทั้งหมด ลงในหน่วยความจำพร้อมกัน

ฟังก์ชัน JavaScript ต่อไปนี้แสดงโค้ดฝั่งไคลเอ็นต์สำหรับ การโหลดไฟล์ไบนารีขนาดใหญ่จากแอปพลิเคชันเนทีฟอย่างมีประสิทธิภาพโดยใช้ fetch()การเรียกมาตรฐานไปยัง 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);
  }
});

ปฏิบัติตามคำแนะนำด้านความปลอดภัย

โปรดปฏิบัติตามหลักเกณฑ์ต่อไปนี้เมื่อ ใช้บริดจ์เพื่อปกป้องแอปพลิเคชันและข้อมูลผู้ใช้

  • บังคับใช้ HTTPS: เพื่อให้มั่นใจว่าเนื้อหาที่เป็นอันตรายของบุคคลที่สามจะไม่สามารถเรียกใช้ตรรกะดั้งเดิมของแอปพลิเคชันได้ ให้อนุญาตเฉพาะการสื่อสารกับต้นทางที่ปลอดภัย

  • อาศัยกฎต้นทาง: วิธีที่ดีที่สุดในการจัดการความน่าเชื่อถือคือการ กำหนด allowedOriginRules อย่างเคร่งครัดและตรวจสอบ sourceOrigin ที่ระบุไว้ ในการเรียกกลับของข้อความ หลีกเลี่ยงการใช้ไวลด์การ์ดแบบเต็ม (*) ซึ่งตรงกับต้นทางทั้งหมด เป็นกฎต้นทางเดียวของคุณ เว้นแต่จะจำเป็นจริงๆ การใช้ไวลด์การ์ดสำหรับโดเมนย่อย (เช่น *.example.com) ยังคงใช้ได้และปลอดภัยสำหรับการจับคู่โดเมนย่อยหลายรายการ (เช่น foo.example.com, bar.example.com)

    หมายเหตุ: แม้ว่ากฎต้นทางจะป้องกันเว็บไซต์ของบุคคลที่สามที่เป็นอันตราย และ iframe ที่ซ่อนอยู่ได้ แต่ก็ไม่สามารถป้องกันช่องโหว่ ของ Cross-Site Scripting (XSS) ภายในโดเมนที่เชื่อถือได้ของคุณเอง เช่น หากหน้าเว็บแสดงเนื้อหาที่ผู้ใช้สร้างขึ้นและมีความเสี่ยงต่อ XSS ที่จัดเก็บไว้ ผู้โจมตีอาจเรียกใช้สคริปต์ที่ทําหน้าที่เป็นต้นทางที่เชื่อถือได้ของคุณ พิจารณา ใช้การตรวจสอบเพย์โหลดของข้อความก่อนดำเนินการที่ละเอียดอ่อน ในแพลตฟอร์มเนทีฟ

  • ลดพื้นที่ผิว: แสดงเฉพาะเมธอดหรือข้อมูลที่เฉพาะเจาะจงซึ่งหน้าเว็บต้องการเท่านั้น

  • ตรวจสอบฟีเจอร์ขณะรันไทม์: Bridge API ล่าสุด ซึ่งรวมถึง addWebMessageListener เป็นส่วนหนึ่งของไลบรารี Jetpack Webkit ดังนั้น โปรดตรวจสอบว่ามีบริการสนับสนุนโดยใช้ WebViewFeature.isFeatureSupported() ก่อนโทรหา