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 कोड में मौजूद आखिरी एक्सप्रेशन का आकलन किया गया नतीजा दिखता है. यह वैल्यू, 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 में मौजूद सारा डेटा, Binder लेन-देन के ज़रिए भेजा जाता है. लेन-देन के साइज़ की सामान्य सीमा, हर उस कॉल पर लागू होती है जो डेटा भेजता है या डेटा दिखाता है.
यह जवाब हमेशा स्ट्रिंग के तौर पर दिखाया जाता है और यह बाइंडर के मुताबिक होता है
लेन-देन के लिए ज़्यादा से ज़्यादा साइज़ की सीमा, अगर
JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
नहीं है
समर्थित हैं. स्ट्रिंग वाली वैल्यू को साफ़ तौर पर JavaScript स्ट्रिंग में बदलना ज़रूरी है. ऐसा न करने पर, खाली स्ट्रिंग दिखती है. अगर JS_FEATURE_PROMISE_RETURN
सुविधा काम करती है, तो JavaScript कोड, String
पर रिज़ॉल्व होने वाला एक Promise रिटर्न कर सकता है.
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 कोड चलाना
provideNamedData(...)
एपीआई का इस्तेमाल करके, WebAssembly (Wasm) कोड को पास किया जा सकता है. इसके बाद, इसे सामान्य तरीके से कंपाइल और एक्ज़ीक्यूट किया जा सकता है, जैसा कि यहां दिखाया गया है.
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 Separation
सभी 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(...)
पर पास करें.
फ़िलहाल, पैरामीटर की मदद से हेप का ज़्यादा से ज़्यादा साइज़ और वैल्यू और गड़बड़ियों का आकलन करने के लिए, ज़्यादा से ज़्यादा साइज़ तय किया जा सकता है.