دسترسی به میاناهای برنامه‌سازی کاربردی بومی با پل جاوا اسکریپت

این صفحه روش‌های مختلف و روال‌های مطلوب برای ایجاد پل بومی، که به‌عنوان پل جاوا اسکریپت نیز شناخته می‌شود، را برای تسهیل ارتباط بین محتوای وب در WebView و برنامه میزبان Android مورد بحث قرار می‌دهد.

این ویژگی به توسعه‌دهندگان وب امکان می‌دهد از جاوا اسکریپت برای دسترسی به ویژگی‌های پلاتفرم بومی—مثل دوربین، سیستم فایل، یا حسگرهای سخت‌افزاری پیشرفته—که معمولاً در میاناهای برنامه‌سازی کاربردی وب استاندارد ارائه نمی‌شوند استفاده کنند.

موارد استفاده

پیاده‌سازی پل جاوا اسکریپت سناریوهای یکپارچه‌سازی مختلفی را فعال می‌کند که در آن‌ها محتوای وب به دسترسی عمیق‌تری به سیستم‌عامل Android نیاز دارد. در زیر چند نمونه آورده شده است:

  • ادغام پلاتفرم: راه‌اندازی عناصر بومی میانای کاربری Android (برای مثال، پیام‌واره‌های زیست‌سنجشی، BottomSheetDialog) از صفحه وب.
  • عملکرد: واگذاری وظایف محاسباتی سنگین به کد بومی Java یا Kotlin.
  • ماندگاری داده‌ها: دسترسی به پایگاه‌های داده رمزگذاری‌شده محلی یا اولویت‌های هم‌رسانی‌شده.
  • انتقال داده‌های بزرگ: انتقال فایل‌های رسانه‌ای یا ساختارهای داده پیچیده بین برنامه و رندرکننده وب.

سازوکارهای ارتباطی

‫Android سه نسل اصلی از «میاناهای برنامه‌سازی کاربردی» برای ایجاد پل بومی ارائه می‌دهد. اگرچه همه آن‌ها هنوز دردسترس هستند، اما ازنظر امنیت، کاربردپذیری، و عملکرد تفاوت‌های قابل‌توجهی دارند.

استفاده از addWebMessageListener (توصیه‌شده)

addWebMessageListener مدرن‌ترین و توصیه‌شده‌ترین روش برای ارتباط بین محتوای وب و کد برنامه بومی است. این ویژگی سهولت استفاده از میانای جاوااسکریپت را با امنیت سیستم پیام‌رسانی ترکیب می‌کند.

نحوه عملکرد: برنامه شنونده‌ای با نام مشخص و مجموعه‌ای از قوانین مبدأ مجاز اضافه می‌کند. سپس «وب‌نما» تضمین می‌کند که شیء جاوا اسکریپت از لحظه‌ای که صفحه شروع به بار شدن می‌کند در حوزه جهانی (window.objectName) حضور داشته باشد.

راه‌اندازی: برای اطمینان از اینکه «وب‌نما» شیء جاوا اسکریپت را قبل‌از اجرای هرگونه دستورگان تزریق می‌کند، باید addWebMessageListener را قبل‌از پیمایش به صفحه (برای مثال، فراخواندن WebViewCompat.navigate یا loadUrl) فراخوانی کنید.

ویژگی‌های کلیدی:

  • امنیت و اعتماد: برخلاف «میاناهای برنامه‌سازی کاربردی» قدیمی، این روش درطول مقداردهی اولیه به Set<String> از allowedOriginRules نیاز دارد. این سازوکار اصلی برای ایجاد اعتماد است.

    وقتی مبدأ مطمئنی مثل https://example.com را مشخص می‌کنید، «وب‌نما» تضمین می‌کند که فقط اشیا جاوا اسکریپت تزریق‌شده را درمعرض صفحه‌های وبی قرار می‌دهد که از آن مبدأ دقیق بار شده‌اند.

    کاربرگ برگشتی شنونده بومی با هر پیام پارامتر sourceOrigin دریافت می‌کند. اگر پل شما از چندین مبدأ مجاز پشتیبانی می‌کند، می‌توانید از این برای درستی‌سنجی مبدأ دقیق فرستنده استفاده کنید.

    ازآنجایی‌که WebView این بررسی‌های مبدأ را در سطح پلاتفرم به‌طور دقیق اجرا می‌کند، برنامه شما به‌طورکلی می‌تواند به پیام‌های دریافتی از sourceOrigin قابل‌اعتماد به‌عنوان پیام‌های درست تکیه کند و نیاز به اعتبارسنجی دقیق محتوا در اکثر پیاده‌سازی‌های استاندارد را ازبین ببرد.

    • «وب‌نما» قوانین را براساس طرح (HTTP/HTTPS)، میزبان، و درگاه مطابقت می‌دهد.
    • «وب‌نما» مسیرها را نادیده می‌گیرد. برای مثال، https://example.com اجازه می‌دهد https://example.com/login و https://example.com/home.
    • «وب‌نما» نویسه‌های عام را برای شروع میزبان در زیردامنه‌ها به‌شدت محدود می‌کند. برای مثال، https://*.example.com با https://foo.example.com مطابقت دارد اما با https://example.com مطابقت ندارد. اگر باید https://example.com و زیردامنه‌های آن را مطابقت دهید، باید هر قانون مبدأ را به‌طور جداگانه به فهرست مجاز اضافه کنید (برای مثال، "https://example.com", "https://*.example.com"). نمی‌توانید از نویسه‌های عام برای طرح یا در میان دامنه استفاده کنید.

    این کار پل را به دامنه‌های درستی‌سنجی‌شده محدود می‌کند و از اجرای کد بومی توسط محتوای غیرمجاز طرف سوم یا iframe تزریق‌شده جلوگیری می‌کند.

  • پشتیبانی چندقاب: در همه قاب‌هایی که با قوانین مبدأ مطابقت دارند کار می‌کند.

  • رشته‌بندی: برگشت شنونده در رشته اصلی (میانای کاربر) برنامه اجرا می‌شود. اگر پل شما باید پردازش داده‌های پیچیده، تجزیه JSON، یا جستجوی پایگاه داده را انجام دهد، باید این کار را به یک رشته پس‌زمینه واگذار کنید تا از مسدود شدن رابط کاربری برنامه با خطای «برنامه پاسخ نمی‌دهد» (ANR) جلوگیری شود.

  • دوطرفه: وقتی صفحه وب پیامی ارسال می‌کند، برنامه JavaScriptReplyProxy دریافت می‌کند که می‌تواند از آن برای ارسال پیام به آن قاب خاص استفاده کند. می‌توانید این شیء replyProxy را حفظ کنید و هرزمان خواستید از آن برای ارسال هر تعداد پیام به صفحه استفاده کنید، نه فقط برای پاسخ دادن به هر پیام تکی که صفحه ارسال می‌کند. اگر چارچوب مبدأ به جای دیگری پیمایش کند یا ازبین برود، پیام‌های ارسال‌شده بااستفاده از postMessage() در پراکسی بی‌صدا نادیده گرفته می‌شوند.

  • شروع ازطرف برنامه: اگرچه صفحه وب همیشه باید کانال ارتباطی با برنامه را شروع کند، برنامه بومی می‌تواند به‌صورت یک‌طرفه از صفحه وب بخواهد این فرایند را شروع کند. برنامه بومی می‌تواند با صفحه وب با addDocumentStartJavaScript() (برای ارزیابی جاوا اسکریپت قبل‌از بار شدن صفحه) یا evaluateJavaScript() (برای ارزیابی جاوا اسکریپت پس‌از بار شدن صفحه) ارتباط برقرار کند.

محدودیت: این «میانای برنامه‌سازی کاربردی» داده‌ها را به‌صورت رشته یا byte[] آرایه ارسال می‌کند. برای ساختارهای داده پیچیده‌تر، مانند اشیای JSON، باید این ساختار را به یکی از این قالب‌ها سریال‌سازی کنید و سپس در طرف دیگر آن را غیرسریال‌سازی کنید تا ساختار داده بازسازی شود.

نمونه استفاده:

برای درک توالی کامل تبادل پیام دوطرفه، رویدادها به این ترتیب پیش می‌روند:

  1. راه‌اندازی (برنامه): برنامه بومی شنونده را با addWebMessageListener ثبت می‌کند و ناوبری صفحه را راه‌اندازی می‌کند (مثلاً با WebViewCompat.navigate یا loadUrl).
  2. ارسال پیام (وب): جاوا اسکریپت صفحه وب myObject.postMessage(message) را برای شروع ارتباط فرا می‌خواند.
  3. دریافت و پاسخ به پیام (برنامه): برنامه پیام را در بازخوان تماس برگشتی دریافت می‌کند و بااستفاده از replyProxy.postMessage() ارائه‌شده پاسخ می‌دهد.
  4. دریافت پاسخ (وب): صفحه وب پاسخ ناهم‌زمان را در تابع برگشتی myObject.onmessage() دریافت می‌کند.

کاتلین

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)
}

جاوا

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);
}

جاوا اسکریپت زیر پیاده‌سازی سمت کارخواه addWebMessageListener را نشان می‌دهد و به محتوای وب اجازه می‌دهد از برنامه بومی پیام دریافت کند و پیام‌های خود را ازطریق پراکسی myObject ارسال کند.

myObject.onmessage = function(event) {
    console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");

استفاده از postWebMessage (جایگزین)

‫Android این ویژگی را معرفی کرد تا جایگزینی ناهمگام و مبتنی بر پیام مشابه با window.postMessage در وب ارائه دهد.

نحوه عملکرد: برنامه از WebViewCompat.postWebMessage برای ارسال بار به قاب اصلی صفحه وب استفاده می‌کند. برای ایجاد کانال ارتباطی دوطرفه، می‌توانید WebMessageChannel ایجاد کنید و یکی از درگاه‌های آن را با پیام به محتوای وب منتقل کنید.

ویژگی‌ها:

  • ناهم‌زمان: مانند addWebMessageListener، این روش از پیام‌رسانی ناهم‌زمان استفاده می‌کند که تضمین می‌کند صفحه وب درحین پردازش داده‌ها در پس‌زمینه توسط برنامه، به تعاملات کاربر پاسخگو باقی بماند.
  • آگاه از مبدأ: می‌توانید targetOrigin را مشخص کنید تا مطمئن شوید WebView داده‌ها را فقط به وب‌سایت مطمئن ارائه می‌دهد.

محدودیت‌ها:

  • محدوده: این API ارتباط را به چارچوب اصلی محدود می‌کند. از خطاب مستقیم یا ارسال پیام به iframe پشتیبانی نمی‌کند.
  • محدودیت‌های URI: نمی‌توانید از این روش برای محتوایی که بااستفاده از data: نشانی‌های وب، file: نشانی‌های وب، یا loadData() بار شده است استفاده کنید، مگر اینکه «*» را به‌عنوان مبدأ هدف مشخص کنید. با انجام این کار، هر صفحه‌ای می‌تواند پیام را دریافت کند.
  • خطر هویت: محتوای وب راه روشنی برای درستی‌سنجی هویت فرستنده ندارد. پیامی که صفحه وب دریافت می‌کند می‌تواند از برنامه بومی شما یا iframe دیگری منشأ گرفته باشد.

وقتی به کانال ساده و ناهم‌زمان برای داده‌های رشته‌ای در نسخه‌های قدیمی‌تر Android که از addWebMessageListener پشتیبانی نمی‌کنند نیاز دارید، از این روش استفاده کنید.

استفاده از addJavascriptInterface (قدیمی)

قدیمی‌ترین روش شامل تزریق مستقیم نمونه شیء بومی در WebView است.

نحوه عملکرد: کلاس Kotlin یا Java را تعریف می‌کنید، روش‌های مجاز را با @JavascriptInterface حاشیه‌نویسی می‌کنید، و نمونه‌ای از کلاس را بااستفاده از addJavascriptInterface(Object, String) به WebView اضافه می‌کنید.

ویژگی‌ها:

  • هم‌زمان: محیط اجرای جاوا اسکریپت تا زمانی که متد در کد Android شما برگردد مسدود می‌شود.
  • ایمنی رشته: سیستم متدهای رشته پس‌زمینه را فراخوانی می‌کند، که نیاز به همگام‌سازی دقیق در سمت Kotlin یا Java دارد.
  • خطر امنیتی: به‌طور پیش‌فرض، addJavascriptInterface برای همه چارچوب‌های درون «وب‌نما»، ازجمله iframe، دردسترس است. کنترل دسترسی مبتنی بر مبدأ ندارد. به‌دلیل رفتار ناهم‌زمان «وب‌نما»، نمی‌توان نشانی وب چارچوبی را که رابط شما را فرا می‌خواند به‌طور ایمن تعیین کرد. نباید برای درستی‌سنجی امنیت به روش‌هایی مانند WebView.getUrl() تکیه کنید، زیرا تضمینی برای دقیق بودن آن‌ها وجود ندارد و مشخص نمی‌کند که کدام قاب خاص درخواست را ارسال کرده است.

تبدیل و اجبار نوع داده

هنگام استفاده از addJavascriptInterface، «پل جاوا مبتنی بر Chromium» انواع داده را بین زمان اجرای جاوا اسکریپت و کد برنامه Android شما تبدیل می‌کند.

قوانین اجبار زیر برای پارامترهای روش و مقادیر برگشتی اعمال می‌شود.

نگاشت نوع پارامتر (جاوا اسکریپت به جاوا)

وقتی جاوا اسکریپت آرگومان‌هایی را به روش Java یا Kotlin حاشیه‌گذاری‌شده منتقل می‌کند، پل مقادیر جاوا اسکریپت را به انواع پارامتر Java مربوطه تبدیل می‌کند:

نوع پارامتر جاوا مقدار آرگومان جاوا اسکریپت رفتار اجباری
byte، short، int، long عدد (عدد صحیح) مقادیر به نوع عدد صحیح هدف تبدیل می‌شوند. مقادیر خارج از محدوده براساس قوانین استاندارد تبدیل عددی، به محدوده برمی‌گردند.
byte، short، int، long NaN به 0 تبدیل می‌شود.
byte، short، int، long Infinity برای byte و short به -1 تبدیل می‌شود، یا برای int و long به Integer.MAX_VALUE و Long.MAX_VALUE تبدیل می‌شود.
float، double عدد به مقدار ممیز شناور Java مربوطه تبدیل می‌شود.
float، double NaN / Infinity به Float.NaN،‏ Double.NaN،‏ Float.POSITIVE_INFINITY، یا Double.POSITIVE_INFINITY تبدیل می‌شود.
char عدد (عدد صحیح) به نقطه کد یونی‌کد مربوطه تبدیل شد.
char غیرعدد صحیح، NaN،‏ Infinity به \u0000 تبدیل می‌شود.
boolean true / false به Java true یا false تبدیل می‌شود.
boolean عدد، رشته، شیء به false تبدیل می‌شود (ازجمله رشته‌های غیرخالی و اعداد غیرصفر).
String رشته مقدار رشته حفظ می‌شود.
String عدد، بولی به‌صورت نمایش رشته‌ای قالب‌بندی شده است (برای مثال، "42"، "true"، "false").
String null / undefined ‫null به null جاوا تبدیل می‌شود؛ undefined به رشته حرفی "undefined" تبدیل می‌شود.
String Object،‏ ArrayBuffer،‏ TypedArray به رشته تحت‌اللفظی "undefined" تبدیل می‌شود.
آرایه ابتدایی (مثل int[]،‏ byte[]،‏ boolean[]) یا String[] آرایه ([...]) به آرایه یک‌بعدی Java از نوع عنصر هدف تبدیل می‌شود. آرایه‌های پراکنده شاخص‌های تخصیص‌نیافته را با مقادیر پیش‌فرض (0، false، null) پر می‌کنند.
آرایه ابتدایی (مثل int[]، byte[]) TypedArray (Int8Array،‏ Uint8Array،‏ Int32Array،‏ Float64Array) عناصر به آرایه ابتدایی Java مربوطه تبدیل می‌شوند.
آرایه چندبعدی (مثل int[][]) آرایه تودرتو ([[...]]) پشتیبانی نمی‌شود. پارامترهای آرایه چندبعدی به null ارزیابی می‌شوند.
ArrayBuffer، DataView ArrayBuffer، DataView به‌عنوان آرایه پشتیبانی نمی‌شود. نمونه‌های ArrayBuffer و DataView به null ارزیابی می‌شوند.
‫Object یا کلاس سفارشی شیء جاوا اسکریپت ({...}) پشتیبانی نمی‌شود. حروف شیء جاوا اسکریپت دلخواه در جاوا به null ارزیابی می‌شود.
‫Object یا کلاس سفارشی بسته‌بندی‌کننده شیء جاوا تزریق‌شده پشتیبانی‌شده (سفر رفت‌وبرگشت). نمونه Java زیرین را به روش Java منتقل می‌کند. اگر نوع جاوا با امضای پارامتر مطابقت نداشته باشد، استثنای جاوا اسکریپت ایجاد می‌کند.
انواع جعبه‌ای (مثل Integer، Double، Boolean) عدد، بولی پشتیبانی نمی‌شود. انواع اولیه جعبه‌ای به‌عنوان اشیای مات درنظر گرفته می‌شوند و به null ارزیابی می‌شوند.
هر نوع ابتدایی null / undefined به مقادیر پیش‌فرض تبدیل می‌شود (0، 0.0، \u0000، false).
‫Object،‏ String، آرایه null به Java null تبدیل می‌شود.
نگاشت نوع برگشتی (جاوا به جاوا اسکریپت)

وقتی یک روش Java یا Kotlin حاشیه‌گذاری‌شده مقداری را برمی‌گرداند، پل آن را به نوع JavaScript تبدیل می‌کند:

نوع برگشتی جاوا مقدار جاوا اسکریپت جاوا اسکریپت typeof
boolean true / false "boolean"
byte،‏ short،‏ int،‏ long،‏ float،‏ double عدد "number"
char عدد (نقطه کد یونی‌کد) "number"
‫String (غیرتهی) مقدار رشته‌ای "string"
‫String‏ (null) undefined "undefined"
void undefined "undefined"
آرایه Java (مثل int[]، String[]) undefined ‫"undefined". از مقادیر برگشتی آرایه پشتیبانی نمی‌شود. روش Java اجرا نمی‌شود و undefined بدون ایجاد استثنا برگردانده می‌شود.
«شیء جاوا» / نوع سفارشی (غیرتهی) بسته‌بندی شیء "object". یک داده‌پوش جاوا اسکریپت در اطراف نمونه Java ایجاد می‌کند. کد جاوا اسکریپت می‌تواند هر روش عمومی در این شیء را که با @JavascriptInterface حاشیه‌نویسی شده است فراخوانی کند.
شیء Java / نوع سفارشی (null) null "object"
نوع اولیه جعبه‌ای (مثل Integer، Double) بسته‌بندی شیء "object". به‌عنوان بسته‌بندی شیء جاوا مات بدون روش‌های @JavascriptInterface دردسترس برگردانده شد، که مقدار را در جاوا اسکریپت غیرقابل استفاده می‌کند.

روش و دردسترس بودن عضو

پل جاوا اسکریپت قوانین سختگیرانه‌ای برای دسترسی و رؤیت‌پذیری عضو اعمال می‌کند تا از اجرای ناخواسته کد محافظت کند:

  • فیلدها آشکار نیستند: فیلدهای جاوا (ازجمله فیلدهای public و public final) از جاوا اسکریپت قابل‌دسترس نیستند و به undefined ارزیابی می‌شوند.
  • الزام شرح: فقط روش‌هایی که به‌طور صریح با @JavascriptInterface شرح‌گذاری شده‌اند در معرض JavaScript قرار می‌گیرند.
  • محدودیت‌های رؤیت‌پذیری: روش‌ها باید public باشند. روش‌های private و protected هرگز درمعرض «جاوا اسکریپت» قرار نمی‌گیرند، حتی اگر دارای @JavascriptInterface شرح باشند.
  • روش‌های ثابت: روش‌های ثابت که با @JavascriptInterface حاشیه‌نویسی شده‌اند از جاوا اسکریپت قابل‌فراخوانی هستند.
  • وراثت و ملغی کردن: وقتی زیرکلاسی روشی را ملغی می‌کند، @JavascriptInterface گزارمان‌ها به‌ارث برده نمی‌شوند. اگر زیرکلاسی روش حاشیه‌نویسی‌شده‌ای را از ابرکلاس ملغی کند، زیرکلاس باید به‌طور صریح حاشیه‌نویسی @JavascriptInterface را در روش ملغی‌شده اضافه کند تا آن را درمعرض JavaScript قرار دهد. روش‌های عمومی غیرلغو‌شده‌ای که از یک ابرکلاس به‌ارث می‌رسند، اگر در ابرکلاس حاشیه‌نویسی شده باشند، همچنان دردسترس خواهند بود.
  • محافظت دربرابر انعکاس: روش‌های انعکاس استاندارد جاوا (مثل getClass()) مسدود می‌شوند و استثنای جاوا اسکریپت ایجاد می‌کنند تا از آسیب‌پذیری‌های اجرای کد از راه دور جلوگیری کنند.
  • سربار کردن روش: روش‌های سربارشده Java پشتیبانی می‌شوند. پل فقط براساس تعداد آرگومان‌های ارسال‌شده تماس‌های روش را حل می‌کند و انواع آرگومان را درنظر نمی‌گیرد. فراخوانی روش سربارگذاری‌شده با تعداد آرگومان نامعتبر باعث ایجاد استثنا در جاوا اسکریپت می‌شود. اگر دو سربار دارای تعداد آرگومان یکسان باشند، یکی به‌صورت تصادفی انتخاب خواهد شد.

خلاصه سازوکارها

جدول زیر مقایسه‌ای سریع از سه سازوکار اصلی پیاده‌سازی پل بومی ارائه می‌دهد:

روش addWebMessageListener postWebMessage addJavascriptInterface
پیاده‌سازی ناهمگام (شنونده در رشته اصلی) غیرهمگام هم‌زمان
امنیت بالاترین (براساس فهرست مجازها) بالا (مبدأآگاه) کم (بدون بررسی مبدأ)
پیچیدگی متوسط متوسط ساده
جهت دوطرفه دوطرفه وب به برنامه
حداقل نسخه «وب‌نما» نسخه ۸۲ (و Jetpack Webkit 1.3.0) نسخه ۴۵ (و Jetpack Webkit 1.1.0) همه نسخه‌ها
توصیه‌شده بله نه نه

انجام انتقال‌های داده بزرگ

هنگام انتقال داده‌برهای بزرگ، مثل رشته‌های چند مگابایتی یا فایل‌های دودویی، باید حافظه را به‌دقت مدیریت کنید تا از خطاهای «برنامه پاسخ نمی‌دهد» (ANR) یا خرابی در دستگاه‌های ۳۲ بیتی جلوگیری کنید. این بخش درباره تکنیک‌های مختلف و محدودیت‌های مرتبط با انتقال مقادیر قابل‌توجهی از داده‌ها بین برنامه میزبان و محتوای وب بحث می‌کند.

انتقال داده‌های دودویی با آرایه‌های بایت

با کلاس WebMessageCompat، می‌توانید آرایه‌های byte[] را مستقیماً به‌جای تبدیل داده‌های باینری به رشته‌های Base64 ارسال کنید. ازآنجایی‌که Base64 تقریباً ۳۳٪ سربار به اندازه داده اضافه می‌کند، این روش به‌طور قابل‌توجهی ازنظر حافظه کارآمدتر و سریع‌تر است.

  • مزیت باینری: انتقال داده‌های باینری مانند فایل‌های تصویری یا صوتی بین برنامه بومی و محتوای وب.
  • محدودیت: حتی با آرایه‌های بایت، سیستم داده‌ها را در مرز ارتباط بین‌فرایندی (IPC) بین برنامه و فرایند مجزایی که WebView برای پرداز کردن محتوای وب استفاده می‌کند کپی می‌کند. این روش همچنان برای فایل‌های بسیار بزرگ حافظه قابل‌توجهی مصرف می‌کند.

نمونه‌های کد زیر نشان می‌دهد که چگونه addWebMessageListener را در سمت برنامه بومی راه‌اندازی کنید تا پیام‌های نشان‌گذاری‌شده با WebMessageCompat.TYPE_ARRAY_BUFFER را دریافت کند و درصورت تمایل با بررسی WebViewFeature.MESSAGE_ARRAY_BUFFER با داده‌های باینری پاسخ دهد.

کاتلین

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
        )
    }
}

جاوا

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
      );
  }
}

کد جاوا اسکریپت زیر پیاده‌سازی سمت مشتری 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]);
        }
    };
}

بار کردن کارآمد داده‌های در مقیاس بزرگ

برای فایل‌های بسیار بزرگ (>۱۰ مگابایت)، از روش shouldInterceptRequest برای جاری‌سازی داده‌ها استفاده کنید:

  1. صفحه وب fetch() تماسی را به نشانی وب سفارشی جای‌بانی آغاز می‌کند. برای مثال، https://app.local/large-file.
  2. برنامه Android این درخواست را در WebViewClient.shouldInterceptRequest رهگیری می‌کند.
  3. برنامه داده‌ها را به‌صورت InputStream برمی‌گرداند.

این کار امکان جاری‌سازی داده‌ها را به‌صورت تکه‌ای فراهم می‌کند، به‌جای اینکه کل بار مفید یک‌باره در حافظه بار شود.

تابع JavaScript زیر کد سمت کارخواه را برای بار کردن کارآمد فایل باینری بزرگ از برنامه بومی بااستفاده از تماس fetch() استاندارد با نشانی وب سفارشی جای‌بان نشان می‌دهد.

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 نشان می‌دهد تا فایل باینری بزرگی را با رهگیری نشانی وب جای‌بان سفارشی درخواست‌شده توسط محتوای وب جاری‌سازی کند.

کاتلین

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)
    }
}

جاوا

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های پنهان محافظت می‌کنند، اما نمی‌توانند از آسیب‌پذیری‌های «نوشتن بین‌سایتی» (XSS) در دامنه مطمئن خودتان محافظت کنند. برای مثال، اگر صفحه وب شما محتوای تولیدشده توسط کاربر را نمایش دهد و دربرابر حمله «تزریق اسکریپت در برنامه وب‌نما» آسیب‌پذیر باشد، مهاجم می‌تواند اسکریپتی را اجرا کند که به‌عنوان مبدأ معتمد شما عمل می‌کند. پیش‌از اجرای عملیات حساس پلاتفرم بومی، اعمال اعتبارسنجی روی محموله‌های پیام را درنظر بگیرید.

  • به‌حداقل رساندن سطح: فقط روش‌ها یا داده‌های خاصی را که صفحه وب نیاز دارد آشکار کنید.

  • بررسی ویژگی‌ها در زمان اجرا: میاناهای برنامه‌سازی کاربردی پل اخیر، ازجمله addWebMessageListener، بخشی از کتابخانه Jetpack Webkit هستند. بنابراین، همیشه قبل‌از تماس گرفتن با پشتیبانی، بااستفاده از WebViewFeature.isFeatureSupported() بررسی کنید که پشتیبانی ارائه می‌دهند یا نه.