JavaScript の評価
Jetpack ライブラリ JavaScriptEngine を使用すると、 WebView のインスタンスを作成せずに JavaScript コードを評価できます。
非対話型の JavaScript 評価が必要なアプリケーションの場合は、 JavaScriptEngine ライブラリには、次のような利点があります。
WebView を割り当てる必要がないため、リソース消費量を抑える 作成します。
Service(WorkManager タスク)で実行できます。
オーバーヘッドの少ない複数の分離された環境により、アプリケーションで 複数の JavaScript スニペットを同時に実行できます。
API 呼び出しを使用して大量のデータを渡す機能。
基本的な使用方法
まず、JavaScriptSandbox
のインスタンスを作成します。これは
プロセス外 JavaScript エンジンに接続します。
ListenableFuture<JavaScriptSandbox> jsSandboxFuture =
JavaScriptSandbox.createConnectedInstanceAsync(context);
サンドボックスのライフサイクルを このコンポーネントは JavaScript の評価が必要なコンポーネントです。
たとえば、サンドボックスをホストするコンポーネントは Activity
または
Service
。単一の Service
を使用して JavaScript 評価をカプセル化できます。
すべてのアプリケーション コンポーネントが対象になります。
割り当てが適正であるため、JavaScriptSandbox
インスタンスを維持する
高価ですJavaScriptSandbox
インスタンスは、アプリケーションごとに 1 つだけ許可されます。「
IllegalStateException
は、アプリがスペースを割り当てようとしたときにスローされます。
2 番目の JavaScriptSandbox
インスタンス。ただし、複数の実行環境が
必要な場合は、複数の JavaScriptIsolate
インスタンスを割り当てることができます。
不要になったらサンドボックス インスタンスを閉じてリソースを解放します。「
JavaScriptSandbox
インスタンスは、AutoCloseable
インターフェースを実装します。
シンプルなブロックのユースケースで try-with-resources を使用できます。
または、JavaScriptSandbox
インスタンスのライフサイクルが
アクティビティの onStop()
コールバックで閉じるか、
Service の onDestroy()
の間:
jsSandbox.close();
JavaScriptIsolate
インスタンスは、実行のためのコンテキストを表します。
使用できます。必要に応じて割り当てることができるため、セキュリティが脆弱
オリジンが異なるスクリプトの境界線、または JavaScript の同時実行を有効にする
JavaScript は本質的にシングルスレッドであるため、実行できません。以降の呼び出しでは、
同じインスタンスは同じ状態を共有するため、一部のデータ インスタンスは
後に JavaScriptIsolate
の同じインスタンスで処理します。
JavaScriptIsolate jsIsolate = jsSandbox.createIsolate();
close()
メソッドを呼び出して、JavaScriptIsolate
を明示的に解放します。
JavaScript コードを実行している分離インスタンスを閉じる
(Future
が不完全の場合)IsolateTerminatedException
になります。「
分離された後、バックグランドでクリーンアップされ、
JS_FEATURE_ISOLATE_TERMINATION
をサポートしている場合は、
サンドボックスのクラッシュの処理に関するセクションに
できます。そうしないと、保留中の評価がすべて完了するまでクリーンアップが延期されます。
サンドボックスが閉じられます。
アプリケーションは、JavaScriptIsolate
インスタンスを作成してアクセスできます。
表示されます。
これで、アプリケーションで 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);
同じ JavaScript スニペットを適切なフォーマットで記述すると、次のようになります。
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);
コード スニペットは String
として渡され、結果は String
として配信されます。
evaluateJavaScriptAsync()
を呼び出すと、評価された
JavaScript コード内の最後の式の結果を返します。必要があります。
JavaScript String
型の値。それ以外の場合、ライブラリ API は空の値を返します。
JavaScript コードでは return
キーワードを使用しないでください。サンドボックスが
特定の機能、追加の戻り値の型(たとえば、Promise
String
に解決されるものなど)が考えられます。
このライブラリでは、Python 形式で記述されたスクリプトの評価も
AssetFileDescriptor
または ParcelFileDescriptor
。詳しくは、
evaluateJavaScriptAsync(AssetFileDescriptor)
、
evaluateJavaScriptAsync(ParcelFileDescriptor)
。
これらの API はディスク上のファイルまたはアプリ内のファイルからの評価に適している
ディレクトリを作成します。
このライブラリは、デバッグに使用できるコンソール ロギングもサポートしています。
あります。これは setConsoleCallback()
を使用して設定できます。
コンテキストが維持されるため、コードをアップロードして複数回実行できる
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);
もちろん、変数も永続的であるため、前のステップの スニペット:
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);
たとえば、必要なすべてのオブジェクトを割り当て、 JavaScript コードを実行すると、次のようになります。
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);
try-with-resources を使用して、すべてのリソースに
使用されなくなります。サンドボックスの結果を閉じる
すべての JavaScriptIsolate
インスタンスで失敗した、保留中のすべての評価で
SandboxDeadException
に置き換えます。JavaScript の評価でコードエラーが発生すると、
エラーが発生すると、JavaScriptException
が作成されます。そのサブクラスを参照する
をご覧ください。
サンドボックスのクラッシュへの対処
すべての JavaScript は、プロセスとは別のサンドボックス プロセスで実行されます。 実行されません。JavaScript コードが原因でこのサンドボックス化されたプロセスが たとえば、メモリの上限を使い切ると、アプリケーションのメイン プロセスに影響はありません。
サンドボックスがクラッシュすると、そのサンドボックス内のすべての分離が終了します。最も
すべての評価が失敗し、
IsolateTerminatedException
。状況によっては、
特定の例外(SandboxDeadException
や
MemoryLimitExceededException
がスローされる可能性があります。
評価のたびにクラッシュを処理することは、必ずしも現実的ではありません。
さらに、アイソレート ツールは明示的に要求された
他の分離された環境における、バックグラウンド タスクや評価による評価クラッシュ
コールバックをアタッチして、その処理ロジックを一元化するには、
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);
オプションのサンドボックス機能
基盤となる WebView のバージョンによっては、サンドボックスの実装に
さまざまな機能が用意されています。そのため、それぞれの要求に応えるために、
JavaScriptSandbox.isFeatureSupported(...)
を使用します。大事なこと
を使用して、これらの機能に依存するメソッドを呼び出す前に、機能のステータスを確認することができます。
地域によっては使用できない可能性のある JavaScriptIsolate
メソッドがある
RequiresFeature
アノテーションが付いているため、簡単に特定できます。
必要があります。
パラメータの引き渡し
JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
が次の場合:
JavaScript エンジンに送信される評価リクエストは
バインダー トランザクションの上限によって制限されます。この機能がサポートされていない場合、
JavaScriptEngine は Binder トランザクションを通じて発生します。全般
トランザクション サイズ制限は、
がデータを返します。
応答は常に文字列として返され、Binder の対象である
トランザクションの最大サイズが
次を含まない: JavaScriptSandbox.JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT
サポートされません。文字列以外の値は、明示的に JavaScript 文字列に変換する必要があります
それ以外の場合は空の文字列が返されます。JS_FEATURE_PROMISE_RETURN
の場合
サポートされている場合、JavaScript コードは代わりに Promise を返す
String
に解決されます
大きなバイト配列を JavaScriptIsolate
インスタンスに渡すには、次のようにします。
provideNamedData(...)
API を使用できます。この API の使用は、
Binder トランザクションの上限が適用されます。各バイト配列は、一意の ID を使用して
再利用できません。
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 コードの実行
WebAssembly(Wasm)コードは、provideNamedData(...)
を使用して渡すことができます。
その後、以下のように、通常の方法でコンパイルして実行されます。
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);
}
JavaScriptIsolate 分離
すべての JavaScriptIsolate
インスタンスは互いに独立しており、
何でも共有できます。次のスニペットの場合、結果は次のようになります。
Hi from AAA!5
および
Uncaught Reference Error: a is not defined
「jsTwo
」インスタンスには、
「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 のサポート
この Jetpack ライブラリを Kotlin コルーチンで使用するには、依存関係を以下に追加します。
kotlinx-coroutines-guava
。これにより
ListenableFuture
。
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.6.0"
}
次のように、コルーチン スコープから Jetpack ライブラリ API を呼び出すことができるようになりました。 以下に例を示します。
// 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
)
}
構成パラメータ
隔離された環境インスタンスをリクエストする場合、そのインスタンスを
できます。構成を調整するには、
IsolateStartupParameters のインスタンスを分離し、
JavaScriptSandbox.createIsolate(...)
。
現在、パラメータを使用して最大ヒープサイズと最大サイズを指定できます。 評価の戻り値とエラーを確認できます