این صفحه به بررسی روشها و بهترین شیوههای مختلف برای ایجاد یک پل بومی، که با نام پل جاوا اسکریپت نیز شناخته میشود، میپردازد تا ارتباط بین محتوای وب در یک 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()پشتیبانی آنها را بررسی کنید.
این صفحه به بررسی روشها و بهترین شیوههای مختلف برای ایجاد یک پل بومی، که با نام پل جاوا اسکریپت نیز شناخته میشود، میپردازد تا ارتباط بین محتوای وب در یک 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حاشیهنویسی شدهاند، در معرض جاوا اسکریپت قرار میگیرند. - Visibility restrictions : Methods must be
public.privateandprotectedmethods are never exposed to JavaScript, even if they carry the@JavascriptInterfaceannotation. - Static methods : Static methods annotated with
@JavascriptInterfaceare callable from JavaScript. - Inheritance and overriding :
@JavascriptInterfaceannotations are not inherited when a subclass overrides a method. If a subclass overrides an annotated method from a superclass, the subclass must explicitly include the@JavascriptInterfaceannotation on the overridden method to expose it to JavaScript. Non-overridden public methods inherited from a superclass remain accessible if annotated in the superclass. - Reflection protection : Standard Java reflection methods (such as
getClass()) are blocked and throw a JavaScript exception to prevent remote code execution vulnerabilities. - Method overloading : Overloaded Java methods are supported. The bridge resolves method calls based on the number of passed arguments only, and does not take argument types into account. Calling an overloaded method with an invalid argument count raises a JavaScript exception. If two overloads have the same argument count, one will be chosen arbitrarily.
Summary of mechanisms
The following table provides a quick comparison of the three primary native bridge implementation mechanisms:
| روش | addWebMessageListener | postWebMessage | addJavascriptInterface |
|---|---|---|---|
| پیادهسازی | Asynchronous (Listener on main thread) | ناهمزمان | همزمان |
| امنیت | Highest (Allowlist-based) | High (Origin aware) | Low (No origin checks) |
| Complexity | متوسط | متوسط | ساده |
| جهت | دو جهته | دو جهته | Web to app |
| Minimum WebView version | Version 82 (and Jetpack Webkit 1.3.0) | Version 45 (and Jetpack Webkit 1.1.0) | All versions |
| توصیه شده | بله | خیر | خیر |
Handle large data transfers
You must manage memory carefully when transferring large payloads, such as multi-megabyte strings or binary files, to avoid Application Not Responding (ANR) errors or crashes on 32-bit devices. This section discusses the various techniques and limitations associated with transferring significant amounts of data between the host application and web content.
Transfer binary data with byte arrays
With the WebMessageCompat class, you can send byte[] arrays directly instead of serializing binary data into Base64 strings. Since Base64 adds roughly 33% overhead to the data size, this is significantly more memory-efficient and faster.
- Binary advantage : Transfer binary data like image files or audio between your native app and web content.
- Limitation : Even with byte arrays, the system copies data across the inter-process communication (IPC) boundary between the app and the isolated process that WebView uses to render the web content. This still consumes significant memory for very large files.
The following code examples demonstrate how to set up addWebMessageListener on the native app side to receive messages marked with WebMessageCompat.TYPE_ARRAY_BUFFER and optionally reply with binary data by checking for 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
);
}
}
The following JavaScript code demonstrates the client-side implementation of addWebMessageListener , enabling the web content to send and receive binary data ( ArrayBuffer ) to and from the native app using the window.myBridge proxy injected in the previous example.
// 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]);
}
};
}
Efficient large-scale data loading
For very large files (>10 MB), use the shouldInterceptRequest method to stream data:
- The web page initiates a
fetch()call to a custom, placeholder URL. For example,https://app.local/large-file. - The Android app intercepts this request in
WebViewClient.shouldInterceptRequest. - The app returns the data as an
InputStream.
This enables streaming data in chunks rather than loading the entire payload into memory at once.
The following JavaScript function demonstrates the client-side code for efficiently loading a large binary file from the native application using a standard fetch() call to a custom, placeholder 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);
}
}
The following code examples demonstrate the native app side, using the WebViewClient.shouldInterceptRequest method in both Kotlin and Java, to stream a large binary file by intercepting a custom placeholder URL requested by the web content.
کاتلین
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);
}
});
Follow security recommendations
To protect your application and user data, follow these guidelines when implementing a bridge:
Enforce HTTPS : To ensure that malicious third-party content can't invoke your application's native logic, only allow communication with secure origins.
Rely on origin rules : The best way to deal with trust is to strictly define your
allowedOriginRulesand check thesourceOriginprovided in the message callback. Avoid using the full wildcard (*), which matches all origins, as your only origin rule unless absolutely necessary. Using wildcards for subdomains (for example,*.example.com) remains valid and secure for matching multiple subdomains (for example,foo.example.com,bar.example.com).Note : While origin rules protect against malicious third-party websites and hidden iframes, they can't protect against cross-site scripting (XSS) vulnerabilities within your own trusted domain. For example, if your web page displays user-generated content and is vulnerable to stored XSS, an attacker could execute a script acting as your trusted origin. Consider applying validation to the message payloads before executing sensitive native platform operations.
Minimize surface area : Only expose the specific methods or data that the web page requires.
Check features at runtime : Recent bridge APIs, including
addWebMessageListener, are part of the Jetpack Webkit library. So, always check for support usingWebViewFeature.isFeatureSupported()before calling them.
This page discusses the various methods and best practices for establishing a native bridge, also known as JavaScript bridge, to facilitate communication between web content in a WebView and a host Android application.
This enables web developers to use JavaScript to access native platform features—such as the camera, file system, or advanced hardware sensors—that standard web APIs don't normally provide.
موارد استفاده
A JavaScript bridge implementation enables various integration scenarios where web content requires deeper access to the Android operating system. The following are some examples:
- Platform integration : Triggering native Android UI components (for example, Biometric prompts,
BottomSheetDialog) from a web page. - Performance : Offloading heavy computational tasks to native Java or Kotlin code.
- Data persistence : Accessing local encrypted databases or shared preferences.
- Large data transfers : Passing media files or complex data structures between the app and the web renderer.
Communication mechanisms
Android offers three primary generations of APIs to establish a native bridge. While they are all still available, they differ significantly in security, usability, and performance.
Use addWebMessageListener (Recommended)
addWebMessageListener is the most modern and recommended approach for communication between the web content and native app code. It combines the ease of use of the JavaScript interface with the security of the messaging system.
How it works : The app adds a listener with a specific name and a set of allowed origin rules. The WebView then ensures the JavaScript object is present in the global scope ( window.objectName ) from the moment the page begins to load.
Initialization : To ensure the WebView injects the JavaScript object before any script runs, you must call addWebMessageListener before navigating to the page (such as calling WebViewCompat.navigate or loadUrl ).
Key features :
Security and trust : Unlike legacy APIs, this method requires a
Set<String>ofallowedOriginRulesduring initialization. This is the primary mechanism for establishing trust.When you specify a trusted origin, such as
https://example.com, the WebView guarantees that it only exposes the injected JavaScript objects to web pages loaded from that exact origin.The native listener callback receives a
sourceOriginparameter with every message. You can use this to verify the exact origin of the sender if your bridge supports multiple allowed origins.Because the WebView strictly enforces these origin checks at the platform level, your app can generally rely upon messages received from a trusted
sourceOriginas truthful, eliminating the need for rigorous payload validation in most standard implementations.- WebView matches rules against the scheme (HTTP/HTTPS), host, and port.
- WebView ignores paths. For example,
https://example.comallowshttps://example.com/loginandhttps://example.com/home. - WebView strictly limits wildcards to the start of the host for subdomains. For example,
https://*.example.commatcheshttps://foo.example.combut nothttps://example.com. If you need to match bothhttps://example.comand its subdomains, you must add each origin rule separately to the allowlist (for example,"https://example.com", "https://*.example.com"). You can't use wildcards for the scheme or in the middle of a domain.
This restricts the bridge to verified domains, preventing unauthorized third-party content or injected iframes from executing native code.
Multi-frame support : Works across all frames that match the origin rules.
Threading : The listener callback runs on the application's main (UI) thread. If your bridge needs to handle complex data processing, JSON parsing, or database lookups, you must offload that work to a background thread to prevent freezing the application UI with an "app not responding" (ANR) error.
Bidirectional : When the web page sends a message, the app receives a
JavaScriptReplyProxythat it can use to send messages back to that specific frame. You can retain thisreplyProxyobject and use it at any time to send any number of messages to the page, not just to reply to each individual message the page sends. If the originating frame navigates away or is destroyed, messages sent usingpostMessage()on the proxy are silently ignored.App-side initiation : Although the web page must always initiate the communication channel with the app, the native app can unilaterally prompt the web page to begin this process. The native app can communicate to the web page with
addDocumentStartJavaScript()(to evaluate JavaScript before the page loads) orevaluateJavaScript()(to evaluate JavaScript after the page has loaded).
Limitation : This API sends data as either strings or byte[] arrays. For more complicated data structures, such as, JSON objects, you must serialize this to one of those formats and then deserialize on the other side to reconstruct the data structure.
Usage example :
To understand the full sequence of a bidirectional message exchange, the events proceed in this order:
- Initiation (app) : The native app registers the listener with
addWebMessageListenerand initiates page navigation (such as withWebViewCompat.navigateorloadUrl). - Message send (web) : The web page's JavaScript calls
myObject.postMessage(message)to initiate the communication. - Message receive and reply (app) : The app receives the message in the listener callback and replies using the provided
replyProxy.postMessage(). - Reply receive (web) : The web page receives the asynchronous reply in the
myObject.onmessage()callback function.
کاتلین
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);
}
The following JavaScript demonstrates the client-side implementation of addWebMessageListener , allowing the web content to receive messages from the native app and send its own messages through the myObject proxy.
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
Use postWebMessage (Alternative)
Android introduced this to provide an asynchronous, messaging-based alternative similar to the web's window.postMessage .
How it works : The app uses WebViewCompat.postWebMessage to send a payload to the web page's main frame. To establish a bidirectional communication channel, you can create a WebMessageChannel and pass one of its ports with the message to the web content.
Characteristics :
- Asynchronous : Like
addWebMessageListener, this method uses asynchronous messaging, which ensures the web page remains responsive to user interactions while the app processes data in the background. - Origin aware : You can specify a
targetOriginto ensure the WebView delivers data only to a trusted website.
Limitations :
- Scope : This API limits communication to the main frame. It doesn't support directly addressing or sending messages to iframes.
- URI restrictions : You cannot use this method for content loaded using
data:URIs,file:URIs, orloadData(), unless you specify "*" as the target origin. Doing this lets any page receive the message. - Identity risk : There is no clear way for the web content to verify the sender's identity. A message that the web page receives could have originated from your native app or another iframe.
Use this method when you need a simple, async channel for string-based data in earlier Android versions that don't support addWebMessageListener .
Use addJavascriptInterface (Legacy)
The oldest method involves injecting a native object instance directly into the WebView.
How it works : You define a Kotlin or Java class, annotate the allowed methods with @JavascriptInterface , and add an instance of the class to the WebView using addJavascriptInterface(Object, String) .
Characteristics :
- Synchronous : The JavaScript execution environment blocks until the method in your Android code returns.
- Thread safety : The system calls methods on a background thread, requiring careful synchronization on the Kotlin or Java side.
- Security risk : By default,
addJavascriptInterfaceis available to every frame within the WebView, including iframes. It lacks origin-based access control. Due to the asynchronous behavior of WebView, it isn't possible to safely determine the URL of the frame that is calling your interface. You must not rely on methods likeWebView.getUrl()for security verification, as they aren't guaranteed to be accurate and don't indicate which specific frame made the request.
Data type conversions and coercion
When using addJavascriptInterface , the Chromium-based Java Bridge converts data types between the JavaScript runtime and your Android app code.
The following coercion rules apply to method parameters and return values.
Parameter type mapping (JavaScript to Java)
When JavaScript passes arguments to an annotated Java or Kotlin method, the bridge coerces JavaScript values into the corresponding Java parameter types:
| Java parameter type | JavaScript argument value | Coercion behavior |
|---|---|---|
byte , short , int , long | Number (integer) | Values are cast to the target integer type. Out-of-bounds values wrap around according to standard numeric casting rules. |
byte , short , int , long | NaN | Coerces to 0 . |
byte , short , int , long | Infinity | Coerces to -1 for byte and short , or Integer.MAX_VALUE and Long.MAX_VALUE for int and long . |
float , double | شماره | Coerces to the corresponding Java floating-point value. |
float , double | NaN / Infinity | Coerces to Float.NaN , Double.NaN , Float.POSITIVE_INFINITY , or Double.POSITIVE_INFINITY . |
char | Number (integer) | Converted to the corresponding Unicode code point. |
char | Non-integer, NaN , Infinity | Coerces to \u0000 . |
boolean | true / false | Coerces to Java true or false . |
boolean | Number, String, Object | Coerces to false (including non-empty strings and non-zero numbers). |
String | رشته | String value is preserved. |
String | Number, Boolean | Formatted as a string representation (for example, "42" , "true" , "false" ). |
String | null / undefined | null coerces to Java null ; undefined coerces to the literal string "undefined" . |
String | Object, ArrayBuffer, TypedArray | Coerces to the literal string "undefined" . |
Primitive array (such as int[] , byte[] , boolean[] ) or String[] | Array ( [...] ) | Converts to a 1D Java array of the target element type. Sparse arrays fill unassigned indexes with default values ( 0 , false , null ). |
Primitive array (such as int[] , byte[] ) | TypedArray ( Int8Array , Uint8Array , Int32Array , Float64Array ) | Elements are coerced into the corresponding Java primitive array. |
Multi-dimensional array (such as int[][] ) | Nested array ( [[...]] ) | Not supported. Multi-dimensional array parameters evaluate to null . |
ArrayBuffer , DataView | ArrayBuffer , DataView | Not supported as arrays. ArrayBuffer and DataView instances evaluate to null . |
Object or custom class | JavaScript object ( {...} ) | Not supported. Arbitrary JavaScript object literals evaluate to null in Java. |
Object or custom class | Injected Java object wrapper | Supported (Round-tripping). Passes the underlying Java instance to the Java method. Throws a JavaScript exception if the Java type does not match the parameter signature. |
Boxed types (such as Integer , Double , Boolean ) | Number, Boolean | Not supported. Boxed primitive types are treated as opaque objects and evaluate to null . |
| Any primitive type | null / undefined | Coerces to default values ( 0 , 0.0 , \u0000 , false ). |
Object , String , array | null | Coerces to Java null . |
Return type mapping (Java to JavaScript)
When an annotated Java or Kotlin method returns a value, the bridge converts it to a JavaScript type:
| Java return type | JavaScript value | JavaScript typeof |
|---|---|---|
boolean | true / false | "boolean" |
byte , short , int , long , float , double | شماره | "number" |
char | Number (Unicode code point) | "number" |
String (non-null) | String value | "string" |
String ( null ) | undefined | "undefined" |
void | undefined | "undefined" |
Java array (such as int[] , String[] ) | undefined | "undefined" . Array return values are not supported. The Java method is not executed, and undefined is returned without raising an exception. |
| Java Object / custom type (non-null) | Object wrapper | "object" . Creates a JavaScript wrapper around the Java instance. JavaScript code can call any public method on this object that is annotated with @JavascriptInterface . |
Java Object / custom type ( null ) | null | "object" |
Boxed primitive (such as Integer , Double ) | Object wrapper | "object" . Returned as an opaque Java object wrapper with no accessible @JavascriptInterface methods, making the value unusable in JavaScript. |
Method and member accessibility
The JavaScript bridge enforces strict member access and visibility rules to protect against unintended code execution:
- Fields are not exposed : Java fields (including
publicandpublic finalfields) are not accessible from JavaScript and evaluate toundefined. - Annotation requirement : Only methods explicitly annotated with
@JavascriptInterfaceare exposed to JavaScript. - Visibility restrictions : Methods must be
public.privateandprotectedmethods are never exposed to JavaScript, even if they carry the@JavascriptInterfaceannotation. - Static methods : Static methods annotated with
@JavascriptInterfaceare callable from JavaScript. - Inheritance and overriding :
@JavascriptInterfaceannotations are not inherited when a subclass overrides a method. If a subclass overrides an annotated method from a superclass, the subclass must explicitly include the@JavascriptInterfaceannotation on the overridden method to expose it to JavaScript. Non-overridden public methods inherited from a superclass remain accessible if annotated in the superclass. - Reflection protection : Standard Java reflection methods (such as
getClass()) are blocked and throw a JavaScript exception to prevent remote code execution vulnerabilities. - Method overloading : Overloaded Java methods are supported. The bridge resolves method calls based on the number of passed arguments only, and does not take argument types into account. Calling an overloaded method with an invalid argument count raises a JavaScript exception. If two overloads have the same argument count, one will be chosen arbitrarily.
Summary of mechanisms
The following table provides a quick comparison of the three primary native bridge implementation mechanisms:
| روش | addWebMessageListener | postWebMessage | addJavascriptInterface |
|---|---|---|---|
| پیادهسازی | Asynchronous (Listener on main thread) | ناهمزمان | همزمان |
| امنیت | Highest (Allowlist-based) | High (Origin aware) | Low (No origin checks) |
| Complexity | متوسط | متوسط | ساده |
| جهت | دو جهته | دو جهته | Web to app |
| Minimum WebView version | Version 82 (and Jetpack Webkit 1.3.0) | Version 45 (and Jetpack Webkit 1.1.0) | All versions |
| توصیه شده | بله | خیر | خیر |
Handle large data transfers
You must manage memory carefully when transferring large payloads, such as multi-megabyte strings or binary files, to avoid Application Not Responding (ANR) errors or crashes on 32-bit devices. This section discusses the various techniques and limitations associated with transferring significant amounts of data between the host application and web content.
Transfer binary data with byte arrays
With the WebMessageCompat class, you can send byte[] arrays directly instead of serializing binary data into Base64 strings. Since Base64 adds roughly 33% overhead to the data size, this is significantly more memory-efficient and faster.
- Binary advantage : Transfer binary data like image files or audio between your native app and web content.
- Limitation : Even with byte arrays, the system copies data across the inter-process communication (IPC) boundary between the app and the isolated process that WebView uses to render the web content. This still consumes significant memory for very large files.
The following code examples demonstrate how to set up addWebMessageListener on the native app side to receive messages marked with WebMessageCompat.TYPE_ARRAY_BUFFER and optionally reply with binary data by checking for 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
);
}
}
The following JavaScript code demonstrates the client-side implementation of addWebMessageListener , enabling the web content to send and receive binary data ( ArrayBuffer ) to and from the native app using the window.myBridge proxy injected in the previous example.
// 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]);
}
};
}
Efficient large-scale data loading
For very large files (>10 MB), use the shouldInterceptRequest method to stream data:
- The web page initiates a
fetch()call to a custom, placeholder URL. For example,https://app.local/large-file. - The Android app intercepts this request in
WebViewClient.shouldInterceptRequest. - The app returns the data as an
InputStream.
This enables streaming data in chunks rather than loading the entire payload into memory at once.
The following JavaScript function demonstrates the client-side code for efficiently loading a large binary file from the native application using a standard fetch() call to a custom, placeholder 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);
}
}
The following code examples demonstrate the native app side, using the WebViewClient.shouldInterceptRequest method in both Kotlin and Java, to stream a large binary file by intercepting a custom placeholder URL requested by the web content.
کاتلین
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);
}
});
Follow security recommendations
To protect your application and user data, follow these guidelines when implementing a bridge:
Enforce HTTPS : To ensure that malicious third-party content can't invoke your application's native logic, only allow communication with secure origins.
Rely on origin rules : The best way to deal with trust is to strictly define your
allowedOriginRulesand check thesourceOriginprovided in the message callback. Avoid using the full wildcard (*), which matches all origins, as your only origin rule unless absolutely necessary. Using wildcards for subdomains (for example,*.example.com) remains valid and secure for matching multiple subdomains (for example,foo.example.com,bar.example.com).Note : While origin rules protect against malicious third-party websites and hidden iframes, they can't protect against cross-site scripting (XSS) vulnerabilities within your own trusted domain. For example, if your web page displays user-generated content and is vulnerable to stored XSS, an attacker could execute a script acting as your trusted origin. Consider applying validation to the message payloads before executing sensitive native platform operations.
Minimize surface area : Only expose the specific methods or data that the web page requires.
Check features at runtime : Recent bridge APIs, including
addWebMessageListener, are part of the Jetpack Webkit library. So, always check for support usingWebViewFeature.isFeatureSupported()before calling them.
This page discusses the various methods and best practices for establishing a native bridge, also known as JavaScript bridge, to facilitate communication between web content in a WebView and a host Android application.
This enables web developers to use JavaScript to access native platform features—such as the camera, file system, or advanced hardware sensors—that standard web APIs don't normally provide.
موارد استفاده
A JavaScript bridge implementation enables various integration scenarios where web content requires deeper access to the Android operating system. The following are some examples:
- Platform integration : Triggering native Android UI components (for example, Biometric prompts,
BottomSheetDialog) from a web page. - Performance : Offloading heavy computational tasks to native Java or Kotlin code.
- Data persistence : Accessing local encrypted databases or shared preferences.
- Large data transfers : Passing media files or complex data structures between the app and the web renderer.
Communication mechanisms
Android offers three primary generations of APIs to establish a native bridge. While they are all still available, they differ significantly in security, usability, and performance.
Use addWebMessageListener (Recommended)
addWebMessageListener is the most modern and recommended approach for communication between the web content and native app code. It combines the ease of use of the JavaScript interface with the security of the messaging system.
How it works : The app adds a listener with a specific name and a set of allowed origin rules. The WebView then ensures the JavaScript object is present in the global scope ( window.objectName ) from the moment the page begins to load.
Initialization : To ensure the WebView injects the JavaScript object before any script runs, you must call addWebMessageListener before navigating to the page (such as calling WebViewCompat.navigate or loadUrl ).
Key features :
Security and trust : Unlike legacy APIs, this method requires a
Set<String>ofallowedOriginRulesduring initialization. This is the primary mechanism for establishing trust.When you specify a trusted origin, such as
https://example.com, the WebView guarantees that it only exposes the injected JavaScript objects to web pages loaded from that exact origin.The native listener callback receives a
sourceOriginparameter with every message. You can use this to verify the exact origin of the sender if your bridge supports multiple allowed origins.Because the WebView strictly enforces these origin checks at the platform level, your app can generally rely upon messages received from a trusted
sourceOriginas truthful, eliminating the need for rigorous payload validation in most standard implementations.- WebView matches rules against the scheme (HTTP/HTTPS), host, and port.
- WebView ignores paths. For example,
https://example.comallowshttps://example.com/loginandhttps://example.com/home. - WebView strictly limits wildcards to the start of the host for subdomains. For example,
https://*.example.commatcheshttps://foo.example.combut nothttps://example.com. If you need to match bothhttps://example.comand its subdomains, you must add each origin rule separately to the allowlist (for example,"https://example.com", "https://*.example.com"). You can't use wildcards for the scheme or in the middle of a domain.
This restricts the bridge to verified domains, preventing unauthorized third-party content or injected iframes from executing native code.
Multi-frame support : Works across all frames that match the origin rules.
Threading : The listener callback runs on the application's main (UI) thread. If your bridge needs to handle complex data processing, JSON parsing, or database lookups, you must offload that work to a background thread to prevent freezing the application UI with an "app not responding" (ANR) error.
Bidirectional : When the web page sends a message, the app receives a
JavaScriptReplyProxythat it can use to send messages back to that specific frame. You can retain thisreplyProxyobject and use it at any time to send any number of messages to the page, not just to reply to each individual message the page sends. If the originating frame navigates away or is destroyed, messages sent usingpostMessage()on the proxy are silently ignored.App-side initiation : Although the web page must always initiate the communication channel with the app, the native app can unilaterally prompt the web page to begin this process. The native app can communicate to the web page with
addDocumentStartJavaScript()(to evaluate JavaScript before the page loads) orevaluateJavaScript()(to evaluate JavaScript after the page has loaded).
Limitation : This API sends data as either strings or byte[] arrays. For more complicated data structures, such as, JSON objects, you must serialize this to one of those formats and then deserialize on the other side to reconstruct the data structure.
Usage example :
To understand the full sequence of a bidirectional message exchange, the events proceed in this order:
- Initiation (app) : The native app registers the listener with
addWebMessageListenerand initiates page navigation (such as withWebViewCompat.navigateorloadUrl). - Message send (web) : The web page's JavaScript calls
myObject.postMessage(message)to initiate the communication. - Message receive and reply (app) : The app receives the message in the listener callback and replies using the provided
replyProxy.postMessage(). - Reply receive (web) : The web page receives the asynchronous reply in the
myObject.onmessage()callback function.
کاتلین
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);
}
The following JavaScript demonstrates the client-side implementation of addWebMessageListener , allowing the web content to receive messages from the native app and send its own messages through the myObject proxy.
myObject.onmessage = function(event) {
console.log("App says: " + event.data);
};
myObject.postMessage("Hello world!");
Use postWebMessage (Alternative)
Android introduced this to provide an asynchronous, messaging-based alternative similar to the web's window.postMessage .
How it works : The app uses WebViewCompat.postWebMessage to send a payload to the web page's main frame. To establish a bidirectional communication channel, you can create a WebMessageChannel and pass one of its ports with the message to the web content.
Characteristics :
- Asynchronous : Like
addWebMessageListener, this method uses asynchronous messaging, which ensures the web page remains responsive to user interactions while the app processes data in the background. - Origin aware : You can specify a
targetOriginto ensure the WebView delivers data only to a trusted website.
Limitations :
- Scope : This API limits communication to the main frame. It doesn't support directly addressing or sending messages to iframes.
- URI restrictions : You cannot use this method for content loaded using
data:URIs,file:URIs, orloadData(), unless you specify "*" as the target origin. Doing this lets any page receive the message. - Identity risk : There is no clear way for the web content to verify the sender's identity. A message that the web page receives could have originated from your native app or another iframe.
Use this method when you need a simple, async channel for string-based data in earlier Android versions that don't support addWebMessageListener .
Use addJavascriptInterface (Legacy)
The oldest method involves injecting a native object instance directly into the WebView.
How it works : You define a Kotlin or Java class, annotate the allowed methods with @JavascriptInterface , and add an instance of the class to the WebView using addJavascriptInterface(Object, String) .
Characteristics :
- Synchronous : The JavaScript execution environment blocks until the method in your Android code returns.
- Thread safety : The system calls methods on a background thread, requiring careful synchronization on the Kotlin or Java side.
- Security risk : By default,
addJavascriptInterfaceis available to every frame within the WebView, including iframes. It lacks origin-based access control. Due to the asynchronous behavior of WebView, it isn't possible to safely determine the URL of the frame that is calling your interface. You must not rely on methods likeWebView.getUrl()for security verification, as they aren't guaranteed to be accurate and don't indicate which specific frame made the request.
Data type conversions and coercion
When using addJavascriptInterface , the Chromium-based Java Bridge converts data types between the JavaScript runtime and your Android app code.
The following coercion rules apply to method parameters and return values.
Parameter type mapping (JavaScript to Java)
When JavaScript passes arguments to an annotated Java or Kotlin method, the bridge coerces JavaScript values into the corresponding Java parameter types:
| Java parameter type | JavaScript argument value | Coercion behavior |
|---|---|---|
byte , short , int , long | Number (integer) | Values are cast to the target integer type. Out-of-bounds values wrap around according to standard numeric casting rules. |
byte , short , int , long | NaN | Coerces to 0 . |
byte , short , int , long | Infinity | Coerces to -1 for byte and short , or Integer.MAX_VALUE and Long.MAX_VALUE for int and long . |
float , double | شماره | Coerces to the corresponding Java floating-point value. |
float , double | NaN / Infinity | Coerces to Float.NaN , Double.NaN , Float.POSITIVE_INFINITY , or Double.POSITIVE_INFINITY . |
char | Number (integer) | Converted to the corresponding Unicode code point. |
char | Non-integer, NaN , Infinity | Coerces to \u0000 . |
boolean | true / false | Coerces to Java true or false . |
boolean | Number, String, Object | Coerces to false (including non-empty strings and non-zero numbers). |
String | رشته | String value is preserved. |
String | Number, Boolean | Formatted as a string representation (for example, "42" , "true" , "false" ). |
String | null / undefined | null coerces to Java null ; undefined coerces to the literal string "undefined" . |
String | Object, ArrayBuffer, TypedArray | Coerces to the literal string "undefined" . |
Primitive array (such as int[] , byte[] , boolean[] ) or String[] | Array ( [...] ) | Converts to a 1D Java array of the target element type. Sparse arrays fill unassigned indexes with default values ( 0 , false , null ). |
Primitive array (such as int[] , byte[] ) | TypedArray ( Int8Array , Uint8Array , Int32Array , Float64Array ) | Elements are coerced into the corresponding Java primitive array. |
Multi-dimensional array (such as int[][] ) | Nested array ( [[...]] ) | Not supported. Multi-dimensional array parameters evaluate to null . |
ArrayBuffer , DataView | ArrayBuffer , DataView | Not supported as arrays. ArrayBuffer and DataView instances evaluate to null . |
Object or custom class | JavaScript object ( {...} ) | Not supported. Arbitrary JavaScript object literals evaluate to null in Java. |
Object or custom class | Injected Java object wrapper | Supported (Round-tripping). Passes the underlying Java instance to the Java method. Throws a JavaScript exception if the Java type does not match the parameter signature. |
Boxed types (such as Integer , Double , Boolean ) | Number, Boolean | Not supported. Boxed primitive types are treated as opaque objects and evaluate to null . |
| Any primitive type | null / undefined | Coerces to default values ( 0 , 0.0 , \u0000 , false ). |
Object , String , array | null | Coerces to Java null . |
Return type mapping (Java to JavaScript)
When an annotated Java or Kotlin method returns a value, the bridge converts it to a JavaScript type:
| Java return type | JavaScript value | JavaScript typeof |
|---|---|---|
boolean | true / false | "boolean" |
byte , short , int , long , float , double | شماره | "number" |
char | Number (Unicode code point) | "number" |
String (non-null) | String value | "string" |
String ( null ) | undefined | "undefined" |
void | undefined | "undefined" |
Java array (such as int[] , String[] ) | undefined | "undefined" . Array return values are not supported. The Java method is not executed, and undefined is returned without raising an exception. |
| Java Object / custom type (non-null) | Object wrapper | "object" . Creates a JavaScript wrapper around the Java instance. JavaScript code can call any public method on this object that is annotated with @JavascriptInterface . |
Java Object / custom type ( null ) | null | "object" |
Boxed primitive (such as Integer , Double ) | Object wrapper | "object" . Returned as an opaque Java object wrapper with no accessible @JavascriptInterface methods, making the value unusable in JavaScript. |
Method and member accessibility
The JavaScript bridge enforces strict member access and visibility rules to protect against unintended code execution:
- Fields are not exposed : Java fields (including
publicandpublic finalfields) are not accessible from JavaScript and evaluate toundefined. - Annotation requirement : Only methods explicitly annotated with
@JavascriptInterfaceare exposed to JavaScript. - Visibility restrictions : Methods must be
public.privateandprotectedmethods are never exposed to JavaScript, even if they carry the@JavascriptInterfaceannotation. - Static methods : Static methods annotated with
@JavascriptInterfaceare callable from JavaScript. - Inheritance and overriding :
@JavascriptInterfaceannotations are not inherited when a subclass overrides a method. If a subclass overrides an annotated method from a superclass, the subclass must explicitly include the@JavascriptInterfaceannotation on the overridden method to expose it to JavaScript. Non-overridden public methods inherited from a superclass remain accessible if annotated in the superclass. - Reflection protection : Standard Java reflection methods (such as
getClass()) are blocked and throw a JavaScript exception to prevent remote code execution vulnerabilities. - Method overloading : Overloaded Java methods are supported. The bridge resolves method calls based on the number of passed arguments only, and does not take argument types into account. Calling an overloaded method with an invalid argument count raises a JavaScript exception. If two overloads have the same argument count, one will be chosen arbitrarily.
Summary of mechanisms
The following table provides a quick comparison of the three primary native bridge implementation mechanisms:
| روش | addWebMessageListener | postWebMessage | addJavascriptInterface |
|---|---|---|---|
| پیادهسازی | Asynchronous (Listener on main thread) | ناهمزمان | همزمان |
| امنیت | Highest (Allowlist-based) | High (Origin aware) | Low (No origin checks) |
| Complexity | متوسط | متوسط | ساده |
| جهت | دو جهته | دو جهته | Web to app |
| Minimum WebView version | Version 82 (and Jetpack Webkit 1.3.0) | Version 45 (and Jetpack Webkit 1.1.0) | All versions |
| توصیه شده | بله | خیر | خیر |
Handle large data transfers
You must manage memory carefully when transferring large payloads, such as multi-megabyte strings or binary files, to avoid Application Not Responding (ANR) errors or crashes on 32-bit devices. This section discusses the various techniques and limitations associated with transferring significant amounts of data between the host application and web content.
Transfer binary data with byte arrays
With the WebMessageCompat class, you can send byte[] arrays directly instead of serializing binary data into Base64 strings. Since Base64 adds roughly 33% overhead to the data size, this is significantly more memory-efficient and faster.
- Binary advantage : Transfer binary data like image files or audio between your native app and web content.
- Limitation : Even with byte arrays, the system copies data across the inter-process communication (IPC) boundary between the app and the isolated process that WebView uses to render the web content. This still consumes significant memory for very large files.
The following code examples demonstrate how to set up addWebMessageListener on the native app side to receive messages marked with WebMessageCompat.TYPE_ARRAY_BUFFER and optionally reply with binary data by checking for 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
);
}
}
The following JavaScript code demonstrates the client-side implementation of addWebMessageListener , enabling the web content to send and receive binary data ( ArrayBuffer ) to and from the native app using the window.myBridge proxy injected in the previous example.
// 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]);
}
};
}
Efficient large-scale data loading
For very large files (>10 MB), use the shouldInterceptRequest method to stream data:
- The web page initiates a
fetch()call to a custom, placeholder URL. For example,https://app.local/large-file. - The Android app intercepts this request in
WebViewClient.shouldInterceptRequest. - The app returns the data as an
InputStream.
This enables streaming data in chunks rather than loading the entire payload into memory at once.
The following JavaScript function demonstrates the client-side code for efficiently loading a large binary file from the native application using a standard fetch() call to a custom, placeholder 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);
}
}
The following code examples demonstrate the native app side, using the WebViewClient.shouldInterceptRequest method in both Kotlin and Java, to stream a large binary file by intercepting a custom placeholder URL requested by the web content.
کاتلین
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);
}
});
Follow security recommendations
To protect your application and user data, follow these guidelines when implementing a bridge:
Enforce HTTPS : To ensure that malicious third-party content can't invoke your application's native logic, only allow communication with secure origins.
Rely on origin rules : The best way to deal with trust is to strictly define your
allowedOriginRulesand check thesourceOriginprovided in the message callback. Avoid using the full wildcard (*), which matches all origins, as your only origin rule unless absolutely necessary. Using wildcards for subdomains (for example,*.example.com) remains valid and secure for matching multiple subdomains (for example,foo.example.com,bar.example.com).Note : While origin rules protect against malicious third-party websites and hidden iframes, they can't protect against cross-site scripting (XSS) vulnerabilities within your own trusted domain. For example, if your web page displays user-generated content and is vulnerable to stored XSS, an attacker could execute a script acting as your trusted origin. Consider applying validation to the message payloads before executing sensitive native platform operations.
Minimize surface area : Only expose the specific methods or data that the web page requires.
Check features at runtime : Recent bridge APIs, including
addWebMessageListener, are part of the Jetpack Webkit library. So, always check for support usingWebViewFeature.isFeatureSupported()before calling them.