این صفحه روشهای مختلف و روالهای مطلوب برای ایجاد
پل بومی، که بهعنوان پل جاوا اسکریپت نیز شناخته میشود، را برای تسهیل ارتباط
بین محتوای وب در 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، باید این ساختار را به یکی از این قالبها سریالسازی کنید و سپس در طرف دیگر آن را غیرسریالسازی کنید تا ساختار داده بازسازی شود.
نمونه استفاده:
برای درک توالی کامل تبادل پیام دوطرفه، رویدادها به این ترتیب پیش میروند:
- راهاندازی (برنامه): برنامه بومی شنونده را با
addWebMessageListenerثبت میکند و ناوبری صفحه را راهاندازی میکند (مثلاً باWebViewCompat.navigateیاloadUrl). - ارسال پیام (وب): جاوا اسکریپت صفحه وب
myObject.postMessage(message)را برای شروع ارتباط فرا میخواند. - دریافت و پاسخ به پیام (برنامه): برنامه پیام را در
بازخوان تماس برگشتی دریافت میکند و بااستفاده از
replyProxy.postMessage()ارائهشده پاسخ میدهد. - دریافت پاسخ (وب): صفحه وب پاسخ ناهمزمان را در تابع برگشتی
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 برای جاریسازی دادهها استفاده کنید:
- صفحه وب
fetch()تماسی را به نشانی وب سفارشی جایبانی آغاز میکند. برای مثال،https://app.local/large-file. - برنامه Android این درخواست را در
WebViewClient.shouldInterceptRequest رهگیری میکند. - برنامه دادهها را بهصورت
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()بررسی کنید که پشتیبانی ارائه میدهند یا نه.