JavaScript Değerlendirmesi
Jetpack kitaplığı JavaScriptEngine, bir uygulamanın Web Görünümü örneği oluşturmadan JavaScript kodunu değerlendirmesi için bir yol sağlar.
Etkileşimli olmayan JavaScript değerlendirmesi gerektiren uygulamalarda JavaScriptEngine kitaplığının kullanılması aşağıdaki avantajlara sahiptir:
Web Görünümü örneği tahsis edilmesine gerek olmadığından daha düşük kaynak tüketimi.
Hizmet (WorkManager görevi) içinde yapılabilir.
Uygulamanın düşük ek yüke sahip birkaç JavaScript snippet'ini aynı anda çalıştırabilmesini sağlayan çok sayıda yalıtılmış ortam.
API çağrısı kullanarak büyük miktarlarda veri iletme özelliği.
Temel Kullanım
Başlamak için JavaScriptSandbox
örneği oluşturun. Bu, işlem olmayan JavaScript motoruyla bir bağlantıyı temsil eder.
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
Korumalı alanın yaşam döngüsünü, JavaScript değerlendirmesi gereken bileşenin yaşam döngüsüyle uyumlu hale getirmeniz önerilir.
Örneğin, korumalı alanı barındıran bileşen bir Activity
veya Service
olabilir. Tüm uygulama bileşenleri için JavaScript değerlendirmesini kapsamak amacıyla tek bir Service
kullanılabilir.
Ayırma işlemi oldukça pahalı olduğu için JavaScriptSandbox
örneğini koruyun. Uygulama başına yalnızca bir JavaScriptSandbox
örneğine izin verilir. Bir uygulama ikinci bir JavaScriptSandbox
örneği ayırmaya çalıştığında IllegalStateException
yayınlanır. Bununla birlikte, birden fazla yürütme ortamı gerekirse birkaç JavaScriptIsolate
örneği ayrılabilir.
Artık kullanılmadığında kaynakları boşaltmak için korumalı alan örneğini kapatın. JavaScriptSandbox
örneği, basit engelleme kullanım alanları için kaynakları deneme kullanımına olanak tanıyan bir AutoCloseable
arayüzü uygular.
Alternatif olarak, JavaScriptSandbox
örnek yaşam döngüsünün barındırma bileşeni tarafından yönetildiğinden emin olun ve bunu bir Etkinlik için onStop()
geri çağırmasında veya bir Hizmet için onDestroy()
sırasında kapatın:
jsSandbox.close();
JavaScriptIsolate
örneği, JavaScript kodunu yürütmek için kullanılan bağlamı temsil eder. JavaScript yapısı tek iş parçacıklı olduğundan, gerektiğinde farklı kaynaklara sahip komut dosyaları için zayıf güvenlik sınırları sağlayarak veya eşzamanlı JavaScript yürütme işlemini mümkün kılarak atanabilirler. Aynı örneğe yapılan sonraki çağrılar aynı durumu paylaşır. Bu nedenle, önce bazı veriler oluşturup daha sonra aynı JavaScriptIsolate
örneğinde bu verileri işleyebilirsiniz.
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
JavaScriptIsolate
yöntemini close()
yöntemini çağırarak açıkça serbest bırakın.
JavaScript kodu çalıştıran bir izole örneğinin kapatılması (eksik bir Future
olması) IsolateTerminatedException
ile sonuçlanır. Uygulama, JS_FEATURE_ISOLATE_TERMINATION
özelliğini destekliyorsa bu sayfanın sonraki kısımlarında yer alan korumalı alan kilitlenmelerini işleme bölümünde açıklandığı gibi izolasyon daha sonra arka planda temizlenir. Aksi takdirde, bekleyen tüm değerlendirmeler tamamlanana veya korumalı alan kapatılana kadar temizleme işlemi ertelenir.
Bir uygulama, herhangi bir iş parçacığından JavaScriptIsolate
örneği oluşturup bu örneğe erişebilir.
Artık uygulama bazı JavaScript kodları çalıştırmaya hazırdır:
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);
Aynı JavaScript snippet'i düzgün şekilde biçimlendirilmiştir:
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);
Kod snippet'i String
olarak iletilir ve sonuç String
olarak yayınlanır.
evaluateJavaScriptAsync()
çağrısının, JavaScript kodundaki son ifadenin değerlendirilen sonucunu döndürdüğünü unutmayın. Bu, JavaScript String
türünde olmalıdır. Aksi takdirde, Library API boş bir değer döndürür.
JavaScript kodu, return
anahtar kelimesini kullanmamalıdır. Korumalı alan belirli özellikleri destekliyorsa ek döndürme türleri (örneğin, String
'a çözümleyen bir Promise
) mümkün olabilir.
Kitaplık, AssetFileDescriptor
veya ParcelFileDescriptor
biçimindeki komut dosyalarının değerlendirilmesini de destekler. Daha fazla bilgi için evaluateJavaScriptAsync(AssetFileDescriptor)
ve evaluateJavaScriptAsync(ParcelFileDescriptor)
sayfalarını inceleyin.
Bu API'ler diskteki veya uygulama dizinlerindeki bir dosyadan değerlendirme yapmaya daha uygundur.
Kitaplık, hata ayıklama amacıyla kullanılabilecek konsol günlük kaydını da destekler. Bu özellik, setConsoleCallback()
kullanılarak ayarlanabilir.
Bağlam devam ettiğinden, JavaScriptIsolate
kullanım ömrü boyunca kod yükleyebilir ve kodu birkaç kez çalıştırabilirsiniz:
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);
Elbette, değişkenler de kalıcı olduğundan önceki snippet'e şunlarla devam edebilirsiniz:
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);
Örneğin, gerekli tüm nesneleri ayırmak ve bir JavaScript kodunu yürütmek için kullanılan tam snippet aşağıdaki gibi görünebilir:
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);
Ayrılan tüm kaynakların serbest bırakıldığından ve artık kullanılmadığından emin olmak için kaynakları deneyerek kullanmanız önerilir. Korumalı alanın kapatılması, SandboxDeadException
ile başarısız olan tüm JavaScriptIsolate
örneklerinde bekleyen tüm değerlendirmelere neden olur. JavaScript değerlendirmesi bir hatayla karşılaştığında JavaScriptException
oluşturulur. Daha spesifik istisnalar için
alt sınıflarına bakın.
Korumalı Alan Kilitlenmelerini Yönetme
Tüm JavaScript kodları, uygulamanızın ana işleminden uzakta, korumalı alana alınmış ayrı bir işlemde yürütülür. JavaScript kodu, korumalı alana alınan bu işlemin (örneğin, bir bellek sınırını tüketerek) kilitlenmesine neden olursa uygulamanın ana işlemi etkilenmez.
Korumalı alan kilitlenmesi, bu korumalı alandaki tüm izolasyonların sonlandırılmasına neden olur. Bunun en bariz belirtisi, IsolateTerminatedException
ile tüm değerlendirmelerin başarısız olmaya başlamasıdır. Koşullara bağlı olarak SandboxDeadException
veya MemoryLimitExceededException
gibi daha spesifik istisnalar tanınabilir.
Her bir değerlendirme için kilitlenmeleri ele almak her zaman pratik olmaz.
Dahası, diğer izolasyonlardaki arka plan görevleri veya değerlendirmeler nedeniyle bir izolasyon açık bir şekilde talep edilen değerlendirmenin dışında sonlandırılabilir. Kilitlenme işleme mantığı, JavaScriptIsolate.addOnTerminatedCallback()
ile bir geri çağırma eklenerek merkezi hale getirilebilir.
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);
İsteğe Bağlı Korumalı Alan Özellikleri
Temel WebView sürümüne bağlı olarak, bir korumalı alan uygulamasında farklı özellik grupları bulunabilir. Bu nedenle, gerekli her özelliği JavaScriptSandbox.isFeatureSupported(...)
kullanarak sorgulamanız gerekir. Bu özellikleri temel alan yöntemler çağırmadan önce özellik durumunu kontrol etmek önemlidir.
Her yerde kullanılamayabilecek JavaScriptIsolate
yöntemlerine RequiresFeature
ek açıklaması eklenerek kod içinde bu çağrıların tespit edilmesini kolaylaştırır.
Parametreleri Geçirme
JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
destekleniyorsa JavaScript motoruna gönderilen değerlendirme istekleri bağlayıcı işlem sınırlarına bağlı olmaz. Özellik desteklenmiyorsa JavaScriptEngine'e gönderilen tüm veriler bir Bağlayıcı işlemi aracılığıyla gerçekleşir. Genel işlem boyutu sınırı, veri aktaran veya veri döndüren her çağrı için geçerlidir.
Yanıt her zaman Dize olarak döndürülür ve JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
desteklenmiyorsa Bağlayıcı işlemi maksimum boyut sınırına tabidir. Dize olmayan değerler açıkça bir JavaScript Dizesine dönüştürülmelidir. Aksi takdirde boş bir dize döndürülür. JS_FEATURE_PROMISE_RETURN
özelliği destekleniyorsa JavaScript kodu, alternatif olarak String
ile çözümlenen bir Promise döndürebilir.
Büyük bayt dizilerini JavaScriptIsolate
örneğine geçirmek için provideNamedData(...)
API'yi kullanabilirsiniz. Bu API'nin kullanımı, Bağlayıcı işlem sınırlarına tabi değildir. Her bayt dizisi, yeniden kullanılamayan benzersiz bir tanımlayıcı kullanılarak iletilmelidir.
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 Kodunu Çalıştırma
WebAssembly (Wasm) kodu, provideNamedData(...)
API kullanılarak iletilebilir, ardından aşağıda gösterildiği gibi normal şekilde derlenip yürütülebilir.
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 Yalıtımı
Tüm JavaScriptIsolate
örnekleri birbirinden bağımsızdır ve hiçbir şey paylaşmaz. Aşağıdaki snippet ile
Hi from AAA!5
ve
Uncaught Reference Error: a is not defined
çünkü "jsTwo
" örneği, "jsOne
" içinde oluşturulan nesneleri göremez.
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 Desteği
Bu Jetpack kitaplığını Kotlin eş yordamlarıyla kullanmak için kotlinx-coroutines-guava
'e bir bağımlılık ekleyin. Bu, ListenableFuture
ile entegrasyona olanak tanır.
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0"
}
Jetpack kitaplığı API'leri artık aşağıda gösterildiği gibi, eş yordam kapsamından çağrılabilir:
// 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
)
}
Yapılandırma Parametreleri
Yalıtılmış bir ortam örneği isterken yapılandırmasını değiştirebilirsiniz. Yapılandırmayı düzenlemek için IsolateStartupParameters örneğini JavaScriptSandbox.createIsolate(...)
'e iletin.
Şu anda parametreler, değerlendirme döndürme değerleri ve hataları için maksimum yığın boyutunun ve maksimum boyutun belirtilmesine olanak tanımaktadır.