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şimsiz JavaScript değerlendirmesi gerektiren uygulamalarda JavaScriptEngine kitaplığı aşağıdaki avantajlara sahiptir:

  • WebView tahsis etmeye gerek olmadığından daha düşük kaynak tüketimi kullanır.

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

  • Düşük ek yük sunan birden çok izole ortam, uygulamanın birkaç JavaScript snippet'ini aynı anda çalıştırabilir.

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

Temel Kullanım

Başlamak için JavaScriptSandbox öğesinin bir örneğini 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ü, gereken bir JavaScript bileşenidir.

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

Tahsis etme düzeyi 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, kaynakları boşa çıkarmak 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, yürütülecek bir bağlamı temsil eder JavaScript kodu. Gerektiğinde bunlara ayrılabilir ve güvenlik zayıftır farklı kaynaklı komut dosyaları için sınırlar veya eşzamanlı JavaScript'i etkinleştirme yapısı gereği tek iş parçacıklı olduğundan yürütmelidir. 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 izole bir örneği kapatma (eksik bir Future öğesi yoksa) 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üzel biçimlendirilmiş:

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, sonuç olarak ise String olarak iletilir. evaluateJavaScriptAsync() çağrısının, değerlendirilen işlevinin sonucu. 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ıkta şu tür e-postalara sahip komut dosyalarının değerlendirilmesi de desteklenir. AssetFileDescriptor veya ParcelFileDescriptor. Görüntüleyin evaluateJavaScriptAsync(AssetFileDescriptor) ve Ayrıntılı bilgi için evaluateJavaScriptAsync(ParcelFileDescriptor). Bu API'ler, diskteki veya uygulama içindeki bir dosyadan değerlendirme yapmaya daha uygundur. dizin oluşturabilirsiniz.

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

Bağlam devam ettiği için kod yükleyip birkaç kez çalıştırabilirsiniz JavaScriptIsolate ömrü boyunca:

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ıdır. Dolayısıyla, önceki şununla snippet:

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 için tam snippet ve JavaScript kodu çalıştırmak 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 doğru bir şekilde toplandığından emin olmak için kaynak serbest bırakılır ve artık kullanılmaz. Korumalı alan sonuçları kapatılıyor JavaScriptIsolate örneğin tamamı başarısız olan bekleyen tüm değerlendirmelerde SandboxDeadException ile. JavaScript değerlendirmesi, hata verirse JavaScriptException oluşturulur. Alt sınıflarına başvurma inceleyin.

Korumalı Alan Kilitlenmelerini Yönetme

Tüm JavaScript, JavaScript'inizin dışında, korumalı alana alınmış ayrı bir işlemde yürütülür ana işlemi. Korumalı alana alınmış bu işleme JavaScript kodu neden oluyorsa kilitlenebilir. Örneğin, bellek sınırını tükettiğinde uygulamanın ana bu değişiklikten etkilenmeyecektir.

Korumalı alan kilitlenmesi, söz konusu korumalı alandaki tüm izolelerin sonlandırılmasına neden olur. En Bunun bariz bir belirtisi, tüm değerlendirmelerin IsolateTerminatedException. Koşullara bağlı olarak daha fazla SandboxDeadException veya MemoryLimitExceededException atılabilir.

Her bir değerlendirmede kilitlenmeleri ele almak 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. Çökme işleme mantığı, API'leri kullanan bir geri çağırma eklenerek 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);
ile izolasyonu kapatırken tetikleyin.

İsteğe Bağlı Korumalı Alan Özellikleri

Temel WebView sürümüne bağlı olarak bir korumalı alan uygulaması farklı özellikler bulunuyor. Bu yüzden, gerekli her bir özelliğini kullanarak JavaScriptSandbox.isFeatureSupported(...) öğrenebilirsiniz. Bu, bu özellikleri temel alan yöntemleri çağırmadan önce özellik durumunu kontrol edin.

Her yerde kullanılamayabilecek JavaScriptIsolate yöntem RequiresFeature ek açıklamasıyla işaretlenmiştir ve bu şekilde, bunlar kolayca ayırt edilebilir çağrısının nasıl yapılacağını göstereceğim.

Parametreleri Geçirme

JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT destekleniyorsa JavaScript motoruna gönderilen değerlendirme istekleri bağlı değil tarafından uygulanır. Özellik desteklenmiyorsa JavaScriptEngine, bir Bağlayıcı 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 Bağlayıcı işlem maksimum boyut sınırı: JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT değil desteklenir. Dize olmayan değerler açık bir şekilde JavaScript Dizesine 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 Kodunu Ç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 paylaşmanın iyi bir yoludur. Aşağıdaki snippet,

Hi from AAA!5

ve

Uncaught Reference Error: a is not defined

çünkü ”jsTwo” örneği şurada oluşturulan nesnelerin görünürlüğüne sahip değildir: "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 Desteği

Bu Jetpack kitaplığını Kotlin eş yordamlarıyla kullanmak için kotlinx-coroutines-guava. Bu sayede, Google Analytics 4'te ListenableFuture

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

Jetpack kitaplığı API'leri artık eş düzey bir kapsamdan çağrılabilir: gösterilmektedir:

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