JavaScript-Evaluierung
Die Jetpack-Bibliothek JavaScriptEngine bietet einer Anwendung die Möglichkeit, JavaScript-Code evaluieren, ohne eine WebView-Instanz zu erstellen.
Verwenden Sie für Anwendungen, die eine nicht interaktive JavaScript-Auswertung erfordern, den Die JavaScriptEngine-Bibliothek bietet folgende Vorteile:
Geringerer Ressourcenverbrauch, da keine WebView zugewiesen werden muss Instanz.
Kann in einem Service (WorkManager-Aufgabe) ausgeführt werden.
Mehrere isolierte Umgebungen mit geringem Aufwand, sodass die Anwendung mehrere JavaScript-Snippets gleichzeitig ausführen.
Fähigkeit, große Datenmengen mithilfe eines API-Aufrufs zu übergeben.
Grundlegende Nutzung
Erstellen Sie zuerst eine Instanz von JavaScriptSandbox
. Dies stellt eine
Verbindung zur Out-of-Process-JavaScript-Engine.
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
Es empfiehlt sich, den Lebenszyklus der Sandbox auf den Lebenszyklus der Komponente, die JavaScript-Auswertung benötigt.
Beispielsweise kann eine Komponente, die die Sandbox hostet, ein Activity
oder ein
Service
. Es kann eine einzelne Service
verwendet werden, um die JavaScript-Auswertung zu kapseln
für alle Anwendungskomponenten.
JavaScriptSandbox
-Instanz beibehalten, da ihre Zuweisung recht erfolgt
teuer sein. Pro Anwendung ist nur eine JavaScriptSandbox
-Instanz zulässig. Eine
IllegalStateException
wird ausgelöst, wenn eine Anwendung versucht, einen
zweite JavaScriptSandbox
-Instanz. Wenn jedoch mehrere Ausführungsumgebungen
erforderlich sind, können mehrere JavaScriptIsolate
-Instanzen zugewiesen werden.
Wenn sie nicht mehr verwendet wird, schließen Sie die Sandbox-Instanz, um Ressourcen freizugeben. Die
Die JavaScriptSandbox
-Instanz implementiert eine AutoCloseable
-Schnittstelle, die
ermöglicht das Testen mit Ressourcen für einfache blockierende Anwendungsfälle.
Alternativ können Sie dafür sorgen, dass der Lebenszyklus der JavaScriptSandbox
-Instanz von der
der Hosting-Komponente und schließen Sie sie im onStop()
-Callback für eine Aktivität oder
während onDestroy()
für einen Dienst:
jsSandbox.close();
Eine JavaScriptIsolate
-Instanz stellt einen Kontext zum Ausführen
JavaScript-Code. Sie können bei Bedarf zugewiesen werden und bieten so schwache Sicherheit.
Grenzen für Skripts unterschiedlichen Ursprungs oder die Aktivierung gleichzeitiger JavaScripts
ausgeführt wird, da JavaScript von Natur aus Single-Threaded ist. Nachfolgende Aufrufe von
dieselbe Instanz den gleichen Status hat, daher ist es möglich, Daten zu erstellen,
und später in derselben Instanz von JavaScriptIsolate
verarbeiten.
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
Geben Sie JavaScriptIsolate
explizit frei, indem Sie die zugehörige Methode close()
aufrufen.
Schließen einer Isolationsinstanz, auf der JavaScript-Code ausgeführt wird
(ein unvollständiges Future
) führt zu einem IsolateTerminatedException
. Die
'Isolierung' wird anschließend im Hintergrund bereinigt, wenn die Implementierung
unterstützt JS_FEATURE_ISOLATE_TERMINATION
, wie in den
Umgang mit Sandbox-Abstürzen weiter unten.
Seite. Andernfalls wird die Bereinigung verschoben, bis alle ausstehenden Bewertungen abgeschlossen sind
abgeschlossen oder die Sandbox geschlossen wird.
Eine Anwendung kann eine JavaScriptIsolate
-Instanz erstellen und auf sie zugreifen aus
jedem Thread.
Jetzt kann die Anwendung JavaScript-Code ausführen:
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);
Dasselbe JavaScript-Snippet gut formatiert:
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);
Das Code-Snippet wird als String
übergeben und das Ergebnis als String
geliefert.
Beachten Sie, dass der Aufruf von evaluateJavaScriptAsync()
die bewertete
Ergebnis des letzten Ausdrucks im JavaScript-Code. Dies muss
des JavaScript-Typs String
; Andernfalls gibt die Library API einen leeren Wert zurück.
Im JavaScript-Code darf kein return
-Keyword verwendet werden. Wenn die Sandbox
bestimmte Funktionen und zusätzliche Rückgabetypen unterstützt (z. B. ein Promise
)
der zu String
aufgelöst wird, möglich sein.
Die Bibliothek unterstützt auch die Auswertung von Skripts in Form eines
AssetFileDescriptor
oder ParcelFileDescriptor
. Weitere Informationen finden Sie unter
evaluateJavaScriptAsync(AssetFileDescriptor)
und
evaluateJavaScriptAsync(ParcelFileDescriptor)
.
Diese APIs eignen sich besser für die Auswertung über eine Datei auf der Festplatte oder in der App.
Verzeichnisse enthalten.
Die Bibliothek unterstützt auch Konsolen-Logging, das für die Fehlerbehebung verwendet werden kann
zu verstehen. Dies kann mit setConsoleCallback()
eingerichtet werden.
Da der Kontext bestehen bleibt, können Sie Code hochladen und mehrmals ausführen
während der Lebensdauer von 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);
Natürlich sind die Variablen auch persistent, sodass Sie Snippet mit:
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);
Zum Beispiel das komplette Snippet für die Zuordnung aller notwendigen Objekte und kann das Ausführen eines JavaScript-Codes wie folgt aussehen:
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);
Wir empfehlen Ihnen, Ressourcen zum Testen zu nutzen, um sicherzustellen, dass alle
Ressourcen freigegeben und nicht mehr verwendet werden. Sandbox-Ergebnisse schließen
in allen ausstehenden Bewertungen in allen JavaScriptIsolate
Instanzen fehlschlagen
mit einem SandboxDeadException
. Wenn bei der JavaScript-Bewertung
Fehler ist, wird ein JavaScriptException
erstellt. Sehen Sie sich die zugehörigen abgeleiteten Klassen an.
für spezifischere Ausnahmen.
Umgang mit Sandbox-Abstürzen
Das gesamte JavaScript wird in einem separaten Sandbox-Prozess außerhalb der Hauptprozess der Anwendung. Wenn der JavaScript-Code diesen Sandbox-Prozess verursacht zum Absturz gerät, z. B. durch Überschreitung eines Speicherlimits, ist davon nicht betroffen.
Ein Sandbox-Absturz führt dazu, dass alle Isolierungen in dieser Sandbox beendet werden. Die meisten
offensichtliches Symptom hierfür ist, dass alle Bewertungen
IsolateTerminatedException
Je nach Situation
bestimmte Ausnahmen wie SandboxDeadException
oder
MemoryLimitExceededException
kann ausgegeben werden.
Der Umgang mit Abstürzen für jede einzelne Bewertung ist nicht immer praktikabel.
Darüber hinaus kann ein Isolator außerhalb eines explizit angeforderten
aufgrund von Hintergrundaufgaben oder
Bewertungen in anderen Isolierungen. Der Absturz
kann die Verarbeitungslogik zentralisiert werden,
indem ein Callback mithilfe von
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);
Optionale Sandbox-Funktionen
Abhängig von der zugrunde liegenden WebView-Version kann eine Sandbox-Implementierung
verschiedene Funktionen zur Verfügung. Daher müssen alle erforderlichen Abfragen
mithilfe von JavaScriptSandbox.isFeatureSupported(...)
. Es ist wichtig,
um den Funktionsstatus zu überprüfen, bevor Methoden aufgerufen werden, die diese Funktionen nutzen.
JavaScriptIsolate
-Methoden, die möglicherweise nicht überall verfügbar sind,
die mit dem Vermerk RequiresFeature
versehen sind, damit Sie sie leichter erkennen
Anrufe im Code.
Parameter übergeben
Wenn JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
gleich
unterstützt, sind die an die JavaScript-Engine gesendeten Bewertungsanfragen nicht gebunden
binder-Transaktionslimits einzuhalten. Wird die Funktion nicht unterstützt, werden alle Daten
Die JavaScriptEngine erfolgt über eine Binder-Transaktion. Das allgemeine
Transaktionsgrößenlimit gilt für jeden Aufruf, bei dem Daten oder
gibt Daten zurück.
Die Antwort wird immer als String zurückgegeben und unterliegt dem Binder
Größenbeschränkung für Transaktionen, wenn
JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
ist nicht
unterstützt. Nicht-String-Werte müssen explizit in einen JavaScript-String konvertiert werden
Andernfalls wird ein leerer String zurückgegeben. Wenn JS_FEATURE_PROMISE_RETURN
-Funktion unterstützt wird, kann der JavaScript-Code alternativ ein Promise-
zu String
wird.
Für die Übergabe großer Byte-Arrays an die JavaScriptIsolate
-Instanz
können die provideNamedData(...)
API verwenden. Die Nutzung dieser API ist nicht an
Binder-Transaktionslimits Jedes Byte-Array muss mit einem eindeutigen
die nicht wiederverwendet werden kann.
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-Code ausführen
WebAssembly-Code (Wasm) kann mit dem provideNamedData(...)
übergeben werden.
API kompiliert und dann wie unten gezeigt kompiliert und ausgeführt wird.
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-Isolierung
Alle JavaScriptIsolate
-Instanzen sind unabhängig voneinander und
alles mit anderen teilen. Das folgende Snippet führt zu
Hi from AAA!5
und
Uncaught Reference Error: a is not defined
da die Instanz „jsTwo
“ die in
„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-Support
Um diese Jetpack-Bibliothek mit Kotlin-Koroutinen zu verwenden, fügen Sie
kotlinx-coroutines-guava
Dies ermöglicht die Integration mit
ListenableFuture
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0"
}
Die APIs der Jetpack-Bibliothek können jetzt von einem Koroutinebereich aus aufgerufen werden: unten gezeigt:
// 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
)
}
Konfigurationsparameter
Wenn Sie eine Instanz der isolierten Umgebung anfordern, können Sie ihre
Konfiguration. Um die Konfiguration zu optimieren, übergeben Sie die
IsolateStartupParameters in
JavaScriptSandbox.createIsolate(...)
Derzeit können mit Parametern die maximale Heap-Größe und die maximale Größe angegeben werden für Bewertungsrückgabewerte und -fehler.