JavaScript এবং WebAssembly চালানো

জাভাস্ক্রিপ্ট মূল্যায়ন

Jetpack লাইব্রেরি JavaScriptEngine একটি অ্যাপ্লিকেশনের জন্য একটি WebView উদাহরণ তৈরি না করেই JavaScript কোড মূল্যায়ন করার একটি উপায় প্রদান করে।

নন-ইন্টারেক্টিভ জাভাস্ক্রিপ্ট মূল্যায়ন প্রয়োজন এমন অ্যাপ্লিকেশনগুলির জন্য, JavaScriptEngine লাইব্রেরি ব্যবহার করার নিম্নলিখিত সুবিধা রয়েছে:

  • কম রিসোর্স খরচ, যেহেতু ওয়েবভিউ ইন্সট্যান্স বরাদ্দ করার প্রয়োজন নেই।

  • একটি পরিষেবাতে করা যেতে পারে (ওয়ার্ক ম্যানেজার টাস্ক)।

  • কম ওভারহেড সহ একাধিক বিচ্ছিন্ন পরিবেশ, অ্যাপ্লিকেশানটিকে একাধিক জাভাস্ক্রিপ্ট স্নিপেট একসাথে চালাতে সক্ষম করে৷

  • একটি API কল ব্যবহার করে প্রচুর পরিমাণে ডেটা পাস করার ক্ষমতা।

মৌলিক ব্যবহার

শুরু করতে, JavaScriptSandbox এর একটি উদাহরণ তৈরি করুন। এটি প্রক্রিয়ার বাইরে থাকা জাভাস্ক্রিপ্ট ইঞ্জিনের সাথে একটি সংযোগের প্রতিনিধিত্ব করে।

ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
               JavaScriptSandbox.createConnectedInstanceAsync(context);

স্যান্ডবক্সের জীবনচক্রকে সেই উপাদানের জীবনচক্রের সাথে সারিবদ্ধ করার পরামর্শ দেওয়া হচ্ছে যার জন্য JavaScript মূল্যায়ন প্রয়োজন।

উদাহরণস্বরূপ, স্যান্ডবক্স হোস্ট করা একটি উপাদান একটি Activity বা একটি Service হতে পারে। সমস্ত অ্যাপ্লিকেশন উপাদানগুলির জন্য জাভাস্ক্রিপ্ট মূল্যায়ন এনক্যাপসুলেট করতে একটি একক Service ব্যবহার করা যেতে পারে।

JavaScriptSandbox দৃষ্টান্ত বজায় রাখুন কারণ এর বরাদ্দ মোটামুটি ব্যয়বহুল। প্রতি অ্যাপ্লিকেশানের জন্য শুধুমাত্র একটি JavaScriptSandbox উদাহরণ অনুমোদিত৷ একটি IllegalStateException নিক্ষেপ করা হয় যখন একটি অ্যাপ্লিকেশন একটি দ্বিতীয় JavaScriptSandbox উদাহরণ বরাদ্দ করার চেষ্টা করে। যাইহোক, যদি একাধিক এক্সিকিউশন এনভায়রনমেন্টের প্রয়োজন হয়, বেশ কয়েকটি JavaScriptIsolate উদাহরণ বরাদ্দ করা যেতে পারে।

যখন এটি আর ব্যবহার করা হয় না, সম্পদ খালি করতে স্যান্ডবক্সের উদাহরণ বন্ধ করুন। JavaScriptSandbox দৃষ্টান্ত একটি AutoCloseable ইন্টারফেস প্রয়োগ করে, যা সাধারণ ব্লকিং ব্যবহারের ক্ষেত্রে সম্পদ ব্যবহার করার চেষ্টা করার অনুমতি দেয়। বিকল্পভাবে, নিশ্চিত করুন যে JavaScriptSandbox ইনস্ট্যান্স লাইফসাইকেল হোস্টিং কম্পোনেন্ট দ্বারা পরিচালিত হয়, এটিকে একটি ক্রিয়াকলাপের জন্য onStop() কলব্যাকে বা একটি পরিষেবার জন্য onDestroy() চলাকালীন বন্ধ করে:

jsSandbox.close();

একটি JavaScriptIsolate উদাহরণ জাভাস্ক্রিপ্ট কোড কার্যকর করার জন্য একটি প্রসঙ্গ উপস্থাপন করে। প্রয়োজনে এগুলি বরাদ্দ করা যেতে পারে, বিভিন্ন উত্সের স্ক্রিপ্টগুলির জন্য দুর্বল সুরক্ষা সীমানা প্রদান করে বা সমসাময়িক জাভাস্ক্রিপ্ট এক্সিকিউশন সক্ষম করে যেহেতু জাভাস্ক্রিপ্ট প্রকৃতির দ্বারা একক-থ্রেডেড। একই দৃষ্টান্তের পরবর্তী কলগুলি একই অবস্থা ভাগ করে, তাই প্রথমে কিছু ডেটা তৈরি করা সম্ভব এবং তারপরে JavaScriptIsolate এর একই উদাহরণে এটি প্রক্রিয়া করা সম্ভব।

JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();

JavaScriptIsolate এর close() পদ্ধতিতে কল করে স্পষ্টভাবে প্রকাশ করুন। জাভাস্ক্রিপ্ট কোড (একটি অসম্পূর্ণ Future থাকা) চলমান একটি বিচ্ছিন্ন উদাহরণ বন্ধ করার ফলে একটি IsolateTerminatedException হয়। আইসোলেটটি পরবর্তীতে পটভূমিতে পরিষ্কার করা হয় যদি বাস্তবায়ন JS_FEATURE_ISOLATE_TERMINATION সমর্থন করে, যেমনটি এই পৃষ্ঠায় পরে হ্যান্ডলিং স্যান্ডবক্স ক্র্যাশ বিভাগে বর্ণিত হয়েছে। অন্যথায়, সমস্ত মুলতুবি মূল্যায়ন সম্পূর্ণ না হওয়া পর্যন্ত বা স্যান্ডবক্স বন্ধ না হওয়া পর্যন্ত পরিচ্ছন্নতা স্থগিত করা হবে।

একটি অ্যাপ্লিকেশন যেকোনো থ্রেড থেকে একটি JavaScriptIsolate উদাহরণ তৈরি এবং অ্যাক্সেস করতে পারে।

এখন, অ্যাপ্লিকেশনটি কিছু জাভাস্ক্রিপ্ট কোড চালানোর জন্য প্রস্তুত:

final String code = "function sum(a, b) { let r = a + b; return r.toString(); }; sum(3, 4)";
ListenableFuture<String> resultFuture = jsIsolate.evaluateJavaScriptAsync(code);
String result = resultFuture.get(5, TimeUnit.SECONDS);

একই জাভাস্ক্রিপ্ট স্নিপেট সুন্দরভাবে বিন্যাসিত:

function sum(a, b) {
    let r = a + b;
    return r.toString(); // make sure we return String instance
};

// Calculate and evaluate the expression
// NOTE: We are not in a function scope and the `return` keyword
// should not be used. The result of the evaluation is the value
// the last expression evaluates to.
sum(3, 4);

কোড স্নিপেট একটি String হিসাবে পাস করা হয় এবং ফলাফল একটি String হিসাবে বিতরণ করা হয়। মনে রাখবেন যে কলিং evaluateJavaScriptAsync() জাভাস্ক্রিপ্ট কোডের শেষ এক্সপ্রেশনের মূল্যায়ন করা ফলাফল প্রদান করে। এটি অবশ্যই জাভাস্ক্রিপ্ট String টাইপের হতে হবে; অন্যথায়, লাইব্রেরি API একটি খালি মান প্রদান করে। জাভাস্ক্রিপ্ট কোড return কীওয়ার্ড ব্যবহার করা উচিত নয়। যদি স্যান্ডবক্স নির্দিষ্ট বৈশিষ্ট্য সমর্থন করে, অতিরিক্ত রিটার্ন প্রকারগুলি (উদাহরণস্বরূপ, একটি Promise যা একটি String সমাধান করে) সম্ভব হতে পারে।

লাইব্রেরিটি একটি AssetFileDescriptor বা একটি ParcelFileDescriptor আকারে থাকা স্ক্রিপ্টগুলির মূল্যায়নকেও সমর্থন করে৷ আরো বিস্তারিত জানার জন্য evaluateJavaScriptAsync(AssetFileDescriptor) এবং evaluateJavaScriptAsync(ParcelFileDescriptor) দেখুন। এই APIগুলি ডিস্কে বা অ্যাপ ডিরেক্টরিতে থাকা ফাইল থেকে মূল্যায়নের জন্য আরও উপযুক্ত।

লাইব্রেরি কনসোল লগিং সমর্থন করে যা ডিবাগিং উদ্দেশ্যে ব্যবহার করা যেতে পারে। এটি setConsoleCallback() ব্যবহার করে সেট আপ করা যেতে পারে।

যেহেতু প্রসঙ্গটি টিকে থাকে, আপনি কোড আপলোড করতে পারেন এবং JavaScriptIsolate এর জীবদ্দশায় বেশ কয়েকবার এটি চালাতে পারেন:

String jsFunction = "function sum(a, b) { let r = a + b; return r.toString(); }";
ListenableFuture<String> func = js.evaluateJavaScriptAsync(jsFunction);
String twoPlusThreeCode = "let five = sum(2, 3); five";
ListenableFuture<String> r1 = Futures.transformAsync(func,
       input -> js.evaluateJavaScriptAsync(twoPlusThreeCode)
       , executor);
String twoPlusThree = r1.get(5, TimeUnit.SECONDS);

String fourPlusFiveCode = "sum(4, parseInt(five))";
ListenableFuture<String> r2 = Futures.transformAsync(func,
       input -> js.evaluateJavaScriptAsync(fourPlusFiveCode)
       , executor);
String fourPlusFive = r2.get(5, TimeUnit.SECONDS);

অবশ্যই, ভেরিয়েবলগুলিও স্থায়ী, তাই আপনি এর সাথে পূর্ববর্তী স্নিপেটটি চালিয়ে যেতে পারেন:

String defineResult = "let result = sum(11, 22);";
ListenableFuture<String> r3 = Futures.transformAsync(func,
       input -> js.evaluateJavaScriptAsync(defineResult)
       , executor);
String unused = r3.get(5, TimeUnit.SECONDS);

String obtainValue = "result";
ListenableFuture<String> r4 = Futures.transformAsync(func,
       input -> js.evaluateJavaScriptAsync(obtainValue)
       , executor);
String value = r4.get(5, TimeUnit.SECONDS);

উদাহরণস্বরূপ, সমস্ত প্রয়োজনীয় বস্তু বরাদ্দ করার জন্য এবং একটি জাভাস্ক্রিপ্ট কোড চালানোর জন্য সম্পূর্ণ স্নিপেট নিম্নলিখিত মত দেখতে পারে:

final ListenableFuture<JavaScriptSandbox> sandbox
       = JavaScriptSandbox.createConnectedInstanceAsync(this);
final ListenableFuture<JavaScriptIsolate> isolate
       = Futures.transform(sandbox,
               input -> (jsSandBox = input).createIsolate(),
               executor);
final ListenableFuture<String> js
       = Futures.transformAsync(isolate,
               isolate -> (jsIsolate = isolate).evaluateJavaScriptAsync("'PASS OK'"),
               executor);
Futures.addCallback(js,
       new FutureCallback<String>() {
           @Override
           public void onSuccess(String result) {
               text.append(result);
           }
           @Override
           public void onFailure(Throwable t) {
               text.append(t.getMessage());
           }
       },
       mainThreadExecutor);

সমস্ত বরাদ্দকৃত সংস্থান প্রকাশ করা হয়েছে এবং আর ব্যবহার করা হচ্ছে না তা নিশ্চিত করতে আপনি চেষ্টা-সহ-সম্পদ ব্যবহার করার পরামর্শ দেওয়া হচ্ছে। স্যান্ডবক্স বন্ধ করার ফলে সমস্ত JavaScriptIsolate দৃষ্টান্তগুলির সমস্ত মুলতুবি মূল্যায়ন একটি SandboxDeadException এর সাথে ব্যর্থ হয়। যখন JavaScript মূল্যায়ন একটি ত্রুটির সম্মুখীন হয়, একটি JavaScriptException তৈরি করা হয়। আরো সুনির্দিষ্ট ব্যতিক্রমের জন্য এর সাবক্লাসগুলি পড়ুন।

স্যান্ডবক্স ক্র্যাশগুলি পরিচালনা করা

সমস্ত জাভাস্ক্রিপ্ট আপনার অ্যাপ্লিকেশনের মূল প্রক্রিয়া থেকে দূরে একটি পৃথক স্যান্ডবক্সড প্রক্রিয়ায় সম্পাদিত হয়। যদি জাভাস্ক্রিপ্ট কোডের কারণে এই স্যান্ডবক্সড প্রক্রিয়াটি ক্র্যাশ হয়ে যায়, উদাহরণস্বরূপ, একটি মেমরি সীমা শেষ করে, অ্যাপ্লিকেশনটির মূল প্রক্রিয়াটি প্রভাবিত হবে না।

একটি স্যান্ডবক্স ক্র্যাশের ফলে সেই স্যান্ডবক্সের সমস্ত বিচ্ছিন্নতা বন্ধ হয়ে যাবে। এর সবচেয়ে সুস্পষ্ট লক্ষণ হল যে সমস্ত মূল্যায়ন IsolateTerminatedException এর সাথে ব্যর্থ হতে শুরু করবে। পরিস্থিতির উপর নির্ভর করে, আরও নির্দিষ্ট ব্যতিক্রম যেমন SandboxDeadException বা MemoryLimitExceededException নিক্ষেপ করা যেতে পারে।

প্রতিটি পৃথক মূল্যায়নের জন্য ক্র্যাশগুলি পরিচালনা করা সর্বদা ব্যবহারিক নয়। তদুপরি, পটভূমির কাজ বা অন্যান্য বিচ্ছিন্নতার মূল্যায়নের কারণে একটি বিচ্ছিন্নতা স্পষ্টভাবে অনুরোধ করা মূল্যায়নের বাইরে শেষ হতে পারে। JavaScriptIsolate.addOnTerminatedCallback() ব্যবহার করে একটি কলব্যাক সংযুক্ত করে ক্র্যাশ হ্যান্ডলিং লজিক কেন্দ্রীভূত করা যেতে পারে।

final ListenableFuture<JavaScriptSandbox> sandboxFuture =
    JavaScriptSandbox.createConnectedInstanceAsync(this);
final ListenableFuture<JavaScriptIsolate> isolateFuture =
    Futures.transform(sandboxFuture, sandbox -> {
      final IsolateStartupParameters startupParams = new IsolateStartupParameters();
      if (sandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_ISOLATE_MAX_HEAP_SIZE)) {
        startupParams.setMaxHeapSizeBytes(100_000_000);
      }
      return sandbox.createIsolate(startupParams);
    }, executor);
Futures.transform(isolateFuture,
    isolate -> {
      // Add a crash handler
      isolate.addOnTerminatedCallback(executor, terminationInfo -> {
        Log.e(TAG, "The isolate crashed: " + terminationInfo);
      });
      // Cause a crash (eventually)
      isolate.evaluateJavaScriptAsync("Array(1_000_000_000).fill(1)");
      return null;
    }, executor);

ঐচ্ছিক স্যান্ডবক্স বৈশিষ্ট্য

অন্তর্নিহিত WebView সংস্করণের উপর নির্ভর করে, একটি স্যান্ডবক্স বাস্তবায়নে বিভিন্ন সেট বৈশিষ্ট্য উপলব্ধ থাকতে পারে। সুতরাং, JavaScriptSandbox.isFeatureSupported(...) ব্যবহার করে প্রতিটি প্রয়োজনীয় বৈশিষ্ট্যের জন্য অনুসন্ধান করা প্রয়োজন। এই বৈশিষ্ট্যগুলির উপর নির্ভর করে কল করার পদ্ধতিগুলির আগে বৈশিষ্ট্যের স্থিতি পরীক্ষা করা গুরুত্বপূর্ণ৷

JavaScriptIsolate পদ্ধতিগুলি যেগুলি সর্বত্র উপলব্ধ নাও হতে পারে সেগুলি RequiresFeature টীকা দিয়ে টীকা করা হয়, যা এই কলগুলিকে কোডে চিহ্নিত করা সহজ করে তোলে৷

পরামিতি পাস

JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT সমর্থিত হলে, জাভাস্ক্রিপ্ট ইঞ্জিনে পাঠানো মূল্যায়নের অনুরোধগুলি বাইন্ডার লেনদেনের সীমা দ্বারা আবদ্ধ নয়৷ বৈশিষ্ট্যটি সমর্থিত না হলে, JavaScriptEngine-এ সমস্ত ডেটা একটি বাইন্ডার লেনদেনের মাধ্যমে ঘটে। সাধারণ লেনদেনের আকারের সীমা প্রতিটি কলের জন্য প্রযোজ্য যা ডেটা পাস করে বা ডেটা ফেরত দেয়।

প্রতিক্রিয়াটি সর্বদা একটি স্ট্রিং হিসাবে ফেরত দেওয়া হয় এবং JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT সমর্থিত না হলে বাইন্ডার লেনদেনের সর্বোচ্চ আকারের সীমা সাপেক্ষে৷ নন-স্ট্রিং মানগুলিকে স্পষ্টভাবে একটি জাভাস্ক্রিপ্ট স্ট্রিং-এ রূপান্তর করতে হবে অন্যথায় একটি খালি স্ট্রিং ফেরত দেওয়া হবে। JS_FEATURE_PROMISE_RETURN বৈশিষ্ট্যটি সমর্থিত হলে, JavaScript কোড বিকল্পভাবে একটি String এ একটি প্রতিশ্রুতি সমাধান করতে পারে।

JavaScriptIsolate উদাহরণে বড় বাইট অ্যারে পাস করার জন্য, আপনি provideNamedData(...) API ব্যবহার করতে পারেন। এই API-এর ব্যবহার বাইন্ডার লেনদেনের সীমা দ্বারা আবদ্ধ নয়। প্রতিটি বাইট অ্যারে একটি অনন্য শনাক্তকারী ব্যবহার করে পাস করতে হবে যা পুনরায় ব্যবহার করা যাবে না।

if (sandbox.isFeatureSupported(JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER)) {
    js.provideNamedData("data-1", "Hello Android!".getBytes(StandardCharsets.US_ASCII));
    final String jsCode = "android.consumeNamedDataAsArrayBuffer('data-1').then((value) => { return String.fromCharCode.apply(null, new Uint8Array(value)); });";
    ListenableFuture<String> msg = js.evaluateJavaScriptAsync(jsCode);
    String response = msg.get(5, TimeUnit.SECONDS);
}

ওয়াসম কোড চলছে

WebAssembly (Wasm) কোড provideNamedData(...) API ব্যবহার করে পাস করা যেতে পারে, তারপর কম্পাইল করা হয় এবং স্বাভাবিক পদ্ধতিতে এক্সিকিউট করা হয়, যেমনটি নিচে দেখানো হয়েছে।

final byte[] hello_world_wasm = {
   0x00 ,0x61 ,0x73 ,0x6d ,0x01 ,0x00 ,0x00 ,0x00 ,0x01 ,0x0a ,0x02 ,0x60 ,0x02 ,0x7f ,0x7f ,0x01,
   0x7f ,0x60 ,0x00 ,0x00 ,0x03 ,0x03 ,0x02 ,0x00 ,0x01 ,0x04 ,0x04 ,0x01 ,0x70 ,0x00 ,0x01 ,0x05,
   0x03 ,0x01 ,0x00 ,0x00 ,0x06 ,0x06 ,0x01 ,0x7f ,0x00 ,0x41 ,0x08 ,0x0b ,0x07 ,0x18 ,0x03 ,0x06,
   0x6d ,0x65 ,0x6d ,0x6f ,0x72 ,0x79 ,0x02 ,0x00 ,0x05 ,0x74 ,0x61 ,0x62 ,0x6c ,0x65 ,0x01 ,0x00,
   0x03 ,0x61 ,0x64 ,0x64 ,0x00 ,0x00 ,0x09 ,0x07 ,0x01 ,0x00 ,0x41 ,0x00 ,0x0b ,0x01 ,0x01 ,0x0a,
   0x0c ,0x02 ,0x07 ,0x00 ,0x20 ,0x00 ,0x20 ,0x01 ,0x6a ,0x0b ,0x02 ,0x00 ,0x0b,
};
final String jsCode = "(async ()=>{" +
       "const wasm = await android.consumeNamedDataAsArrayBuffer('wasm-1');" +
       "const module = await WebAssembly.compile(wasm);" +
       "const instance = WebAssembly.instance(module);" +
       "return instance.exports.add(20, 22).toString();" +
       "})()";
// Ensure that the name has not been used before.
js.provideNamedData("wasm-1", hello_world_wasm);
FluentFuture.from(js.evaluateJavaScriptAsync(jsCode))
           .transform(this::println, mainThreadExecutor)
           .catching(Throwable.class, e -> println(e.getMessage()), mainThreadExecutor);
}

জাভাস্ক্রিপ্ট আইসোলেট সেপারেশন

সমস্ত JavaScriptIsolate দৃষ্টান্ত একে অপরের থেকে স্বাধীন এবং কিছু ভাগ করে না। নিম্নলিখিত স্নিপেট ফলাফল

Hi from AAA!5

এবং

Uncaught Reference Error: a is not defined

কারণ " jsTwo " ইনস্ট্যান্সে " jsOne "-এ তৈরি করা বস্তুর কোনো দৃশ্যমানতা নেই।

JavaScriptIsolate jsOne = engine.obtainJavaScriptIsolate();
String jsCodeOne = "let x = 5; function a() { return 'Hi from AAA!'; } a() + x";
JavaScriptIsolate jsTwo = engine.obtainJavaScriptIsolate();
String jsCodeTwo = "a() + x";
FluentFuture.from(jsOne.evaluateJavaScriptAsync(jsCodeOne))
       .transform(this::println, mainThreadExecutor)
       .catching(Throwable.class, e -> println(e.getMessage()), mainThreadExecutor);

FluentFuture.from(jsTwo.evaluateJavaScriptAsync(jsCodeTwo))
       .transform(this::println, mainThreadExecutor)
       .catching(Throwable.class, e -> println(e.getMessage()), mainThreadExecutor);

কোটলিন সমর্থন

Kotlin coroutines-এর সাথে এই Jetpack লাইব্রেরি ব্যবহার করতে, kotlinx-coroutines-guava তে নির্ভরতা যোগ করুন। এটি ListenableFuture এর সাথে একীকরণের অনুমতি দেয়।

dependencies {
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0"
}

Jetpack লাইব্রেরি API গুলিকে এখন একটি করুটিন স্কোপ থেকে কল করা যেতে পারে, যেমনটি নীচে প্রদর্শিত হয়েছে:

// Launch a coroutine
lifecycleScope.launch {
    val jsSandbox = JavaScriptSandbox
            .createConnectedInstanceAsync(applicationContext)
            .await()
    val jsIsolate = jsSandbox.createIsolate()
    val resultFuture = jsIsolate.evaluateJavaScriptAsync("PASS")

    // Await the result
    textBox.text = resultFuture.await()
    // Or add a callback
    Futures.addCallback<String>(
        resultFuture, object : FutureCallback<String?> {
            override fun onSuccess(result: String?) {
                textBox.text = result
            }
            override fun onFailure(t: Throwable) {
                // Handle errors
            }
        },
        mainExecutor
    )
}

কনফিগারেশন পরামিতি

একটি বিচ্ছিন্ন পরিবেশের উদাহরণের অনুরোধ করার সময়, আপনি এটির কনফিগারেশন পরিবর্তন করতে পারেন। কনফিগারেশন পরিবর্তন করতে, IsolateStartupParameters ইনস্ট্যান্সটি JavaScriptSandbox.createIsolate(...) এ পাস করুন।

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