JavaScript ব্রিজের মাধ্যমে নেটিভ API অ্যাক্সেস করা

এই পৃষ্ঠায়, WebView-এর মধ্যে থাকা ওয়েব কন্টেন্ট এবং হোস্ট Android অ্যাপ্লিকেশনের মধ্যে যোগাযোগ সুবিধাজনক করে তুলতে, নেটিভ ব্রিজ, যা জাভাস্ক্রিপ্ট ব্রিজ নামেও পরিচিত, তা স্থাপন করার বিভিন্ন পদ্ধতি ও পেশাদার পদ্ধতি নিয়ে আলোচনা করা হয়েছে।

এটি ওয়েব ডেভেলপারদের নেটিভ প্ল্যাটফর্মের ফিচার—যেমন ক্যামেরা, ফাইল সিস্টেম বা উন্নত হার্ডওয়্যার সেন্সর—অ্যাক্সেস করার জন্য JavaScript ব্যবহার করতে দেয় যা সাধারণ ওয়েব API সাধারণত প্রদান করে না।

উদাহরণ

JavaScript ব্রিজ প্রয়োগ করলে বিভিন্ন ইন্টিগ্রেশন পরিস্থিতি চালু করা যায় যেখানে ওয়েব কন্টেন্টের Android অপারেটিং সিস্টেমের আরও গভীরে অ্যাক্সেস প্রয়োজন হয়। নিচে কিছু উদাহরণ দেওয়া হল:

  • প্ল্যাটফর্ম ইন্টিগ্রেশন: ওয়েবপেজ থেকে নেটিভ Android UI কম্পোনেন্ট ট্রিগার করা (যেমন, বায়োমেট্রিক প্রম্পট, BottomSheetDialog)।
  • পারফর্ম্যান্স: নেটিভ Java বা Kotlin কোডে জটিল গণনা সংক্রান্ত টাস্ক অফলোড করা।
  • ডেটা পারসিস্টেন্স: স্থানীয় এনক্রিপটেড ডেটাবেস বা শেয়ার করা প্রেফারেন্স অ্যাক্সেস করা।
  • প্রচুর ডেটা ট্রান্সফার: অ্যাপ ও ওয়েব রেন্ডারারের মধ্যে মিডিয়া ফাইল বা জটিল ডেটা স্ট্রাকচার পাস করা।

যোগাযোগের পদ্ধতি

নেটিভ ব্রিজ তৈরি করার জন্য Android তিনটি প্রাথমিক প্রজন্মের API অফার করে। এগুলি এখনও উপলভ্য থাকলেও, নিরাপত্তা, ব্যবহারযোগ্যতা ও পারফর্ম্যান্সের দিক থেকে এগুলির মধ্যে অনেক পার্থক্য রয়েছে।

addWebMessageListener ব্যবহার করুন (সাজেস্ট করা)

addWebMessageListener হল ওয়েব কন্টেন্ট ও নেটিভ অ্যাপ কোডের মধ্যে যোগাযোগের সবচেয়ে আধুনিক ও সাজেস্ট করা পদ্ধতি। এটি মেসেজিং সিস্টেমের নিরাপত্তা সহ জাভাস্ক্রিপ্ট ইন্টারফেসের সহজ ব্যবহারকে একত্রিত করে।

এটি কীভাবে কাজ করে: অ্যাপটি একটি নির্দিষ্ট নাম ও অনুমোদিত অরিজিন নিয়মের সেট সহ একটি লিসনার যোগ করে। তারপর, ওয়েবভিউ নিশ্চিত করে যে জাভাস্ক্রিপ্ট অবজেক্টটি গ্লোবাল স্কোপে (window.objectName) আছে, সেই মুহূর্ত থেকে যখন পৃষ্ঠাটি লোড করা শুরু হয়।

শুরু করা: কোনও স্ক্রিপ্ট রান করার আগে WebView যাতে জাভাস্ক্রিপ্ট অবজেক্ট ইনজেক্ট করে তা নিশ্চিত করতে, আপনাকে অবশ্যই কোনও পৃষ্ঠায় নেভিগেট করার আগে addWebMessageListener কল করতে হবে (যেমন, WebViewCompat.navigate বা loadUrl কল করা)।

মূল ফিচার:

  • নিরাপত্তা ও বিশ্বাস: পুরনো API-এর মতো, এই পদ্ধতিতে ইনিশিয়ালাইজেশনের সময় Set<String>-এর allowedOriginRules প্রয়োজন হয়। এটি হল বিশ্বাস স্থাপন করার মূল পদ্ধতি।

    আপনি https://example.com-এর মতো কোনও বিশ্বস্ত অরিজিন নির্দিষ্ট করলে, WebView গ্যারান্টি দেয় যে এটি শুধুমাত্র সেই নির্দিষ্ট অরিজিন থেকে লোড করা ওয়েবপেজেই ইনজেক্ট করা জাভাস্ক্রিপ্ট অবজেক্ট দেখায়।

    নেটিভ লিসনার কলব্যাক প্রতিটি মেসেজের সাথে একটি sourceOrigin প্যারামিটার পায়। আপনার ব্রিজ একাধিক অনুমোদিত অরিজিন সাপোর্ট করলে, আপনি এটি ব্যবহার করে প্রেরকের সঠিক অরিজিন যাচাই করতে পারবেন।

    কারণ, WebView প্ল্যাটফর্ম লেভেলে এই অরিজিন চেক কঠোরভাবে প্রয়োগ করে। তাই, আপনার অ্যাপ সাধারণত বিশ্বস্ত সোর্স থেকে পাওয়া মেসেজকে sourceOrigin সত্য বলে ধরে নিতে পারে। এর ফলে, বেশিরভাগ স্ট্যান্ডার্ড প্রয়োগের ক্ষেত্রে পে-লোড যাচাইকরণের প্রয়োজন হয় না।

    • WebView স্কিম (HTTP/HTTPS), হোস্ট ও পোর্টের সাথে নিয়ম ম্যাচ করে।
    • WebView পাথ উপেক্ষা করে। যেমন, https://example.com-এ https://example.com/login ও https://example.com/home-এর অনুমতি আছে।
    • WebView সাবডোমেনের জন্য হোস্টের শুরুতে ওয়াইল্ডকার্ড কঠোরভাবে সীমিত করে। যেমন, https://*.example.com https://foo.example.com-এর সাথে ম্যাচ করে কিন্তু https://example.com-এর সাথে করে না। আপনাকে https://example.com ও এর সাবডোমেন, দুটিই ম্যাচ করাতে হলে, আপনাকে অবশ্যই প্রতিটি অরিজিন নিয়মকে আলাদাভাবে শ্বেত তালিকায় যোগ করতে হবে (যেমন, "https://example.com", "https://*.example.com")। আপনি স্কিমের জন্য বা ডোমেনের মাঝে ওয়াইল্ডকার্ড ব্যবহার করতে পারবেন না।

    এটি ব্রিজকে যাচাই করা ডোমেনগুলিতে সীমাবদ্ধ করে, যার ফলে অননুমোদিত থার্ড-পার্টি কন্টেন্ট বা ইনজেক্ট করা iframe নেটিভ কোড এক্সিকিউট করতে পারে না।

  • একাধিক ফ্রেমের জন্য সহায়তা: অরিজিন সংক্রান্ত নিয়মের সাথে ম্যাচ করে এমন সব ফ্রেম জুড়ে কাজ করে।

  • থ্রেডিং: লিসনার কলব্যাক অ্যাপ্লিকেশনটির মূল (UI) থ্রেডে চলে। আপনার ব্রিজকে জটিল ডেটা প্রসেসিং, JSON পার্সিং বা ডেটাবেস লুক-আপ হ্যান্ডেল করতে হলে, "অ্যাপ উত্তর দিচ্ছে না" (ANR) সংক্রান্ত সমস্যার কারণে অ্যাপ্লিকেশন UI ফ্রিজ হওয়া আটকাতে, আপনাকে অবশ্যই সেই কাজ ব্যাকগ্রাউন্ড থ্রেডে অফলোড করতে হবে।

  • দ্বিমুখী: ওয়েবপেজ কোনও মেসেজ পাঠালে, অ্যাপ একটি JavaScriptReplyProxy পায় যা সেটি সেই নির্দিষ্ট ফ্রেমকে মেসেজ পাঠানোর জন্য ব্যবহার করতে পারে। আপনি এই replyProxy অবজেক্টটি রেখে দিতে পারেন এবং যেকোনও সময় এটি ব্যবহার করে পৃষ্ঠায় যত খুশি মেসেজ পাঠাতে পারেন, পৃষ্ঠা থেকে পাঠানো প্রতিটি মেসেজের উত্তর দেওয়ার জন্য এটি ব্যবহার করতে হবে না। যে ফ্রেম থেকে নেভিগেট করা হয়েছে সেটি সরে গেলে বা ধ্বংস হয়ে গেলে, প্রক্সিতে postMessage() ব্যবহার করে পাঠানো মেসেজ নিঃশব্দে উপেক্ষা করা হয়।

  • অ্যাপ-সাইড ইনিশিয়েশন: যদিও ওয়েব পেজকে সবসময় অ্যাপের সাথে যোগাযোগের চ্যানেল শুরু করতে হবে, তবে নেটিভ অ্যাপ একতরফাভাবে এই প্রসেস শুরু করার জন্য ওয়েব পেজকে প্রম্পট করতে পারে। নেটিভ অ্যাপ addDocumentStartJavaScript() (পেজ লোড হওয়ার আগে জাভাস্ক্রিপ্ট মূল্যায়ন করতে) অথবা evaluateJavaScript() (পেজ লোড হওয়ার পরে জাভাস্ক্রিপ্ট মূল্যায়ন করতে) ব্যবহার করে ওয়েব পেজের সাথে যোগাযোগ করতে পারে।

সীমাবদ্ধতা: এই API ডেটা স্ট্রিং বা byte[] অ্যারে হিসেবে পাঠায়। JSON অবজেক্টের মতো আরও জটিল ডেটা স্ট্রাকচারের জন্য, আপনাকে অবশ্যই এটি এইসব ফর্ম্যাটের মধ্যে একটিতে সিরিয়ালাইজ করতে হবে এবং তারপরে ডেটা স্ট্রাকচার পুনর্গঠন করতে অন্য দিকে ডিসিরিয়ালাইজ করতে হবে।

ব্যবহারের উদাহরণ:

দ্বিমুখী মেসেজ আদানপ্রদানের সম্পূর্ণ ক্রম বুঝতে, ইভেন্টগুলি এই ক্রমে এগিয়ে চলে:

  1. শুরু করা (অ্যাপ): নেটিভ অ্যাপ, লিসনারকে addWebMessageListener-এর সাথে রেজিস্টার করে এবং পৃষ্ঠা নেভিগেশন শুরু করে (যেমন, WebViewCompat.navigate বা loadUrl)।
  2. মেসেজ পাঠানো (ওয়েব): যোগাযোগ শুরু করার জন্য ওয়েবপেজের জাভাস্ক্রিপ্ট myObject.postMessage(message) কল করে।
  3. মেসেজ পাওয়া ও উত্তর দেওয়া (অ্যাপ): অ্যাপটি লিসনার কলব্যাকে মেসেজ পায় এবং প্রদান করা replyProxy.postMessage() ব্যবহার করে উত্তর দেয়।
  4. উত্তর পাওয়া (ওয়েব): ওয়েব পেজটি অ্যাসিঙ্ক্রোনাস উত্তরটি myObject.onmessage() কলব্যাক ফাংশনে পায়।

Kotlin

val myListener = WebViewCompat.WebMessageListener { _, _, _, _, replyProxy ->
    // Handle the message from JS
    replyProxy.postMessage("Acknowledged!")
}

// Check whether the WebView version supports the feature.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
    val allowedOrigins = setOf("https://www.example.com")
    WebViewCompat.addWebMessageListener(webView, "myObject", allowedOrigins, myListener)
}

জাভা

WebMessageListener myListener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {
    // Handle the message from JS
    replyProxy.postMessage("Acknowledged!");
};

// Check whether the WebView version supports the feature.
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
    Set<String> allowedOrigins = Set.of("https://www.example.com");
    WebViewCompat.addWebMessageListener(webView, "myObject", allowedOrigins, myListener);
}

নিম্নলিখিত জাভাস্ক্রিপ্ট addWebMessageListener-এর ক্লায়েন্ট-সাইড প্রয়োগ দেখায়, যা ওয়েব কন্টেন্টকে নেটিভ অ্যাপ থেকে মেসেজ পেতে এবং myObject প্রক্সির মাধ্যমে নিজের মেসেজ পাঠাতে দেয়।

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

postWebMessage (বিকল্প) ব্যবহার করুন

Android এটি চালু করেছে যাতে ওয়েবের window.postMessage-এর মতো অ্যাসিঙ্ক্রোনাস, মেসেজিং-ভিত্তিক বিকল্প প্রদান করা যায়।

এটি কীভাবে কাজ করে: অ্যাপটি ওয়েব পেজের মূল ফ্রেমে পে-লোড পাঠানোর জন্য WebViewCompat.postWebMessage ব্যবহার করে। দ্বিমুখী যোগাযোগ চ্যানেল স্থাপন করতে, আপনি একটি WebMessageChannel তৈরি করতে পারেন এবং এর একটি পোর্ট ওয়েব কন্টেন্টে মেসেজ সহ পাস করতে পারেন।

বৈশিষ্ট্য:

  • অ্যাসিঙ্ক্রোনাস: addWebMessageListener-এর মতো, এই পদ্ধতিতে অ্যাসিঙ্ক্রোনাস মেসেজিং ব্যবহার করা হয়, যা নিশ্চিত করে যে অ্যাপ ব্যাকগ্রাউন্ডে ডেটা প্রসেস করার সময়ও ব্যবহারকারীর ইন্টার‍্যাকশনের ক্ষেত্রে ওয়েব পেজটি রেসপন্সিভ থাকে।
  • অরিজিন সচেতন: আপনি targetOrigin নির্দিষ্ট করতে পারেন যাতে WebView শুধুমাত্র বিশ্বস্ত ওয়েবসাইটে ডেটা ডেলিভার করে।

সীমাবদ্ধতা:

  • স্কোপ: এই API কমিউনিকেশনকে মূল ফ্রেমের মধ্যে সীমাবদ্ধ রাখে। এটি সরাসরি iframe-এ মেসেজ পাঠানো বা অ্যাড্রেস করা সমর্থন করে না।
  • URI সংক্রান্ত বিধিনিষেধ: আপনি এই পদ্ধতিটি data: URI, file: URI বা loadData() ব্যবহার করে লোড করা কন্টেন্টের জন্য ব্যবহার করতে পারবেন না, যদি না আপনি "*"-কে টার্গেট অরিজিন হিসেবে নির্দিষ্ট করেন। এটি করলে, যেকোনও পৃষ্ঠা মেসেজ পেতে পারে।
  • পরিচয় সংক্রান্ত ঝুঁকি: ওয়েব কন্টেন্টের ক্ষেত্রে প্রেরকের পরিচয় যাচাই করার কোনও স্পষ্ট উপায় নেই। ওয়েবপেজ যে মেসেজটি পায় সেটি আপনার নেটিভ অ্যাপ বা অন্য কোনও iframe থেকে আসতে পারে।

addWebMessageListener কাজ করে না এমন আগের Android ভার্সনে স্ট্রিং-ভিত্তিক ডেটার জন্য সহজ, অ্যাসিঙ্ক চ্যানেল প্রয়োজন হলে এই পদ্ধতি ব্যবহার করুন।

addJavascriptInterface (Legacy) ব্যবহার করুন

সবচেয়ে পুরনো পদ্ধতিতে সরাসরি WebView-তে নেটিভ অবজেক্ট ইনস্ট্যান্স ইনজেক্ট করা হয়।

এটি কীভাবে কাজ করে: আপনি একটি Kotlin বা Java ক্লাস নির্ধারণ করেন, @JavascriptInterface-এর মাধ্যমে অনুমোদিত মেথডকে অ্যানোটেট করেন এবং addJavascriptInterface(Object, String) ব্যবহার করে WebView-তে ক্লাসের একটি ইনস্ট্যান্স যোগ করেন।

বৈশিষ্ট্য:

  • সিঙ্ক্রোনাস: আপনার Android কোডের মেথড রিটার্ন না করা পর্যন্ত জাভাস্ক্রিপ্ট এক্সিকিউশন এনভায়রনমেন্ট ব্লক করে।
  • থ্রেড নিরাপত্তা: সিস্টেম ব্যাকগ্রাউন্ড থ্রেডে মেথড কল করে, যার জন্য Kotlin বা Java-এর দিকে সাবধানে সিঙ্ক্রোনাইজেশন করতে হয়।
  • নিরাপত্তা সংক্রান্ত ঝুঁকি: ডিফল্ট হিসেবে, addJavascriptInterface iframe সহ WebView-এর মধ্যে প্রতিটি ফ্রেমের জন্য উপলভ্য। এতে সোর্স-ভিত্তিক অ্যাক্সেস কন্ট্রোল নেই। WebView-এর অ্যাসিঙ্ক্রোনাস আচরণের কারণে, আপনার ইন্টারফেস কল করছে এমন ফ্রেমের URL নিরাপদে নির্ধারণ করা সম্ভব নয়। নিরাপত্তা যাচাইকরণের জন্য আপনাকে WebView.getUrl()-এর মতো পদ্ধতির উপর নির্ভর করা চলবে না, কারণ সেগুলি সঠিক হবে বলে গ্যারান্টি দেওয়া যায় না এবং কোন নির্দিষ্ট ফ্রেম থেকে অনুরোধ করা হয়েছে তা বোঝা যায় না।

ডেটার ধরন পরিবর্তন ও জোর করে পরিবর্তন করা

addJavascriptInterface ব্যবহার করার সময়, Chromium-ভিত্তিক Java Bridge, JavaScript রানটাইম ও আপনার Android অ্যাপ কোডের মধ্যে ডেটা টাইপ কনভার্ট করে।

পদ্ধতিগত প্যারামিটার ও রিটার্ন ভ্যালুর ক্ষেত্রে নিম্নলিখিত কোয়ের্সন সংক্রান্ত নিয়ম প্রযোজ্য হয়।

প্যারামিটারের ধরন ম্যাপিং (JavaScript থেকে Java)

জাভাস্ক্রিপ্ট কোনও অ্যানোটেটেড Java বা Kotlin পদ্ধতিতে আর্গুমেন্ট পাস করলে, ব্রিজ জাভাস্ক্রিপ্ট ভ্যালুকে সংশ্লিষ্ট Java প্যারামিটার টাইপে কোয়ের্স করে:

Java প্যারামিটার ধরন JavaScript আর্গুমেন্ট ভ্যালু জোরজবরদস্তি করা সংক্রান্ত আচরণ
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 Float.NaN, Double.NaN, Float.POSITIVE_INFINITY বা Double.POSITIVE_INFINITY-এ কোয়ের্স করে।
char সংখ্যা (পূর্ণসংখ্যা) সংশ্লিষ্ট ইউনিকোড কোড পয়েন্টে কনভার্ট করা হয়েছে।
char পূর্ণসংখ্যা নয়, NaN, Infinity \u0000-এ বাধ্য করে।
boolean true / false Java true বা false-এ কোয়ের্স করে।
boolean সংখ্যা, স্ট্রিং, অবজেক্ট false-এ কোয়ের্স করে (এর মধ্যে খালি নয় এমন স্ট্রিং ও শূন্য নয় এমন সংখ্যা অন্তর্ভুক্ত)।
String স্ট্রিং স্ট্রিং ভ্যালু সংরক্ষিত থাকে।
String সংখ্যা, বুলিয়ান স্ট্রিং রেপ্রেজেন্টেশন হিসেবে ফর্ম্যাট করা (যেমন, "42", "true", "false")।
String null / undefined null Java-তে কোয়ের্স করে null; undefined আক্ষরিক স্ট্রিংয়ে কোয়ের্স করে "undefined"।
String Object, ArrayBuffer, TypedArray লিটারেল স্ট্রিং "undefined"-এ কোয়ের্স করে।
প্রিমিটিভ অ্যারে (যেমন int[], byte[], boolean[]) বা String[] অ্যারে ([...]) টার্গেট এলিমেন্টের ধরনের 1D Java অ্যারেতে কনভার্ট করে। স্পার্স অ্যারে ডিফল্ট ভ্যালু (0, false, null) দিয়ে অ্যাসাইন না করা ইন্ডেক্স পূরণ করে।
প্রিমিটিভ অ্যারে (যেমন int[], byte[]) TypedArray (Int8Array, Uint8Array, Int32Array, Float64Array) এলিমেন্টগুলিকে সংশ্লিষ্ট Java প্রিমিটিভ অ্যারেতে কোয়ের্স করা হয়।
মাল্টি-ডাইমেনশনাল অ্যারে (যেমন int[][]) নেস্ট করা অ্যারে ([[...]]) কাজ করে না। মাল্টি-ডাইমেনশনাল অ্যারে প্যারামিটার null হিসেবে মূল্যায়ন করা হয়।
ArrayBuffer, DataView ArrayBuffer, DataView অ্যারে হিসেবে কাজ করে না। ArrayBuffer ও DataView ইনস্ট্যান্সের মূল্যায়ন null হিসেবে করা হয়।
Object বা কাস্টম ক্লাস JavaScript অবজেক্ট ({...}) কাজ করে না। আর্বিট্রারি জাভাস্ক্রিপ্ট অবজেক্ট লিটেরাল Java-তে null হিসেবে মূল্যায়ন করা হয়।
Object বা কাস্টম ক্লাস ইনজেক্ট করা Java অবজেক্ট র‍্যাপার কাজ করে (রাউন্ড-ট্রিপিং)। Java পদ্ধতিতে অন্তর্নিহিত Java ইনস্ট্যান্স পাস করে। Java-এর ধরন প্যারামিটার সিগনেচারের সাথে না মিললে, জাভাস্ক্রিপ্ট ব্যতিক্রম ঘটায়।
বক্সড ধরন (যেমন Integer, Double, Boolean) সংখ্যা, বুলিয়ান কাজ করে না। বক্সড প্রিমিটিভ টাইপকে অস্বচ্ছ অবজেক্ট হিসেবে বিবেচনা করা হয় এবং এর মূল্যায়ন null হয়।
যেকোনও আদিম ডেটা টাইপ null / undefined ডিফল্ট মান (0, 0.0, \u0000, false) প্রয়োগ করে।
Object, String, অ্যারে null Java null-তে কোয়ের্স করে।
রিটার্ন টাইপ ম্যাপিং (Java থেকে JavaScript)

অ্যানোটেট করা Java বা Kotlin মেথড কোনও ভ্যালু রিটার্ন করলে, ব্রিজ সেটি JavaScript টাইপে কনভার্ট করে:

Java রিটার্ন টাইপ JavaScript ভ্যালু JavaScript typeof
boolean true / false "boolean"
byte, short, int, long, float, double নম্বর "number"
char নম্বর (ইউনিকোড কোড পয়েন্ট) "number"
String (নন-নাল) স্ট্রিং ভ্যালু "string"
String (null) undefined "undefined"
void undefined "undefined"
Java অ্যারে (যেমন int[], String[]) undefined "undefined"। অ্যারে রিটার্ন ভ্যালু কাজ করে না। Java মেথড এক্সিকিউট করা হয় না এবং কোনও ব্যতিক্রম না ঘটিয়েই undefined রিটার্ন করা হয়।
Java অবজেক্ট / কাস্টম টাইপ (নন-নাল) অবজেক্ট র‌্যাপার "object". Java ইনস্ট্যান্সের চারপাশে একটি জাভাস্ক্রিপ্ট র‍্যাপার তৈরি করে। JavaScript কোড, এই অবজেক্টের যেকোনও পাবলিক মেথড কল করতে পারে যেগুলি @JavascriptInterface দিয়ে অ্যানোটেট করা হয়েছে।
Java অবজেক্ট / কাস্টম টাইপ (null) null "object"
বক্সড প্রিমিটিভ (যেমন Integer, Double) অবজেক্ট র‌্যাপার "object". অ্যাক্সেস করা যায় এমন @JavascriptInterface পদ্ধতি ছাড়া অস্বচ্ছ জাভা অবজেক্ট র‍্যাপার হিসেবে রিটার্ন করা হয়, এর ফলে জাভাস্ক্রিপ্টে ভ্যালু ব্যবহার করা যায় না।

পদ্ধতি ও মেম্বার অ্যাক্সেসিবিলিটি

জাভাস্ক্রিপ্ট ব্রিজ, অনিচ্ছাকৃত কোড এক্সিকিউশন থেকে রক্ষা করার জন্য কঠোর মেম্বার অ্যাক্সেস ও দৃশ্যমানতা সংক্রান্ত নিয়ম প্রয়োগ করে:

  • ফিল্ড এক্সপোজ করা হয় না: জাভা ফিল্ড (public ও public final ফিল্ড সহ) জাভাস্ক্রিপ্ট থেকে অ্যাক্সেস করা যায় না এবং undefined হিসেবে মূল্যায়ন করা হয়।
  • অ্যানোটেশন সংক্রান্ত প্রয়োজনীয়তা: শুধুমাত্র @JavascriptInterface দিয়ে স্পষ্টভাবে অ্যানোটেট করা পদ্ধতিই জাভাস্ক্রিপ্টে এক্সপোজ করা হয়।
  • দৃশ্যমানতা সংক্রান্ত বিধিনিষেধ: পদ্ধতি অবশ্যই public হতে হবে। private এবং protected পদ্ধতিগুলি কখনই জাভাস্ক্রিপ্টে এক্সপোজ করা হয় না, এমনকি সেগুলিতে @JavascriptInterface অ্যানোটেশন থাকলেও না।
  • স্ট্যাটিক পদ্ধতি: @JavascriptInterface দিয়ে অ্যানোটেশন করা স্ট্যাটিক পদ্ধতিগুলি জাভাস্ক্রিপ্ট থেকে কল করা যায়।
  • ইনহেরিটেন্স ও ওভাররাইডিং: কোনও সাবক্লাস কোনও মেথড ওভাররাইড করলে @JavascriptInterface অ্যানোটেশন ইনহেরিট করা হয় না। কোনও সাবক্লাস যদি সুপারক্লাসের অ্যানোটেটেড পদ্ধতি ওভাররাইড করে, তাহলে সাবক্লাসকে অবশ্যই ওভাররাইড করা পদ্ধতিতে @JavascriptInterface অ্যানোটেশন স্পষ্টভাবে যোগ করতে হবে, যাতে সেটি JavaScript-এ দেখা যায়। সুপারক্লাস থেকে উত্তরাধিকার সূত্রে প্রাপ্ত ওভাররাইড না করা পাবলিক পদ্ধতিগুলি সুপারক্লাসে অ্যানোটেট করা হলে অ্যাক্সেসযোগ্য থাকে।
  • রিফ্লেকশন সুরক্ষা: স্ট্যান্ডার্ড জাভা রিফ্লেকশন পদ্ধতি (যেমন getClass()) ব্লক করা হয় এবং রিমোট কোড এক্সিকিউশন সংক্রান্ত দুর্বলতা প্রতিরোধ করতে জাভাস্ক্রিপ্ট ব্যতিক্রম তৈরি করে।
  • মেথড ওভারলোডিং: ওভারলোড করা Java মেথড কাজ করে। ব্রিজ শুধুমাত্র পাস করা আর্গুমেন্টের সংখ্যার উপর ভিত্তি করে মেথড কল সমাধান করে এবং আর্গুমেন্টের ধরন বিবেচনা করে না। ভুল আর্গুমেন্টের সংখ্যা সহ ওভারলোড করা কোনও মেথড কল করলে জাভাস্ক্রিপ্ট ব্যতিক্রম ঘটে। দুটি ওভারলোডের আর্গুমেন্টের সংখ্যা একই হলে, একটিকে নির্বিচারে বেছে নেওয়া হবে।

মেকানিজমের সারসংক্ষেপ

নিচের সারণীতে তিনটি প্রাথমিক নেটিভ ব্রিজ ইমপ্লিমেন্টেশন মেকানিজমের একটি দ্রুত তুলনা দেওয়া হয়েছে:

পদ্ধতি addWebMessageListener postWebMessage addJavascriptInterface
প্রয়োগ অ্যাসিঙ্ক্রোনাস (মূল থ্রেডে লিসনার) অসমনিয়ত সিংক্রোনাস
নিরাপত্তা সর্বোচ্চ (সাদাতালিকা-ভিত্তিক) উচ্চ (উৎস সচেতন) কম (কোনও অরিজিন চেক করা হয় না)
জটিলতা মাঝারি মাঝারি Simple
দিকনির্দেশ দ্বিমুখী দ্বিমুখী ওয়েব থেকে অ্যাপে
WebView-এর ন্যূনতম ভার্সন ভার্সন ৮২ (এবং Jetpack Webkit 1.3.0) ভার্সন 45 (এবং Jetpack Webkit 1.1.0) সমস্ত সংস্করণ
সাজেস্ট করা হয়েছে হ্যাঁ না না

বড় ডেটা ট্রান্সফার ম্যানেজ করা

32-বিট ডিভাইসে অ্যাপ্লিকেশন উত্তর দিচ্ছে না (ANR) সংক্রান্ত সমস্যা বা ক্র্যাশ এড়াতে মাল্টি-মেগাবাইট স্ট্রিং বা বাইনারি ফাইলের মতো বড় পে-লোড ট্রান্সফার করার সময় আপনাকে অবশ্যই মেমরি সাবধানে ম্যানেজ করতে হবে। এই বিভাগে হোস্ট অ্যাপ্লিকেশন ও ওয়েব কন্টেন্টের মধ্যে উল্লেখযোগ্য পরিমাণে ডেটা ট্রান্সফার করার সাথে যুক্ত বিভিন্ন টেকনিক ও সীমাবদ্ধতা নিয়ে আলোচনা করা হয়েছে।

বাইট অ্যারে সহ বাইনারি ডেটা ট্রান্সফার করা

WebMessageCompat ক্লাসের সাহায্যে, Base64 স্ট্রিংয়ে বাইনারি ডেটা সিরিয়ালাইজ করার পরিবর্তে, আপনি সরাসরি byte[] অ্যারে পাঠাতে পারবেন । Base64 ডেটার সাইজে মোটামুটি ৩৩% ওভারহেড যোগ করে, তাই এটি অনেক বেশি মেমরি-সাশ্রয়ী এবং দ্রুত।

  • বাইনারি সুবিধা: আপনার নেটিভ অ্যাপ ও ওয়েব কন্টেন্টের মধ্যে ছবি ফাইল বা অডিওর মতো বাইনারি ডেটা ট্রান্সফার করুন।
  • সীমাবদ্ধতা: বাইট অ্যারে থাকলেও, সিস্টেম অ্যাপ ও আইসোলেটেড প্রসেসের মধ্যে ইন্টার-প্রসেস কমিউনিকেশন (IPC) বাউন্ডারি জুড়ে ডেটা কপি করে। WebView ওয়েব কন্টেন্ট রেন্ডার করার জন্য এটি ব্যবহার করে। এরপরেও খুব বড় ফাইলের জন্য উল্লেখযোগ্য মেমরি খরচ হয়।

addWebMessageListener চিহ্নিত মেসেজ পেতে নেটিভ অ্যাপের দিকে WebMessageCompat.TYPE_ARRAY_BUFFER কীভাবে সেট-আপ করতে হয় এবং WebViewFeature.MESSAGE_ARRAY_BUFFER চেক করে ঐচ্ছিকভাবে বাইনারি ডেটা দিয়ে উত্তর দিতে হয় তা নিম্নলিখিত কোডের উদাহরণ থেকে বুঝতে পারবেন।

Kotlin

fun setupWebView(webView: WebView) {
    if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
        val listener = WebViewCompat.WebMessageListener { view, message, sourceOrigin, isMainFrame, replyProxy ->

            // Check if the received message is an ArrayBuffer
            if (message.type == WebMessageCompat.TYPE_ARRAY_BUFFER) {
                val binaryData: ByteArray = message.arrayBuffer
                // Process your binary data (image, audio, etc.)
                println("Received bytes: ${binaryData.size}")

                // Optional: Send a binary reply back to JavaScript.
                // This example sends a 3-byte array for simplicity.
                if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
                    val replyBytes = byteArrayOf(0x01, 0x02, 0x03)
                    replyProxy.postMessage(replyBytes)
                }
            }
        }

        // "myBridge" matches the window.myBridge in JavaScript
        WebViewCompat.addWebMessageListener(
            webView,
            "myBridge",
            setOf("https://example.com"), // Security: restrict origins
            listener
        )
    }
}

জাভা

public void setupWebView(WebView webView) {
  if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
      WebViewCompat.WebMessageListener listener = (view, message, sourceOrigin, isMainFrame, replyProxy) -> {

          // Check if the received message is an ArrayBuffer
          if (message.getType() == WebMessageCompat.TYPE_ARRAY_BUFFER) {
              byte[] binaryData = message.getArrayBuffer();
              // Process your binary data (image, audio, etc.)
              System.out.println("Received bytes: " + binaryData.length);

              // Optional: Send a binary reply back to JavaScript.
              // This example sends a 3-byte array for simplicity.
              if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
                  byte[] replyBytes = new byte[]{0x01, 0x02, 0x03};
                  replyProxy.postMessage(replyBytes);
              }
          }
      };

      // "myBridge" matches the window.myBridge in JavaScript
      WebViewCompat.addWebMessageListener(
          webView,
          "myBridge",
          Set.of("https://example.com"), // Security: restrict origins
          listener
      );
  }
}

নিম্নলিখিত জাভাস্ক্রিপ্ট কোডটি addWebMessageListener-এর ক্লায়েন্ট-সাইড প্রয়োগ দেখায়, যা ওয়েব কন্টেন্টকে বাইনারি ডেটা (ArrayBuffer) পাঠাতে ও গ্রহণ করতে দেয়। এটি আগের উদাহরণে ইনজেক্ট করা window.myBridge প্রক্সি ব্যবহার করে নেটিভ অ্যাপে ও অ্যাপ থেকে ডেটা পাঠায় ও গ্রহণ করে।

// Function to send an image or binary buffer to the app
async function sendBinaryToApp() {
    const response = await fetch('image.jpg');
    const buffer = await response.arrayBuffer();

    // Check if the injected bridge object exists
    if (window.myBridge) {
        // You can send the ArrayBuffer directly
        window.myBridge.postMessage(buffer);
    }
}

// Receiving binary data from the app
if (window.myBridge) {
    window.myBridge.onmessage = function(event) {
        if (event.data instanceof ArrayBuffer) {
            console.log('Received binary data from App, length:', event.data.byteLength);
            // Process the binary data (for example, as a Uint8Array)
            const bytes = new Uint8Array(event.data);
            console.log('First byte:', bytes[0]);
        }
    };
}

দক্ষতার সাথে প্রচুর ডেটা লোড করা

খুব বড় ফাইলের (>১০ এমবি) ক্ষেত্রে, ডেটা স্ট্রিম করতে shouldInterceptRequest পদ্ধতি ব্যবহার করুন:

  1. ওয়েবপেজ একটি কাস্টম, প্লেসহোল্ডার URL-এ fetch() কল শুরু করে। যেমন, https://app.local/large-file।
  2. Android অ্যাপ এই অনুরোধটি WebViewClient.shouldInterceptRequest-এ ইন্টারসেপ্ট করে।
  3. অ্যাপটি InputStream হিসেবে ডেটা রিটার্ন করে।

এটি পুরো পে-লোড একবারে মেমোরিতে লোড করার পরিবর্তে ডেটা ছোট ছোট ভাগে স্ট্রিম করার সুবিধা দেয়।

নিচে দেওয়া JavaScript ফাংশনটি নেটিভ অ্যাপ্লিকেশন থেকে একটি কাস্টম, প্লেসহোল্ডার URL-এ স্ট্যান্ডার্ড fetch() কল ব্যবহার করে দক্ষতার সাথে একটি বড় বাইনারি ফাইল লোড করার জন্য ক্লায়েন্ট-সাইড কোড দেখায়।

async function fetchBinaryFromApp() {
    try {
        // This URL doesn't need to exist on the internet
        const response = await fetch('https://app.local/data/large-file.bin');

        if (!response.ok) throw new Error('Network response was not okay');

        // For raw binary data:
        const arrayBuffer = await response.arrayBuffer();
        console.log('Received binary data, size:', arrayBuffer.byteLength);
        // Process buffer (for example, new Uint8Array(arrayBuffer))

        /*
        // OR for an image:
        const blob = await response.blob();
        const imageUrl = URL.createObjectURL(blob);
        document.getElementById('myImage').src = imageUrl;
        */

    } catch (error) {
        console.error('Fetch error:', error);
    }
}

নিচের কোডের উদাহরণে নেটিভ অ্যাপের দিকটি দেখানো হয়েছে। এতে Kotlin ও Java, দু'টি ভাষাতেই WebViewClient.shouldInterceptRequest পদ্ধতি ব্যবহার করে ওয়েব কন্টেন্টের অনুরোধ করা কাস্টম প্লেসহোল্ডার URL ইন্টারসেপ্ট করে একটি বড় বাইনারি ফাইল স্ট্রিম করা হয়েছে।

Kotlin

webView.webViewClient = object : WebViewClient() {
    override fun shouldInterceptRequest(
        view: WebView?,
        request: WebResourceRequest?
    ): WebResourceResponse? {
        val url = request?.url ?: return null

        // Check if this is our custom placeholder URL
        if (url.host == "app.local" && url.path == "/data/large-file.bin") {
            try {
                // 1. Get your data as an InputStream
                // (from Assets, Files, or a generated byte stream)
                val inputStream: InputStream = context.assets.open("my_data.pb")

                // 2. Define Response Headers (Crucial for CORS/Fetch)
                val headers = mutableMapOf<String, String>()
                headers["Access-Control-Allow-Origin"] = "*" // Allow fetch from any origin

                // 3. Return the response
                return WebResourceResponse(
                    "application/octet-stream", // MIME type (for example, image/jpeg)
                    "UTF-8", // Encoding
                    200, // Status Code
                    "OK", // Reason Phrase
                    headers, // Custom Headers
                    inputStream // The actual data stream
                )
            } catch (e: Exception) {
                // Handle exception
            }
        }
        return super.shouldInterceptRequest(view, request)
    }
}

জাভা

webView.setWebViewClient(new WebViewClient() {
  @Override
  public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
      String urlPath = request.getUrl().getPath();
      String host = request.getUrl().getHost();

      // Check if this is our custom placeholder URL
      if ("app.local".equals(host) && "/data/large-file.bin".equals(urlPath)) {
          try {
              // 1. Get your data as an InputStream
              // (from Assets, Files, or a generated byte stream)
              InputStream inputStream = getContext().getAssets().open("my_data.pb");

              // 2. Define Response Headers (Crucial for CORS/Fetch)
              Map<String, String> headers = new HashMap<>();
              headers.put("Access-Control-Allow-Origin", "*"); // Allow fetch from any origin

              // 3. Return the response
              return new WebResourceResponse(
                  "application/octet-stream", // MIME type (for example, image/jpeg)
                  "UTF-8",                   // Encoding
                  200,                       // Status Code
                  "OK",                      // Reason Phrase
                  headers,                   // Custom Headers
                  inputStream                // The actual data stream
              );
          } catch (Exception e) {
              // Handle exception
          }
      }
      return super.shouldInterceptRequest(view, request);
  }
});

নিরাপত্তা সংক্রান্ত সাজেশন ফলো করা

আপনার অ্যাপ্লিকেশন ও ব্যবহারকারীর ডেটা সুরক্ষিত রাখতে, ব্রিজ ইমপ্লিমেন্ট করার সময় এইসব নির্দেশিকা অনুসরণ করুন:

  • HTTPS প্রয়োগ করুন: ক্ষতিকর থার্ড-পার্টি কন্টেন্ট যাতে আপনার অ্যাপ্লিকেশনের নেটিভ লজিক ইনভোক করতে না পারে তা নিশ্চিত করতে, শুধুমাত্র সুরক্ষিত অরিজিন থেকে কমিউনিকেশন করার অনুমতি দিন।

  • অরিজিন সংক্রান্ত নিয়মের উপর ভরসা করা: বিশ্বাসযোগ্যতা সংক্রান্ত সমস্যার সমাধান করার সবচেয়ে ভালো উপায় হল, আপনার allowedOriginRules কঠোরভাবে নির্ধারণ করা এবং মেসেজ কলব্যাকে প্রদান করা sourceOrigin চেক করা। সম্পূর্ণ ওয়াইল্ডকার্ড (*) ব্যবহার করা এড়িয়ে চলুন, এটি সব ডোমেনের সাথে ম্যাচ করে। এটি একমাত্র অরিজিন নিয়ম হিসেবে ব্যবহার করবেন না, যদি না এটি একান্তই প্রয়োজন হয়। সাবডোমেনের জন্য ওয়াইল্ডকার্ড (যেমন, *.example.com) ব্যবহার করা এখনও বৈধ এবং একাধিক সাবডোমেন (যেমন, foo.example.com, bar.example.com) ম্যাচ করার জন্য নিরাপদ।

    মনে রাখবেন: অরিজিন সংক্রান্ত নিয়ম ক্ষতিকারক থার্ড-পার্টি ওয়েবসাইট ও লুকানো iframe থেকে রক্ষা করলেও, আপনার বিশ্বস্ত ডোমেনের মধ্যে থাকা ক্রস-সাইট স্ক্রিপ্টিং (XSS) সংক্রান্ত দুর্বলতা থেকে রক্ষা করতে পারে না। যেমন, আপনার ওয়েব পেজে ব্যবহারকারীর তৈরি করা কন্টেন্ট দেখানো হলে এবং সেটি সেভ করা XSS-এর প্রতি দুর্বল হলে, কোনও আক্রমণকারী আপনার বিশ্বস্ত অরিজিন হিসেবে কাজ করে এমন একটি স্ক্রিপ্ট এক্সিকিউট করতে পারে। সংবেদনশীল নেটিভ প্ল্যাটফর্ম অপারেশন এক্সিকিউট করার আগে মেসেজ পেলোডের উপর যাচাইকরণ প্রয়োগ করার কথা বিবেচনা করুন।

  • সারফেস এরিয়া কমানো: ওয়েবপেজের প্রয়োজনীয় নির্দিষ্ট পদ্ধতি বা ডেটাই শুধু এক্সপোজ করুন।

  • রানটাইমে ফিচার চেক করা: Jetpack Webkit লাইব্রেরির অংশ হল addWebMessageListener সহ সাম্প্রতিক ব্রিজ API। তাই, তাদের কল করার আগে WebViewFeature.isFeatureSupported() ব্যবহার করে সহায়তা আছে কিনা তা সবসময় চেক করে নিন।