JavaScript ve WebAssembly'yi yürütme

JavaScript Değerlendirmesi

JavaScriptEngine Jetpack kitaplığı, bir uygulamanın WebView örneği oluşturmadan JavaScript kodunu değerlendirebilirsiniz.

Etkileşimli olmayan JavaScript değerlendirmesi gerektiren uygulamalarda JavaScriptEngine kitaplığının kullanılmasının avantajları şunlardır:

  • WebView örneği ayırmaya gerek olmadığından daha düşük kaynak tüketimi.

  • Bir Service (WorkManager görevi) içinde yapılabilir.

  • Uygulamanın aynı anda birden fazla JavaScript snippet'i çalıştırmasına olanak tanıyan, düşük yükü olan birden fazla yalıtılmış ortam.

  • API çağrısı kullanarak büyük miktarda veri iletme özelliği.

Temel Kullanım

Başlamak için JavaScriptSandbox örneği oluşturun. Bu, devre dışı JavaScript motoruyla bağlantı kurulmasını sağlar.

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

Korumalı alanın yaşam döngüsünün, JavaScript değerlendirmesine ihtiyaç duyan bileşenin yaşam döngüsüyle uyumlu hale getirilmesi önerilir.

Örneğin, korumalı alanı barındıran bir bileşen Activity veya Service olabilir. JavaScript değerlendirmesini kapsamak için tek bir Service kullanılabilir sahip olmayabilir.

Tahsis etme oranı oldukça yüksek olduğu için JavaScriptSandbox örneğini koruma Pahalı. Uygulama başına yalnızca bir JavaScriptSandbox örneğine izin verilir. Bir uygulama,IllegalStateException ikinci JavaScriptSandbox örneği. Ancak birden fazla yürütme ortamı varsa gerekli olduğundan, birden fazla JavaScriptIsolate örneği ayrılabilir.

Artık kullanılmadığında, kaynak boşaltmak için korumalı alan örneğini kapatın. İlgili içeriği oluşturmak için kullanılan JavaScriptSandbox örneği, AutoCloseable arayüzü uygular. , basit engelleme kullanım alanları için "kaynaklarla deneme" kullanımına olanak tanır. Alternatif olarak, JavaScriptSandbox örneğin yaşam döngüsünün barındırma bileşenini bir etkinlik için onStop() geri çağırmasında kapatarak onDestroy() sırasında bir Hizmet:

jsSandbox.close();

JavaScriptIsolate örneği, JavaScript kodunu yürütme bağlamını temsil eder. Gerekirse ayrılabilirler. Böylece farklı kaynaklardan gelen komut dosyaları için zayıf güvenlik sınırları sağlanabilir veya JavaScript doğası gereği tek iş parçacıklı olduğundan eşzamanlı JavaScript yürütme etkinleştirilebilir. Sonraki çağrılar aynı durum aynı durumu paylaştığından bazı veriler oluşturulabilir. ve daha sonra aynı JavaScriptIsolate örneğinde işleyin.

JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();

close() yöntemini çağırarak JavaScriptIsolate öğesini açıkça yayınlayın. JavaScript kodu çalıştıran bir izole örneğin kapatılması (tamamlanmamış bir Future'ye sahip olması) IsolateTerminatedException ile sonuçlanır. İlgili içeriği oluşturmak için kullanılan arka planda temizlenir. aşağıdaki adımları izleyin: JS_FEATURE_ISOLATE_TERMINATION korumalı alan kilitlenmelerini ele alma bölümünü sayfasını ziyaret edin. Aksi takdirde, bekleyen tüm değerlendirmeler tamamlanana kadar temizleme işlemi ertelenir. ya da korumalı alan kapatıldı.

Bir uygulama, şuradan bir JavaScriptIsolate örneği oluşturabilir ve örneğe erişebilir: herhangi bir ileti dizisinde.

Artık uygulama bazı JavaScript kodlarını yürütmeye 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 güzelce biçimlendirilmiş şekilde:

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ğerlendirilmiş sonucunu döndürdüğünü unutmayın. Bu olmalıdır: String JavaScript türü; Aksi takdirde, kitaplık API'si boş bir değer döndürür. JavaScript kodunda return anahtar kelimesi kullanılmamalıdır. Korumalı alan belirli özellikleri ve ek iade türlerini destekler (örneğin, Promise String olarak çözümlenen) 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) başlıklı makalelere göz atın. Bu API'ler, diskteki veya uygulama dizinlerindeki bir dosyadan değerlendirme yapmak için daha uygundur.

Kitaplık, hata ayıklama için kullanılabilecek konsol günlük kaydını da destekler amaçlar. Bu, setConsoleCallback() kullanılarak ayarlanabilir.

Bağlam devam ettiği için kod yükleyebilir ve JavaScriptIsolate'ün kullanım süresi boyunca 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 şu şekilde 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 gereken snippet'in tamamı 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);

Tüm görevlerin ayrılmış olduğundan emin olmak için kaynak serbest bırakılır ve artık kullanılmaz. Korumalı alanı kapatmak, tüm JavaScriptIsolate örneklerinde bekleyen tüm değerlendirmelerin SandboxDeadException ile başarısız olmasına neden olur. JavaScript değerlendirmesi, hata verirse JavaScriptException oluşturulur. Alt sınıflarına başvurma inceleyin.

Korumalı Alan kilitlenmelerini işleme

Tüm JavaScript, JavaScript'inizin dışında, korumalı alana alınmış ayrı bir işlemde yürütülür ana işlemidir. JavaScript kodu, korumalı alan içindeki bu işlemin kilitlenmesine neden olursa (ör. bellek sınırını aşarak) uygulamanın ana işlemi bundan etkilenmez.

Korumalı alan kilitlenmesi, söz konusu korumalı alandaki tüm izolelerin sonlandırılmasına neden olur. Bunun en belirgin belirtisi, tüm değerlendirmelerin IsolateTerminatedException ile başarısız olmaya başlamasıdır. Duruma bağlı olarak SandboxDeadException veya MemoryLimitExceededException gibi daha spesifik istisnalar atılabilir.

Her değerlendirme için ayrı ayrı kilitlenmelerle ilgilenmek her zaman pratik değildir. Ayrıca, bir izole etme işlemi, açık bir şekilde istenen arka plan görevleri veya diğer izole gruplardaki değerlendirmeler nedeniyle yapılan değerlendirmedir. Kilitlenme işleme mantığı, JavaScriptIsolate.addOnTerminatedCallback() kullanılarak geri çağırma işlevi ekleyerek merkezileştirilebilir.

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, korumalı alan uygulamasında farklı özellik grupları kullanılabilir. Bu nedenle, gerekli her özelliği JavaScriptSandbox.isFeatureSupported(...) kullanarak sorgulamanız gerekir. Bu, bu özellikleri temel alan yöntemleri çağırmadan önce özellik durumunu kontrol edin.

Her yerde kullanılamayabilecek JavaScriptIsolate yöntemleri, RequiresFeature ek açıklamayla notlandırılır. Bu sayede, bu çağrıları kodda tespit etmek kolaylaşı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ıyla sınırlı değildir. Özellik desteklenmiyorsa JavaScriptEngine'e gönderilen tüm veriler bir Binder işlemi aracılığıyla gerçekleşir. Genel işlem boyutu sınırı, veri veya işlem aracılığıyla iletilen her çağrı için geçerlidir. verileri döndürür.

Yanıt her zaman dize olarak döndürülür ve JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT desteklenmiyorsa Binder işleminin maksimum boyut sınırına tabidir. Dize olmayan değerler açıkça JavaScript dizesi biçimine dönüştürülmelidir. Aksi takdirde boş bir dize döndürülür. JS_FEATURE_PROMISE_RETURN ise özelliği desteklendiğinde, JavaScript kodu da alternatif olarak bir Promise değeri döndürebilir. String ile çözümleniyor.

JavaScriptIsolate örneğine büyük bayt dizileri iletmek için: provideNamedData(...) API'yi kullanabilir. Bu API'nin kullanımı şunlara bağlı değildir: Bağlayıcı işlem sınırları belirleyin. Her bayt dizisi, benzersiz bir ve tekrar kullanılamayacaktır.

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 Kodu Çalıştırma

WebAssembly (Wasm) kodu, provideNamedData(...) kullanılarak iletilebilir API'den daha sonra aşağıda gösterildiği gibi normal şekilde derlenir ve yürütülür.

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ı Ayırma

Tüm JavaScriptIsolate örnekleri birbirinden bağımsızdır ve hiçbir şey paylaşmaz. Aşağıdaki snippet,

Hi from AAA!5

ve

Uncaught Reference Error: a is not defined

"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 coroutine'leriyle kullanmak için kotlinx-coroutines-guava bağımlılık ekleyin. Bu, ListenableFuture ile entegrasyon sağlar.

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

Jetpack kitaplığı API'leri artık aşağıdaki gibi bir iş parçacığı 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ış ortam örneği isteğinde bulunurken yapılandırma. Yapılandırmayı değiştirmek için IsolateStartupParameters örneğini JavaScriptSandbox.createIsolate(...).

Şu anda parametreler maksimum yığın boyutunun ve maksimum boyutun belirtilmesine izin verir ve hataları kontrol edin.