Ocena JavaScript
Biblioteka Jetpack JavaScriptEngine umożliwia aplikacji oceniać kod JavaScript bez konieczności tworzenia instancji WebView.
W przypadku aplikacji wymagających nieinteraktywnej oceny JavaScriptu za pomocą funkcji Biblioteka JavaScriptEngine ma te zalety:
Niższe zużycie zasobów, ponieważ nie trzeba przydzielić WebView instancji.
Można to zrobić w usłudze (zadanie WorkManager).
Wiele izolowanych środowisk z niskim nakładem pracy, dzięki czemu aplikacja może uruchamianie kilku fragmentów kodu JavaScript jednocześnie.
Możliwość przekazywania dużych ilości danych przy użyciu wywołania interfejsu API.
Podstawowe wykorzystanie
Zacznij od utworzenia instancji JavaScriptSandbox
. To daje
z silnikiem JavaScriptu poza procesem.
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
Zalecamy dostosowanie cyklu życia piaskownicy do cyklu życia który wymaga oceny JavaScriptu.
Na przykład komponent hostujący piaskownicę może być Activity
lub
Service
Do hermetyzacji oceny JavaScript może być używany pojedynczy element Service
dla wszystkich komponentów aplikacji.
Utrzymuj instancję JavaScriptSandbox
, ponieważ jej alokacja jest dość dobra
drogie. Dozwolona jest tylko 1 instancja JavaScriptSandbox
na aplikację. An
Interfejs IllegalStateException
jest zgłaszany, gdy aplikacja próbuje przydzielić
do drugiej instancji JavaScriptSandbox
. Jeśli jednak wiele środowisk wykonawczych
są wymagane, można przydzielić kilka instancji JavaScriptIsolate
.
Gdy instancja piaskownicy nie jest już używana, zamknij ją, aby zwolnić zasoby.
instancji JavaScriptSandbox
implementuje interfejs AutoCloseable
, który
pozwala na korzystanie z funkcji try-with-resources w przypadku prostych przypadków użycia blokujących.
Możesz też sprawdzić, czy cyklem życia instancji JavaScriptSandbox
jest zarządzany przez
komponent hostujący, zamykając go w wywołaniu zwrotnym onStop()
dla aktywności lub
w okresie onDestroy()
za Usługę:
jsSandbox.close();
Instancja JavaScriptIsolate
reprezentuje kontekst wykonywania
kod JavaScript. Mogą być przydzielane w razie potrzeby, zapewniając słabe zabezpieczenia
granic dla skryptów o różnym pochodzeniu lub włączania równoczesnego JavaScriptu
ponieważ JavaScript jest z natury jednowątkowy. Kolejne wywołania do
ta sama instancja ma ten sam stan, dlatego można utworzyć pewne dane
a potem przetwórz je później w tej samej instancji JavaScriptIsolate
.
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
Wyraźnie zwolnij JavaScriptIsolate
, wywołując jego metodę close()
.
Zamykanie izolowanej instancji z kodem JavaScript
(niekompletna wartość Future
) daje IsolateTerminatedException
.
izolowane są później czyszczone w tle, jeśli implementacja
obsługuje JS_FEATURE_ISOLATE_TERMINATION
, jak opisano w
obsługę awarii w piaskownicy.
stronę. W przeciwnym razie czyszczenie zostanie przełożone do czasu, gdy wszystkie oczekujące oceny zostaną
lub piaskownica jest zamknięta.
Aplikacja może utworzyć instancję JavaScriptIsolate
i uzyskać do niej dostęp z poziomu
w dowolnym wątku.
Teraz aplikacja jest gotowa do wykonania kodu 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);
Ten sam fragment kodu JavaScript dobrze sformatowany:
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);
Fragment kodu jest przekazywany jako String
, a wynik jest dostarczany w formacie String
.
Pamiętaj, że wywołanie evaluateJavaScriptAsync()
zwraca ocenę
wyniku ostatniego wyrażenia w kodzie JavaScript. Musi to być
typu String
JavaScript; w przeciwnym razie zwraca pustą wartość.
Kod JavaScript nie powinien zawierać słowa kluczowego return
. Jeśli piaskownica
obsługuje określone funkcje, dodatkowe typy zwrotów (np. Promise
String
) może być możliwe.
Biblioteka obsługuje również ocenę skryptów, które mają postać
AssetFileDescriptor
lub ParcelFileDescriptor
. Zobacz
evaluateJavaScriptAsync(AssetFileDescriptor)
i
evaluateJavaScriptAsync(ParcelFileDescriptor)
, aby uzyskać więcej informacji.
Te interfejsy API lepiej nadają się do oceny z pliku na dysku lub w aplikacji
i katalogów.
Biblioteka obsługuje również logowanie konsoli, które mogą być używane do debugowania
w celach informacyjnych. Możesz to skonfigurować za pomocą narzędzia setConsoleCallback()
.
Ponieważ kontekst się nie powtarza, możesz przesłać kod i wykonać go kilka razy
w ciągu 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);
Oczywiście zmienne też są trwałe, więc można kontynuować fragment z:
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);
Na przykład pełny fragment kodu przydzielający wszystkie niezbędne obiekty wykonanie kodu JavaScript może wyglądać tak:
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);
Zalecamy użycie metody try-with-resources, aby upewnić się, że wszystkie przydzielone zasoby
zasoby są zwolnione i nie są już używane. Zamykanie wyników piaskownicy
we wszystkich oczekujących ocenach we wszystkich instancjach JavaScriptIsolate
, które zakończyły się niepowodzeniem
dzięki SandboxDeadException
. Gdy ocena JavaScript napotyka
wystąpi błąd, zostanie utworzony JavaScriptException
. Odwoływanie się do jej podklas
o bardziej szczegółowe wyjątki.
Postępowanie w przypadku awarii piaskownicy
Cały JavaScript jest wykonywany w osobnym procesie w piaskownicy poza Twoją głównej procedury zgłoszenia. Jeśli kod JavaScript powoduje ten proces w trybie piaskownicy może ulec awarii, np. z powodu wyczerpania limitu pamięci, nie wpłynie na ten proces.
Awaria piaskownicy spowoduje zamknięcie wszystkich izolacji w tej piaskownicy. Najbardziej
Oczywistym objawem jest to, że wszystkie oceny zaczną się niepowodzeniem od
IsolateTerminatedException
W zależności od sytuacji bardziej
określonych wyjątków, takich jak SandboxDeadException
lub
MemoryLimitExceededException
może zostać rzucony.
Postępowanie w przypadku awarii przy każdej indywidualnej ocenie nie zawsze jest praktyczne.
Ponadto izolacja może zakończyć się poza dozwolonym adresem
oceny z powodu zadań w tle lub ocen w innych izolacjach. Wypadek
logikę obsługi można scentralizować, dołączając wywołanie zwrotne za pomocą funkcji
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);
Opcjonalne funkcje piaskownicy
W zależności od bazowej wersji komponentu WebView implementacja piaskownicy może mieć
różne zestawy funkcji. Należy więc sprawdzać wszystkie wymagane
za pomocą JavaScriptSandbox.isFeatureSupported(...)
. To ważne
w celu sprawdzenia stanu funkcji przed jej użyciem.
Metody JavaScriptIsolate
, które mogą nie być dostępne wszędzie, są
opatrzone adnotacjami RequiresFeature
, które ułatwiają ich rozpoznanie,
w celu uzyskania numeru.
Zaliczone parametry
Jeśli JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
to
obsługiwane, żądania oceny wysyłane do mechanizmu JavaScript nie są powiązane
przez limity transakcji powiązanych. Jeśli funkcja nie jest obsługiwana, wszystkie dane do
JavaScript Engine jest realizowany w ramach transakcji Binder. Ogólne
limitu rozmiaru transakcji ma zastosowanie do każdego wywołania, które przekazuje dane lub
zwraca dane.
Odpowiedź jest zawsze zwracana jako ciąg znaków i podlega funkcji Binder
maksymalny rozmiar transakcji, jeśli
JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
to nie
obsługiwane. Wartości inne niż ciągi muszą zostać jawnie przekonwertowane na ciąg znaków JavaScript
w przeciwnym razie zwracany jest pusty ciąg. Jeśli JS_FEATURE_PROMISE_RETURN
jest obsługiwana, kod JavaScript może alternatywnie zwrócić obietnicę
do String
.
W przypadku przekazywania tablic o dużych bajtach do instancji JavaScriptIsolate
:
może używać interfejsu API provideNamedData(...)
. Użycie tego interfejsu API nie jest związane z
limity transakcji Binder. Każda tablica bajtów musi być przekazywana za pomocą unikalnego identyfikatora
identyfikatora, którego nie można ponownie użyć.
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);
}
Działający kod Wasm
Kod WebAssembly (Wasm) można przekazywać za pomocą funkcji provideNamedData(...)
API, a potem skompiluj i wykonaj w zwykły sposób, jak pokazano poniżej.
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);
}
Separacja odseparowania JavaScriptu
Wszystkie instancje JavaScriptIsolate
są od siebie niezależne i nie
udostępniać czegokolwiek. Ten fragment kodu daje wynik
Hi from AAA!5
i
Uncaught Reference Error: a is not defined
ponieważ instancja „jsTwo
” nie ma widoczności obiektów utworzonych w
„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);
Pomoc Kotlin
Aby używać tej biblioteki Jetpack z współrzędnymi Kotlin, dodaj zależność do
kotlinx-coroutines-guava
Umożliwia to integrację z
ListenableFuture
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0"
}
Interfejsy API biblioteki Jetpack można teraz wywoływać z zakresu współprogramu, poniżej:
// 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
)
}
Parametry konfiguracji
W przypadku żądania instancji izolowanego środowiska możesz dostosować jej
konfiguracji. Aby dostosować konfigurację, przekaż
IsolateStartupParameters, aby
JavaScriptSandbox.createIsolate(...)
.
Obecnie parametry pozwalają określić maksymalny i maksymalny rozmiar stosu zwraca wartości i błędy.