JavaScript और WebAssembly एक्ज़ीक्यूट करना

JavaScript मूल्यांकन

Jetpack लाइब्रेरी JavaScriptEngine ऐप्लिकेशन को इंस्टॉल करने का तरीका वेबव्यू इंस्टेंस बनाए बिना JavaScript कोड का आकलन करें.

गैर-इंटरैक्टिव JavaScript मूल्यांकन की ज़रूरत वाले ऐप्लिकेशन के लिए, JavaScriptEngine लाइब्रेरी के ये फ़ायदे हैं:

  • कम संसाधन इस्तेमाल होता है, क्योंकि इसके लिए वेबव्यू तय करने की ज़रूरत नहीं होती इंस्टेंस.

  • किसी सेवा (WorkManager टास्क) में ऐसा किया जा सकता है.

  • कम ओवरहेड के साथ कई आइसोलेटेड एनवायरमेंट, जिनकी वजह से ऐप्लिकेशन कई JavaScript स्निपेट को साथ-साथ चलाना.

  • एपीआई कॉल का इस्तेमाल करके, ज़्यादा डेटा भेजने की सुविधा.

बुनियादी इस्तेमाल

शुरू करने के लिए, JavaScriptSandbox का इंस्टेंस बनाएं. इसका मतलब है प्रोसेस से बाहर के JavaScript इंजन से कनेक्ट करता है.

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

हमारा सुझाव है कि सैंडबॉक्स के लाइफ़साइकल को जिसके लिए JavaScript की जांच करना ज़रूरी है.

उदाहरण के लिए, सैंडबॉक्स को होस्ट करने वाला कॉम्पोनेंट Activity या Service. JavaScript इवैलुएशन को एनकैप्सुलेट करने के लिए, सिंगल Service का इस्तेमाल किया जा सकता है ऐप्लिकेशन के सभी कॉम्पोनेंट के लिए उपलब्ध है.

JavaScriptSandbox इंस्टेंस को बनाए रखें, क्योंकि इसका बंटवारा सही तरीके से किया गया है महंगा. हर ऐप्लिकेशन के लिए, सिर्फ़ एक JavaScriptSandbox इंस्टेंस की अनुमति है. अगर आप IllegalStateException जब कोई अनुप्रयोग किसी अनुप्रयोग का दूसरा JavaScriptSandbox इंस्टेंस. हालांकि, अगर एक से ज़्यादा एनवायरमेंट होने पर होते हैं, तो कई JavaScriptIsolate इंस्टेंस असाइन किए जा सकते हैं.

जब इसका इस्तेमाल नहीं किया जाता, तो संसाधनों को खाली करने के लिए सैंडबॉक्स इंस्टेंस बंद कर दें. कॉन्टेंट बनाने JavaScriptSandbox इंस्टेंस एक AutoCloseable इंटरफ़ेस लागू करता है, जो ब्लॉक करने के आसान उदाहरणों के लिए, संसाधनों की मदद से कोशिश करने की सुविधा देता है. इसके अलावा, पक्का करें कि JavaScriptSandbox इंस्टेंस लाइफ़साइकल को मैनेज किया जा रहा हो होस्ट करने वाले कॉम्पोनेंट को किसी गतिविधि के लिए, onStop() कॉलबैक में बंद करता है या onDestroy() के दौरान किसी सेवा के लिए:

jsSandbox.close();

JavaScriptIsolate इंस्टेंस, एक्ज़ीक्यूट करने के लिए कॉन्टेक्स्ट दिखाता है JavaScript कोड. ज़रूरत पड़ने पर इन्हें बांटा जा सकता है, ताकि कम सुरक्षा मिल सके अलग-अलग ऑरिजिन की स्क्रिप्ट के लिए सीमाएं या एक साथ कई JavaScript को चालू करना क्योंकि JavaScript, प्रकृति के हिसाब से सिंगल-थ्रेड होता है. बाद में किए जाने वाले कॉल समान इंस्टेंस एक ही स्थिति में है, इसलिए कुछ डेटा बनाया जा सकता है पहले और फिर बाद में JavaScriptIsolate के उसी इंस्टेंस में उसे प्रोसेस करें.

JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();

JavaScriptIsolate के close() तरीके को कॉल करके साफ़ तौर पर रिलीज़ करें. JavaScript कोड चलाने वाले आइसोलेटेड इंस्टेंस को बंद करना (Future में अधूरे होने की वजह से) IsolateTerminatedException में नतीजे मिलते हैं. कॉन्टेंट बनाने आइसोलेटेड को बाद में बैकग्राउंड में हटा दिया जाता है. ऐसा तब किया जाता है, जब JS_FEATURE_ISOLATE_TERMINATION का इस्तेमाल करता है, जैसा कि बाद में, सैंडबॉक्स क्रैश को हैंडल करना सेक्शन करें. ऐसा न करने पर, क्लीनअप की प्रोसेस तब तक टाल दी जाती है, जब तक सभी बकाया इवैलुएशन को पूरा नहीं कर लिया जाता पूरा हो जाता है या सैंडबॉक्स बंद हो जाता है.

ऐप्लिकेशन यहां से JavaScriptIsolate इंस्टेंस बना सकता है और उसे ऐक्सेस कर सकता है कोई भी थ्रेड.

अब ऐप्लिकेशन कुछ JavaScript कोड चलाने के लिए तैयार है:

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);

अच्छी तरह से फ़ॉर्मैट किया गया समान JavaScript स्निपेट:

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() को कॉल करने पर, आकलन किया गया नतीजा मिलता है का नतीजा मिलेगा. इसे का JavaScript String टाइप; ऐसा न होने पर, लाइब्रेरी एपीआई एक खाली वैल्यू दिखाता है. JavaScript कोड में return कीवर्ड का इस्तेमाल नहीं किया जाना चाहिए. अगर सैंडबॉक्स कुछ सुविधाओं के साथ-साथ, दूसरी तरह के प्रॉडक्ट भी लौटाए जा सकते हैं. जैसे, Promise जो String में बदल जाता है) हो सकता है.

यह लाइब्रेरी ऐसी स्क्रिप्ट के मूल्यांकन में भी सहायता करती है जो AssetFileDescriptor या ParcelFileDescriptor. यहां जाएं: evaluateJavaScriptAsync(AssetFileDescriptor) और ज़्यादा जानकारी के लिए, evaluateJavaScriptAsync(ParcelFileDescriptor). ये एपीआई, डिस्क या ऐप्लिकेशन में मौजूद किसी फ़ाइल से आकलन करने के लिए बेहतर होते हैं डायरेक्ट्री में जा सकते हैं.

इस लाइब्रेरी में, कंसोल लॉग करने की सुविधा भी काम करती है. इसका इस्तेमाल डीबग करने के लिए किया जा सकता है के मकसद से बनाया गया है. इसे 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);

उदाहरण के लिए, सभी ज़रूरी ऑब्जेक्ट असाइन करने के लिए पूरा स्निपेट और किसी JavaScript कोड को एक्ज़ीक्यूट करने पर ऐसा दिख सकता है:

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 बन गया है. इसके सब-क्लास देखें खास अपवादों के लिए.

सैंडबॉक्स के क्रैश होने की समस्याओं को मैनेज करना

सभी JavaScript को आवेदन की मुख्य प्रक्रिया है. अगर JavaScript कोड की वजह से सैंडबॉक्स की यह प्रोसेस होती है हो सकता है कि वह क्रैश हो जाए. उदाहरण के लिए, खत्म होने की वजह से ऐप्लिकेशन का मुख्य इसका कोई असर नहीं होगा.

एक सैंडबॉक्स क्रैश होने से उस सैंडबॉक्स में मौजूद सभी आइसोलेटेड बंद हो जाएंगे. सबसे ज़्यादा इसका साफ़ लक्षण यह है कि सभी आकलन सही से काम नहीं करेंगे 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);

सैंडबॉक्स की वैकल्पिक सुविधाएं

इस्तेमाल किए जा रहे वेबव्यू के वर्शन के आधार पर, सैंडबॉक्स को लागू करने के लिए सुविधाओं के अलग-अलग सेट उपलब्ध हैं. इसलिए, यह ज़रूरी है कि आप सुविधा को चालू करने के लिए, JavaScriptSandbox.isFeatureSupported(...) का इस्तेमाल करें. यह ज़रूरी है ताकि इन सुविधाओं का इस्तेमाल करके कॉल करने से पहले, सुविधा का स्टेटस देखा जा सके.

ऐसा हो सकता है कि JavaScriptIsolate के ये तरीके हर जगह उपलब्ध न हों एनोटेशन के साथ RequiresFeature एनोटेशन जोड़ा गया हो. इससे, उन्हें पहचानने में आसानी होती है कॉल शामिल हैं.

पासिंग पैरामीटर

अगर JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT समर्थित नहीं, JavaScript इंजन को भेजे गए मूल्यांकन अनुरोध बाइंडर ट्रांज़ैक्शन सीमाओं के मुताबिक काम करते हैं. यदि सुविधा समर्थित नहीं है, तो JavaScriptEngine को बाइंडर ट्रांज़ैक्शन के ज़रिए इस्तेमाल करता है. सामान्य लेन-देन के साइज़ की सीमा, हर उस कॉल पर लागू होती है जो डेटा पास करता है या डेटा दिखाता है.

यह जवाब हमेशा स्ट्रिंग के तौर पर दिखाया जाता है और यह बाइंडर के मुताबिक होता है लेन-देन के लिए ज़्यादा से ज़्यादा साइज़ की सीमा, अगर JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT नहीं है समर्थित हैं. नॉन-स्ट्रिंग वैल्यू को साफ़ तौर पर JavaScript स्ट्रिंग में बदला जाना चाहिए ऐसा न होने पर एक खाली स्ट्रिंग दिखती है. अगर JS_FEATURE_PROMISE_RETURN सुविधा का इस्तेमाल नहीं किया जा सकता, तो हो सकता है कि JavaScript कोड एक प्रॉमिस कोड दिखाए. String पर हल कर रहा है.

JavaScriptIsolate इंस्टेंस में बड़ी बाइट अरे पास करने के लिए, provideNamedData(...) एपीआई का इस्तेमाल किया जा सकता है. इस एपीआई का इस्तेमाल इसके लिए ज़रूरी नहीं है बाइंडर लेन-देन की सीमाएं तय करते हैं. हर बाइट कलेक्शन को एक यूनीक वैल्यू का इस्तेमाल करके पास किया जाना चाहिए ऐसा आइडेंटिफ़ायर जिसका फिर से इस्तेमाल नहीं किया जा सकता.

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);
}

Wasm कोड चलाया जा रहा है

WebAssembly (Wasm) कोड को provideNamedData(...) का इस्तेमाल करके पास किया जा सकता है एपीआई को इकट्ठा करने के बाद उसे सामान्य तरीके से कंपाइल करके लागू किया जाता है, जैसा कि नीचे बताया गया है.

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);
}

JavaScript आइसोलेट सेपरेशन

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 सहायता

Kotlin कोरूटीन के साथ इस Jetpack लाइब्रेरी का इस्तेमाल करने के लिए, kotlinx-coroutines-guava. इसकी मदद से, इंटिग्रेशन के लिए ListenableFuture.

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

Jetpack लाइब्रेरी एपीआई को अब कोरूटीन के स्कोप से कॉल किया जा सकता है. ऐसा इस तरह से किया जा सकता है नीचे बताया गया है:

// 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(...).

फ़िलहाल, पैरामीटर की मदद से, ज़्यादा से ज़्यादा हीप साइज़ और साइज़ के बारे में जानकारी दी जा सकती है का इस्तेमाल किया जा सकता है.