این صفحه به بررسی روشها و بهترین شیوههای مختلف برای ایجاد یک پل بومی، که با نام پل جاوا اسکریپت نیز شناخته میشود، میپردازد تا ارتباط بین محتوای وب در یک WebView و یک برنامه اندروید میزبان را تسهیل کند.
این امر توسعهدهندگان وب را قادر میسازد تا از جاوا اسکریپت برای دسترسی به ویژگیهای بومی پلتفرم - مانند دوربین، سیستم فایل یا حسگرهای سختافزاری پیشرفته - که APIهای استاندارد وب معمولاً ارائه نمیدهند، استفاده کنند.
موارد استفاده
پیادهسازی پل جاوااسکریپت، سناریوهای مختلف ادغام را که در آنها محتوای وب نیاز به دسترسی عمیقتر به سیستم عامل اندروید دارد، امکانپذیر میکند. در زیر چند نمونه آورده شده است:
- یکپارچهسازی پلتفرم : فعالسازی اجزای رابط کاربری بومی اندروید (برای مثال، Biometric prompts،
BottomSheetDialog) از یک صفحه وب. - عملکرد : واگذاری وظایف محاسباتی سنگین به کد بومی جاوا یا کاتلین.
- ماندگاری دادهها : دسترسی به پایگاههای داده رمزگذاری شده محلی یا تنظیمات برگزیده مشترک.
- انتقال دادههای حجیم : انتقال فایلهای رسانهای یا ساختارهای داده پیچیده بین برنامه و رندرکننده وب.
مکانیسمهای ارتباطی
اندروید سه نسل اصلی از APIها را برای ایجاد یک پل بومی ارائه میدهد. اگرچه همه آنها هنوز در دسترس هستند، اما از نظر امنیت، قابلیت استفاده و عملکرد تفاوتهای چشمگیری دارند.
استفاده از addWebMessageListener (توصیه میشود)
addWebMessageListener مدرنترین و توصیهشدهترین رویکرد برای ارتباط بین محتوای وب و کد برنامه بومی است. این رویکرد، سهولت استفاده از رابط جاوا اسکریپت را با امنیت سیستم پیامرسانی ترکیب میکند.
نحوه کار : برنامه یک شنونده با نام خاص و مجموعهای از قوانین مبدا مجاز اضافه میکند. سپس WebView از لحظه شروع بارگذاری صفحه، اطمینان حاصل میکند که شیء جاوا اسکریپت در محدوده سراسری ( window.objectName ) وجود دارد.
مقداردهی اولیه : برای اطمینان از اینکه WebView قبل از اجرای هر اسکریپتی، شیء جاوا اسکریپت را تزریق میکند، باید قبل از پیمایش به صفحه (مانند فراخوانی WebViewCompat.navigate یا loadUrl )، addWebMessageListener فراخوانی کنید.
ویژگیهای کلیدی :
امنیت و اعتماد : برخلاف APIهای قدیمی، این روش در طول مقداردهی اولیه به
Set<String>ازallowedOriginRulesنیاز دارد. این مکانیسم اصلی برای ایجاد اعتماد است.وقتی یک منبع معتبر مانند
https://example.comرا مشخص میکنید، WebView تضمین میکند که اشیاء جاوا اسکریپت تزریق شده را فقط در صفحات وب بارگذاری شده از همان منبع دقیق نمایش دهد.تابع فراخوانی شنونده نیتیو (native listener) با هر پیام، یک پارامتر
sourceOriginدریافت میکند. اگر بریج شما از چندین مبدا مجاز پشتیبانی میکند، میتوانید از این برای تأیید مبدا دقیق فرستنده استفاده کنید.از آنجا که وبویو این بررسیهای مبدا را در سطح پلتفرم به شدت اجرا میکند، برنامه شما میتواند به طور کلی به پیامهای دریافتی از یک
sourceOriginمعتبر به عنوان پیامهای معتبر تکیه کند و نیاز به اعتبارسنجی دقیق بار داده را در اکثر پیادهسازیهای استاندارد از بین ببرد.- وب ویو قوانین را با طرح (HTTP/HTTPS)، میزبان و پورت مطابقت میدهد.
- وب ویو مسیرها را نادیده میگیرد. برای مثال،
https://example.comhttps://example.com/loginوhttps://example.com/homeاجازه میدهد. - وبویو به شدت استفاده از wildcardها را برای زیردامنهها به ابتدای میزبان محدود میکند. برای مثال،
https://*.example.comباhttps://foo.example.comمطابقت دارد اما باhttps://example.comمطابقت ندارد. اگر نیاز دارید که همhttps://example.comو هم زیردامنههای آن را مطابقت دهید، باید هر قانون مبدا را جداگانه به لیست مجاز اضافه کنید (برای مثال،"https://example.com", "https://*.example.com"). شما نمیتوانید از wildcardها برای طرح یا در وسط یک دامنه استفاده کنید.
این کار، پل را به دامنههای تأیید شده محدود میکند و از اجرای کد بومی توسط محتوای غیرمجاز شخص ثالث یا iframeهای تزریقشده جلوگیری میکند.
پشتیبانی از چند فریم : در تمام فریمهایی که با قوانین مبدا مطابقت دارند، کار میکند.
Threading : فراخوانی شنونده (listener callback) روی thread اصلی (UI) برنامه اجرا میشود. اگر bridge شما نیاز به پردازش دادههای پیچیده، تجزیه JSON یا جستجو در پایگاه داده دارد، باید این کار را به یک thread پسزمینه منتقل کنید تا از هنگ کردن UI برنامه با خطای "app not responding" (ANR) جلوگیری شود.
دوطرفه : وقتی صفحه وب پیامی ارسال میکند، برنامه یک
JavaScriptReplyProxyدریافت میکند که میتواند از آن برای ارسال پیامها به آن فریم خاص استفاده کند. شما میتوانید این شیءreplyProxyرا نگه دارید و در هر زمانی از آن برای ارسال هر تعداد پیام به صفحه استفاده کنید، نه فقط برای پاسخ به هر پیام جداگانهای که صفحه ارسال میکند. اگر فریم مبدا حرکت نکند یا از بین برود، پیامهای ارسالی با استفاده ازpostMessage()روی پروکسی بیصدا نادیده گرفته میشوند.شروع به کار در سمت برنامه : اگرچه صفحه وب همیشه باید کانال ارتباطی با برنامه را آغاز کند، برنامه بومی میتواند به طور یکجانبه صفحه وب را برای شروع این فرآیند ترغیب کند. برنامه بومی میتواند با استفاده از
addDocumentStartJavaScript()(برای ارزیابی جاوا اسکریپت قبل از بارگذاری صفحه) یاevaluateJavaScript()(برای ارزیابی جاوا اسکریپت پس از بارگذاری صفحه) با صفحه وب ارتباط برقرار کند.
محدودیت : این API دادهها را به صورت رشته یا آرایههای byte[] ارسال میکند. برای ساختارهای داده پیچیدهتر، مانند اشیاء JSON، باید این را به یکی از این قالبها سریالایز کنید و سپس در طرف دیگر deserialize کنید تا ساختار داده بازسازی شود.
مثال کاربرد :
برای درک کامل توالی تبادل پیام دو طرفه، رویدادها به این ترتیب ادامه مییابند:
- شروع (برنامه) : برنامه بومی شنونده را با
addWebMessageListenerثبت میکند و پیمایش صفحه را آغاز میکند (مانندWebViewCompat.navigateیاloadUrl). - ارسال پیام (وب) : جاوا اسکریپت صفحه وب،
myObject.postMessage(message)را برای شروع ارتباط فراخوانی میکند. - دریافت و پاسخ پیام (برنامه) : برنامه پیام را در callback شنونده دریافت میکند و با استفاده از
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 (جایگزین)
اندروید این را معرفی کرد تا یک جایگزین مبتنی بر پیامرسانی ناهمزمان، مشابه window.postMessage وب، ارائه دهد.
نحوه کار : این برنامه از WebViewCompat.postWebMessage برای ارسال یک payload به فریم اصلی صفحه وب استفاده میکند. برای ایجاد یک کانال ارتباطی دو طرفه، میتوانید یک WebMessageChannel ایجاد کنید و یکی از پورتهای آن را به همراه پیام به محتوای وب منتقل کنید.
ویژگیها :
- ناهمگام (Asynchronous ): مانند
addWebMessageListener، این متد از پیامرسانی ناهمگام استفاده میکند که تضمین میکند صفحه وب در حالی که برنامه دادهها را در پسزمینه پردازش میکند، به تعاملات کاربر پاسخگو باقی میماند. - آگاه از مبدا : میتوانید یک
targetOriginمشخص کنید تا مطمئن شوید که WebView دادهها را فقط به یک وبسایت معتبر ارسال میکند.
محدودیتها :
- محدوده : این API ارتباط را به فریم اصلی محدود میکند. از آدرسدهی مستقیم یا ارسال پیام به iframeها پشتیبانی نمیکند.
- محدودیتهای URI : شما نمیتوانید از این روش برای محتوایی که با استفاده
data:URIs،file:URIs یاloadData()بارگذاری میشود استفاده کنید، مگر اینکه "*" را به عنوان مبدا هدف مشخص کنید. انجام این کار به هر صفحهای اجازه میدهد پیام را دریافت کند. - خطر هویت : هیچ راه مشخصی برای تأیید هویت فرستنده توسط محتوای وب وجود ندارد. پیامی که صفحه وب دریافت میکند میتواند از برنامه اصلی شما یا یک iframe دیگر باشد.
از این متد زمانی استفاده کنید که به یک کانال ساده و غیرهمزمان برای دادههای مبتنی بر رشته در نسخههای قدیمیتر اندروید که addWebMessageListener پشتیبانی نمیکنند، نیاز دارید.
استفاده از addJavascriptInterface (قدیمی)
قدیمیترین روش شامل تزریق مستقیم یک نمونه شیء بومی به WebView است.
نحوه کار : شما یک کلاس Kotlin یا Java تعریف میکنید، متدهای مجاز را با @JavascriptInterface حاشیهنویسی میکنید و با استفاده از addJavascriptInterface(Object, String) یک نمونه از کلاس را به WebView اضافه میکنید.
ویژگیها :
- همگام (Synchronous ): محیط اجرای جاوا اسکریپت تا زمانی که متد موجود در کد اندروید شما خروجی دهد، مسدود میشود.
- ایمنی نخ : سیستم متدها را روی یک نخ پسزمینه فراخوانی میکند که نیاز به همگامسازی دقیق در سمت کاتلین یا جاوا دارد.
- ریسک امنیتی : به طور پیشفرض،
addJavascriptInterfaceبرای هر فریم درون WebView، از جمله iframeها، در دسترس است. فاقد کنترل دسترسی مبتنی بر مبدا است. به دلیل رفتار ناهمزمان WebView، تعیین ایمن URL فریمی که رابط شما را فراخوانی میکند، امکانپذیر نیست. شما نباید برای تأیید امنیت به روشهایی مانندWebView.getUrl()تکیه کنید، زیرا تضمینی برای دقت آنها وجود ندارد و مشخص نمیکنند که کدام فریم خاص درخواست را انجام داده است.
تبدیل نوع دادهها و اجبار
هنگام استفاده از addJavascriptInterface ، پل جاوا مبتنی بر Chromium انواع دادهها را بین زمان اجرای جاوا اسکریپت و کد برنامه اندروید شما تبدیل میکند.
قوانین اجباری زیر برای پارامترهای متد و مقادیر برگشتی اعمال میشوند.
نگاشت نوع پارامتر (از جاوا اسکریپت به جاوا)
وقتی جاوا اسکریپت آرگومانها را به یک متد حاشیهنویسی شده جاوا یا کاتلین ارسال میکند، پل، مقادیر جاوا اسکریپت را به انواع پارامترهای جاوای مربوطه تبدیل میکند:
| نوع پارامتر جاوا | مقدار آرگومان جاوا اسکریپت | رفتار اجباری |
|---|---|---|
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 | شماره | به مقدار ممیز شناور متناظر جاوا تبدیل میکند. |
float ، double | NaN / Infinity | تابع ()Coerces را به Float.NaN ، Double.NaN ، Float.POSITIVE_INFINITY یا Double.POSITIVE_INFINITY وادار میکند. |
char | عدد (عدد صحیح) | به کد پوینت یونیکد مربوطه تبدیل شد. |
char | غیر صحیح، NaN ، Infinity | وادار به \u0000 میکند. |
boolean | true / false | جاوا را مجبور به true یا false میکند. |
boolean | عدد، رشته، شیء | اجباراً مقدار را false برمیگرداند (شامل رشتههای غیر تهی و اعداد غیر صفر). |
String | رشته | مقدار رشته حفظ میشود. |
String | عدد، بولی | به صورت رشتهای قالببندی شده است (برای مثال، "42" ، "true" ، "false" ). |
String | null / undefined | null به جاوا null و undefined به رشتهی تحتاللفظی "undefined" اجبار میکند. |
String | شیء، ArrayBuffer، TypedArray | رشتهی تحتاللفظی "undefined" را اجبار میکند. |
آرایه اولیه (مانند int[] ، byte[] ، boolean[] ) یا String[] | آرایه ( [...] ) | به یک آرایه تک بعدی جاوا از نوع عنصر هدف تبدیل میشود. آرایههای پراکنده، اندیسهای اختصاص داده نشده را با مقادیر پیشفرض ( 0 ، false ، null ) پر میکنند. |
آرایه اولیه (مانند int[] ، byte[] ) | آرایه نوعبندیشده ( Int8Array ، Uint8Array ، Int32Array ، Float64Array ) | عناصر به اجبار در آرایه اولیه جاوا مربوطه قرار میگیرند. |
آرایه چند بعدی (مانند int[][] ) | آرایه تو در تو ( [[...]] ) | پشتیبانی نمیشود. پارامترهای آرایه چندبعدی مقدار null را ارزیابی میکنند. |
ArrayBuffer ، DataView | ArrayBuffer ، DataView | به عنوان آرایه پشتیبانی نمیشود. نمونههای ArrayBuffer و DataView مقدار null را ارزیابی میکنند. |
Object یا کلاس سفارشی | شیء جاوا اسکریپت ( {...} ) | پشتیبانی نمیشود. مقادیر دلخواه شیء جاوا اسکریپت در جاوا به null ارزیابی میشوند. |
Object یا کلاس سفارشی | پوشش دهنده شیء جاوا تزریق شده | پشتیبانی شده (رفت و برگشت). نمونه جاوای زیرین را به متد جاوا ارسال میکند. اگر نوع جاوا با امضای پارامتر مطابقت نداشته باشد، یک استثنای جاوا اسکریپت ایجاد میکند. |
انواع جعبهای (مانند Integer ، Double ، Boolean ) | عدد، بولی | پشتیبانی نمیشود. انواع اولیهی جعبهای به عنوان اشیاء مات در نظر گرفته میشوند و مقدار null را دریافت میکنند. |
| هر نوع اولیه | null / undefined | مقادیر پیشفرض ( 0 ، 0.0 ، \u0000 ، false ) را اجبار میکند. |
Object ، String ، آرایه | null | جاوا را به null وادار میکند. |
نگاشت نوع بازگشتی (جاوا به جاوا اسکریپت)
وقتی یک متد جاوا یا کاتلینِ حاشیهنویسیشده مقداری را برمیگرداند، پل آن را به یک نوع جاوااسکریپت تبدیل میکند:
| نوع بازگشتی جاوا | مقدار جاوا اسکریپت | typeof جاوا اسکریپت |
|---|---|---|
boolean | true / false | "boolean" |
byte ، short ، int ، long ، float ، double | شماره | "number" |
char | شماره (کد نقطه یونیکد) | "number" |
String (غیر تهی) | مقدار رشتهای | "string" |
String ( null ) | undefined | "undefined" |
void | undefined | "undefined" |
آرایه جاوا (مانند int[] ، String[] ) | undefined | "undefined" . مقادیر بازگشتی آرایه پشتیبانی نمیشوند. متد جاوا اجرا نمیشود و undefined بدون ایجاد استثنا بازگردانده میشود. |
| شیء جاوا / نوع سفارشی (غیر تهی) | پوشش دهنده شیء | "object" . یک پوشش جاوااسکریپت در اطراف نمونه جاوا ایجاد میکند. کد جاوااسکریپت میتواند هر متد عمومی را روی این شیء که با @JavascriptInterface حاشیهنویسی شده است، فراخوانی کند. |
شیء جاوا / نوع سفارشی ( null ) | null | "object" |
انواع اولیه جعبهای (مانند Integer ، Double ) | پوشش دهنده شیء | "object" . به صورت یک پوشش شیء جاوای غیرشفاف بدون هیچ متد قابل دسترسی @JavascriptInterface برگردانده میشود و مقدار را در جاوا اسکریپت غیرقابل استفاده میکند. |
دسترسیپذیری متد و عضو
پل جاوا اسکریپت، قوانین سختگیرانهای برای دسترسی اعضا و قابلیت مشاهده اعمال میکند تا از اجرای ناخواسته کد جلوگیری کند:
- فیلدها نمایش داده نمیشوند : فیلدهای جاوا (از جمله فیلدهای
publicوpublic final) از طریق جاوا اسکریپت قابل دسترسی نیستند و به صورتundefinedارزیابی میشوند. - الزام حاشیهنویسی : فقط متدهایی که صریحاً با
@JavascriptInterfaceحاشیهنویسی شدهاند، در معرض جاوا اسکریپت قرار میگیرند. - محدودیتهای دید : متدها باید
publicباشند. متدهایprivateوprotectedهرگز در معرض جاوااسکریپت قرار نمیگیرند، حتی اگر حاشیهنویسی@JavascriptInterfaceرا داشته باشند. - متدهای استاتیک : متدهای استاتیک که با
@JavascriptInterfaceحاشیهنویسی شدهاند، از جاوا اسکریپت قابل فراخوانی هستند. - وراثت و بازنویسی : حاشیهنویسیهای
@JavascriptInterfaceزمانی که یک زیرکلاس یک متد را بازنویسی میکند، به ارث برده نمیشوند. اگر یک زیرکلاس یک متد حاشیهنویسیشده از یک ابرکلاس را بازنویسی کند، زیرکلاس باید صریحاً حاشیهنویسی@JavascriptInterfaceرا در متد بازنویسیشده قرار دهد تا آن را در معرض جاوااسکریپت قرار دهد. متدهای عمومی بازنویسینشده که از یک ابرکلاس به ارث رسیدهاند، در صورت حاشیهنویسی در ابرکلاس، قابل دسترسی باقی میمانند. - محافظت در برابر بازتاب : متدهای استاندارد بازتاب جاوا (مانند
getClass()) مسدود شده و یک استثنای جاوا اسکریپت ایجاد میکنند تا از آسیبپذیریهای اجرای کد از راه دور جلوگیری شود. - سربارگذاری متد : متدهای جاوای سربارگذاریشده پشتیبانی میشوند. پل، فراخوانیهای متد را فقط بر اساس تعداد آرگومانهای ارسالی حل میکند و انواع آرگومانها را در نظر نمیگیرد. فراخوانی یک متد سربارگذاریشده با تعداد آرگومان نامعتبر، یک خطای جاوا اسکریپت ایجاد میکند. اگر دو سربارگذاری تعداد آرگومان یکسانی داشته باشند، یکی به طور دلخواه انتخاب میشود.
خلاصه مکانیسمها
جدول زیر مقایسهای سریع از سه مکانیسم اصلی پیادهسازی پل بومی ارائه میدهد:
| روش | addWebMessageListener | postWebMessage | addJavascriptInterface |
|---|---|---|---|
| پیادهسازی | ناهمزمان (شنونده روی نخ اصلی) | ناهمزمان | همزمان |
| امنیت | بالاترین (مبتنی بر لیست مجاز) | بالا (آگاه از مبدا) | کم (بدون بررسی مبدا) |
| پیچیدگی | متوسط | متوسط | ساده |
| جهت | دو جهته | دو جهته | وب به اپلیکیشن |
| حداقل نسخه وب ویو | نسخه ۸۲ (و Jetpack Webkit 1.3.0) | نسخه ۴۵ (و Jetpack Webkit 1.1.0) | همه نسخهها |
| توصیه شده | بله | خیر | خیر |
انتقال دادههای بزرگ را مدیریت کنید
هنگام انتقال بارهای داده بزرگ، مانند رشتههای چند مگابایتی یا فایلهای باینری، باید حافظه را با دقت مدیریت کنید تا از خطاهای عدم پاسخگویی برنامه (ANR) یا خرابی در دستگاههای 32 بیتی جلوگیری شود. این بخش به بررسی تکنیکها و محدودیتهای مختلف مرتبط با انتقال حجم قابل توجهی از دادهها بین برنامه میزبان و محتوای وب میپردازد.
انتقال دادههای دودویی با آرایههای بایتی
با کلاس WebMessageCompat ، میتوانید آرایههای byte[] را مستقیماً به جای سریالسازی دادههای دودویی به رشتههای Base64 ارسال کنید. از آنجایی که Base64 تقریباً 33٪ سربار به اندازه دادهها اضافه میکند، این روش به طور قابل توجهی از نظر حافظه کارآمدتر و سریعتر است.
- مزیت دودویی : انتقال دادههای دودویی مانند فایلهای تصویری یا صوتی بین برنامه بومی و محتوای وب شما.
- محدودیت : حتی با آرایههای بایتی، سیستم دادهها را در مرز ارتباط بین فرآیندی (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]);
}
};
}
بارگذاری کارآمد دادهها در مقیاس بزرگ
برای فایلهای بسیار بزرگ (بیش از 10 مگابایت)، از متد shouldInterceptRequest برای استریم دادهها استفاده کنید:
- صفحه وب، فراخوانی تابع
fetch()را به یک آدرس اینترنتی (URL) سفارشی و جاگذاریشده آغاز میکند. برای مثال،https://app.local/large-file. - برنامه اندروید این درخواست را در
WebViewClient.shouldInterceptRequest رهگیری میکند. - برنامه دادهها را به عنوان یک
InputStreamبرمیگرداند.
این امر امکان پخش دادهها را به صورت تکهای فراهم میکند، به جای اینکه کل بار داده به طور همزمان در حافظه بارگذاری شود.
تابع جاوا اسکریپت زیر، کد سمت کلاینت را برای بارگذاری کارآمد یک فایل باینری بزرگ از برنامه بومی با استفاده از فراخوانی استاندارد fetch() به یک URL سفارشی و placeholder نشان میدهد.
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 در کاتلین و جاوا، یک فایل باینری بزرگ را با رهگیری یک URL سفارشی درخواست شده توسط محتوای وب، استریم میکند.
کاتلین
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ارائه شده در callback پیام است. از استفاده از wildcard کامل (*) که با همه originها مطابقت دارد، به عنوان تنها قانون origin خود، مگر در موارد ضروری، خودداری کنید. استفاده از wildcardها برای زیر دامنهها (به عنوان مثال،*.example.com) برای تطبیق چندین زیر دامنه (به عنوان مثال،foo.example.com،bar.example.com) معتبر و ایمن باقی میماند.توجه : اگرچه قوانین مبدا در برابر وبسایتهای شخص ثالث مخرب و iframe های پنهان محافظت میکنند، اما نمیتوانند در برابر آسیبپذیریهای اسکریپتنویسی بین سایتی (XSS) در دامنه مورد اعتماد شما محافظت کنند. به عنوان مثال، اگر صفحه وب شما محتوای تولید شده توسط کاربر را نمایش میدهد و در برابر XSS ذخیره شده آسیبپذیر است، یک مهاجم میتواند اسکریپتی را اجرا کند که به عنوان مبدا مورد اعتماد شما عمل میکند. قبل از اجرای عملیات حساس پلتفرم بومی، اعتبارسنجی را برای بارهای داده پیام در نظر بگیرید.
به حداقل رساندن مساحت سطح : فقط روشها یا دادههای خاصی را که صفحه وب نیاز دارد، نمایش دهید.
بررسی ویژگیها در زمان اجرا : APIهای پل اخیر، از جمله
addWebMessageListener، بخشی از کتابخانه Jetpack Webkit هستند. بنابراین، همیشه قبل از فراخوانی آنها، با استفاده ازWebViewFeature.isFeatureSupported()پشتیبانی آنها را بررسی کنید.