Slide 1

Slide 1 text

WebAssembly in Android Apps WASM は JNI の夢を見るか DroidKaigi 2026 有山 圭二(ARIYAMA Keiji)

Slide 2

Slide 2 text

有山 圭二(ARIYAMA Keiji) C-LIS CO., LTD. 有限会社シーリス代表。 Androidアプリ開発チョットデキる GitHub: @keiji © 2026 ARIYAMA Keiji. All rights reserved.

Slide 3

Slide 3 text

Agenda はじめに WASM in Android Apps データ転送の課題と最適化 まとめ © 2026 ARIYAMA Keiji. All rights reserved.

Slide 4

Slide 4 text

はじめに

Slide 5

Slide 5 text

背景: C/C++資産活用のニーズ OpenJPEGなど、C/C++資産の画像デコーダーをAndroidで動かしたいニーズがある。 JPEG2000画像の表示が必要だった一方、AndroidはJPEG2000のデコードに対応していない。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 6

Slide 6 text

課題: 外部コンテンツを扱う際のセキュリティリスク OpenJPEGをJNI(Java Native Interface)から利用していたが、セキュリティ面から課題があった。 画像ファイル等に仕込まれたバッファオーバーフロー攻撃によるDoS(Denial of Service / サービス拒止)や RCE(Remote Code Execution / 任意コード実行)のリスク。 外部から出所不明なコンテンツを読み込む画像デコーダーの特性 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 7

Slide 7 text

OpenJPEGで報告された脆弱性 Threat Level Explanation CWE-457: Use of Uninitialized Variable Critical OpenJPEG 2.5.1〜2.5.3 で、opj_jp2_read_header 呼び出し時にデータストリーム p_stream が短く p_image が未初期化だと OOB (Out-of-Bounds / メモリ境界外)ヒープ書き込みが発生。NIST 評価の CVSS 3.1 スコアは 9.8。CISA の SSVC で technicalImpact=total・exploitation=poc と評されており、RCE の蓋然性が高い CVE202550952 CWE-476: NULL Pointer Dereference Medium OpenJPEG 2.5.0 で、/openjp2/dwt.c 経由の NULL ポインタ参照。無効な JP2 画像を処理すると即クラッシュし得るサービス拒 否。CISA-ADP の CVSS v3.1 6.5(中程度 Medium)、SSVC technicalImpact=partial CVE20208112 CWE-787: Out-ofbounds Write High OpenJPEG 2.3.1〜2020-01-28 の opj_t1_clbl_decode_processor(openjp2/t1.c の qmfbid==1 分岐)にヒープバッファオーバーフ ロー。悪意をもって作成された JP2 画像を解析する段階でヒープ領域を破壊し、DoS(クラッシュ)または任意コード実行(RCE) に至りうる。Oracle・Debian・Fedora・Red Hat 各ディストロの CPU/errata でも公開・修正されている高重要度の脆弱性 CVE20206851 CWE-787: Out-ofbounds Write(関連 CVE) High OpenJPEG 2.3.1 以下で、opj_j2k_update_image_dimensions の検証不足により opj_t1_clbl_decode_processor でヒープオーバー フロー発生。CVE-2020-8112 とは別 bug だが同関数領域。DoS の確実な導出、特定条件下でコード実行可能性 CVE※ Vulnerability Type CVE202554874 ※ CVE: Common Vulnerabilities and Exposures © 2026 ARIYAMA Keiji. All rights reserved.

Slide 8

Slide 8 text

Android の Isolated Process AndroidはIsolated Processにより、プロセス単位で隔離された実行環境を実現できる。 システムコンポーネントとしてのServiceとして機能する オリジナルのアプリが持つパーミッションを一切持たない、独立したプロセスとして動作する ただし JNIの場合、Isolated Process 上でもネイティブコードが OS APIを直接呼び出せるため、セキュリティ的には 限定的 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 9

Slide 9 text

WASM(WebAssembly)とは ブラウザ上のマルチプラットフォームな実行環境。 機械語にコンパイルされたプログラムを実行する仕 組み。 W3C(World Wide Web Consortium)の WebAssembly Community Group による規格。 2017年 初版公開(主要なブラウザでのサポート開 始)。 © 2026 ARIYAMA Keiji. All rights reserved. WebAssembly logo by Carlos Baraza / CC0 1.0

Slide 10

Slide 10 text

WASM(WebAssembly)の用途 JavaScriptで実行するには適切でない高負荷計算を想定している ブラウザでの実行が主流。Node.js、Edge Workers、ゲーム、画像処理ツールなどでも使われる AndroidではWebViewのV8エンジンを通じて WASM を実行できる © 2026 ARIYAMA Keiji. All rights reserved.

Slide 11

Slide 11 text

JPEG2000 Decoder for Android Android 向けに JPEG2000 画像をデコードするライブラリ。 OpenJPEG を WebAssembly 化して Jetpack JavaScriptEngine 内で実行し、隔離環境下で安全にデコード する。 JNIでの直接実行とくらべて脆弱性の範囲を限定して、安全に動作させることを目指している。 https://github.com/keiji/jp2k-decoder-android/tree/feature/droidkaigi2026 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 12

Slide 12 text

Agenda はじめに WASM in Android Apps データ転送の課題と最適化 まとめ © 2026 ARIYAMA Keiji. All rights reserved.

Slide 13

Slide 13 text

WASM in Android Apps 手順 WASMバイナリの作成 AndroidアプリでWASMを動かす © 2026 ARIYAMA Keiji. All rights reserved.

Slide 14

Slide 14 text

WASMバイナリの作成: C言語による実装 hello-wasm.c #include char* hello_wasm(const char* name) { static char buffer[64]; sprintf(buffer, "Hello, %s!", name); return buffer; } © 2026 ARIYAMA Keiji. All rights reserved.

Slide 15

Slide 15 text

WASMバイナリの作成: Emscriptenによるコンパイル Emscripten(emcc)で hello.wasm にコンパイル。 emcc hello-wasm.c -o hello.wasm \ -s STANDALONE_WASM=1 \ -s EXPORTED_FUNCTIONS="['_hello_wasm', '_malloc', '_free']" \ --no-entry \ -O3 _hello_wasm, _malloc, _free © 2026 ARIYAMA Keiji. All rights reserved. 3つの関数を外部にエクスポート。

Slide 16

Slide 16 text

AndroidアプリでWASMを動かす 手順 Jetpack JavaScriptEngineの追加 JavaScriptSandboxの作成 JavaScriptIsolateの生成 スクリプトの実行: evaluateJavaScriptAsync() リソースの解放: JavaScriptIsolate.close() © 2026 ARIYAMA Keiji. All rights reserved.

Slide 17

Slide 17 text

Jetpack JavaScriptEngineの追加 build.gradle.kts dependencies { implementation("androidx.javascriptengine:javascriptengine:1.1.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.11.0") } © 2026 ARIYAMA Keiji. All rights reserved.

Slide 18

Slide 18 text

JavaScriptSandboxの作成〜Isolateの作成 val sandbox = JavaScriptSandbox.createConnectedInstanceAsync(context).await() val isolate = sandbox.createIsolate() Isolate=独立したメモリ空間 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 19

Slide 19 text

JavaScriptの構築 private fun createHelloWasmJsCode(wasmBytes: ByteArray, name: String): String { val base64Wasm = Base64.encodeToString(wasmBytes, Base64.URL_SAFE or Base64.NO_WRAP) val nameBytes = (name + "\u0000").toByteArray(Charsets.US_ASCII) val base64Name = Base64.encodeToString( nameBytes, Base64.URL_SAFE or Base64.NO_WRAP ) // ... © 2026 ARIYAMA Keiji. All rights reserved.

Slide 20

Slide 20 text

WASMのロード: WebAssembly.instantiate // ... return """ async function run() { const bytes = Uint8Array.fromBase64("$base64Wasm", { alphabet: "base64url" }) const importObject = { wasi_snapshot_preview1: { proc_exit: () => {}, fd_write: () => 0, fd_close: () => 0, fd_seek: () => 0, } }; const { instance } = await WebAssembly.instantiate(bytes, importObject); // 略 } run(); © 2026 ARIYAMA Keiji. All rights reserved.

Slide 21

Slide 21 text

""".trimIndent() WASM関数の実行: メモリ割り当てと呼び出し } private fun createHelloWasmJsCode(wasmBytes: ByteArray, name: String): String { ... const nameBytes = Uint8Array.fromBase64("$base64Name", { alphabet: "base64url" }); const namePtr = exports.malloc(nameBytes.length); const memArray = new Uint8Array(memory.buffer); memArray.set(nameBytes, namePtr); const resultPtr = exports.hello_wasm(namePtr); let endPtr = resultPtr; while (memArray[endPtr] !== 0) { endPtr++; } © 2026 ARIYAMA Keiji. All rights reserved.

Slide 22

Slide 22 text

WASM関数の実行: 結果の取得とメモリ解放 const resultBytes = memArray.subarray(resultPtr, endPtr); const resultBase64 = resultBytes.toBase64({ alphabet: "base64url" }); exports.free(namePtr); return resultBase64; } © 2026 ARIYAMA Keiji. All rights reserved.

Slide 23

Slide 23 text

WASM関数の実行: JavaScriptの評価 val jsCode = createHelloWasmJsCode(wasmBytes, name) val jsResult = isolate.evaluateJavaScriptAsync(jsCode).await() createHelloWasmJsCode(wasmBytes, "Keiji") jsResult = "Hello, Keiji!" © 2026 ARIYAMA Keiji. All rights reserved.

Slide 24

Slide 24 text

JavaScriptEngine JsSandboxService0 は WebView 実装が提供する アプリ専用の隔離プロセス(BIND_EXTERNAL_SERVICE で宣言・バインド)。 アプリプロセスとは別プロセスで走るが、当該 App 単一インスタンスで、Binder(IPC)越しにのみ通信す る。 アプリプロセス / クライアント JsSandboxService0(アプリ専用隔離プロセス・単一インスタンス) ListenableFuture(.await で文字列結果を返却) アプリ (WasmManager) JavaScriptSandbox JavaScriptIsolate evaluateJavaScriptAsync(jsCode) = AIDL IPC bindService(BIND_EXTERNAL_SERVICE) 直接バインド = 隔離プロセスを起動 Isolate(JS 実行・専用ヒープ) AIDL / IJsSandboxIsolate 外部パッケージ: org.chromium (提供元) org.chromium WebView 実装 © 2026 ARIYAMA Keiji. All rights reserved. JsSandboxService0 提供 入力 / 出力

Slide 25

Slide 25 text

多層防御: WASM Runtime システムコールを持たない命令レベルの隔離による安全性 線形メモリ上での境界チェックにより、WASM Runtime外へのバッファオーバーフローを原理的に防止 ネイティブクラッシュがJavaScript例外へと変換され、アプリ側で安全にハンドリング可能 WASMの仕様は堅牢でも、実行するWASM Runtimeに脆弱性が存在すれば、RCEや権限昇格に至る可能性は 原理的に存在する。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 26

Slide 26 text

多層防御: JavaScriptEngine ネットワーク通信やファイルアクセス、DOM(Document Object Model)などの API は実行環境内に存在しない 外部とやり取りできるのは、開発者が明示的に繋いだ経路に限定される evaluateJavaScriptAsync()に対して不適切にエスケープされたデータを渡すと、コード挿入の脆弱性につな がる © 2026 ARIYAMA Keiji. All rights reserved.

Slide 27

Slide 27 text

JNIとの比較 ゼロコピーの差異 JNIはゼロコピーを実現できる WASMはメモリ空間の断絶が安全性につながる 解析への耐性 WASMバイナリは構造が整理されており、ロジックや情報の秘匿を目的としてJNIを通じてアクセスするネイティブコ ードに処理を持たせる用途には不向き ネイティブコードも解析は可能。もとよりロジックや情報の秘匿目的で使うには適当でない © 2026 ARIYAMA Keiji. All rights reserved.

Slide 28

Slide 28 text

Agenda はじめに WASM in Android Apps データ転送の課題と最適化 まとめ © 2026 ARIYAMA Keiji. All rights reserved.

Slide 29

Slide 29 text

データ転送の課題と最適化 Wasmは隔離されたJavaScriptEngine(WebView)の中で実行される。 アプリとWebViewの間で、データの転送が発生する。 JavaScriptEngine プロセス アプリプロセス アプリ © 2026 ARIYAMA Keiji. All rights reserved. IPC データ転送 Isolate (WASM 実行)

Slide 30

Slide 30 text

evaluateJavaScriptAsyncによる入力 実行するJavaScriptの中に入力データを含める。 private fun createHelloWasmJsCode(wasmBytes: ByteArray, name: String): String { return """ async function run() { const bytes = Uint8Array.fromBase64("$base64Wasm", { alphabet: "base64url" }) const nameBytes = Uint8Array.fromBase64("$base64Name", { alphabet: "base64url" }); ... """.trimIndent() val jsCode = createHelloWasmJsCode(wasmBytes) val jsResult = isolate.evaluateJavaScriptAsync(jsCode).await() 入力も出力も文字列。バイナリを文字列にエンコード・デコードするコストがかかる。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 31

Slide 31 text

provideNamedDataを用いた入出力 Kotlinの ByteArray を ArrayBuffer としてWebViewのメモリ空間へ直接与えることができる。 Kotlin isolate.provideNamedData(PROVIDED_WASM_DATA, wasmBytes) JavaScript globalThis.transferFromProvidedNamedData('$PROVIDED_WASM_DATA') © 2026 ARIYAMA Keiji. All rights reserved.

Slide 32

Slide 32 text

データ転送方式の比較 方式 入力 文字列 バイナリ evaluateJavaScriptAsync provideNamedData 出力 文字列 文字列 Feature JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER provideNamedData は入力時にのみ機能する。実行結果は evaluateJavaScriptAsync を通じて文字列 provideNamedData はサポートしていない可能性がある。機能のサポート判定が必要。 で受け取る。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 33

Slide 33 text

サポート判定 val supported = JavaScriptSandbox.isFeatureSupported( JavaScriptSandbox.JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER ) if (supported) { // バイトデータの入力に対応している } else { // 未対応 } © 2026 ARIYAMA Keiji. All rights reserved.

Slide 34

Slide 34 text

エンコーディング方式の比較 JSArray Base64 / Base64Url Base85 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 35

Slide 35 text

サンプルデータ 方式 データ形式 サイズ チャンネル ファイルサイズ Bitmapサイズ JPEG2000 Decoder for Android (https://github.com/keiji/jp2k-decoder-android) © 2026 ARIYAMA Keiji. All rights reserved. 入力 JPEG2000 640x480 3ch 147,041 bytes 1,228,854 bytes

Slide 36

Slide 36 text

JSArray Byte配列をJavaScriptの中に直接配列として記述する JavaScript中に展開すると文法上の配列として取り扱われるので、デコードの必要がない [66,77,54,192,18,0,0,0,0,0,54,0,0,0,40,0,0,0,128,2,0,0,32,254,25 5,255,1,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2 ,3,4,255,5,6,5,255,9,10,7,255,9,8,7,255,9,5,7,255,8,4,6,255,7,5, 6,255,9,7,8,255,11,10,11,255,10,10,12,255,17,18,19,255,31,33,35, ... (truncated 70014 lines) ... 5,115,101,99,255,135,115,119,255,140,116,126,255,50,34,43,255,24 ,19,24,255,22,21,23,255,26,27,28,255,30,32,32,255,32,35,36,255,3 1,31,34,255,34,32,36,255,32,37,34,255,49,59,51,255,127,132,130,2 55,112,112,117,255,100,99,106,255,92,91,98,255,84,83,91,255,72,7 2,78,255,42,42,47,255,35,35,40,255,37,37,41,255] © 2026 ARIYAMA Keiji. All rights reserved.

Slide 37

Slide 37 text

JSArray: 結果 1,228,854 バイトのデータが 4,481,456 文字(約364%) Kotlin側のデコードが大きい 全体のイメージのデコードにかかった時間が方式の中でもっともも大きい Output encoded content length: 4481456 chars Output data length: 1228854 bytes Input transfer start delay (Kotlin -> JS start): 4.00 ms Output transfer delay (JS finish -> Kotlin receive): 47.00 ms Output Kotlin decode time: 157.80 ms Performance: inputSize=0B totalTime=889ms dataTransferTime=219ms jsDecodeTime=0ms jsEncodeTime=58ms wasmHeapSize=7MB outputImage=1228854B Pre-process: 0.0 ms, WASM: 113.0 ms, Post-process: 58.0 ms decodeImage() finished in 890 msec © 2026 ARIYAMA Keiji. All rights reserved.

Slide 38

Slide 38 text

Base64/Base64Url バイナリを ASCII(American Standard Code for Information Interchange / 標準情報交換用 7 ビット文字コード)・ 印刷可能なコードポイントに割り当てる JavaScriptEngineはbtoa/atobには非対応(bytes.toBase64/Uint8Array.fromBase64) Qk02wBIAAAAAADYAAAAoAAAAgAIAACD-__8BACAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAgME_wUGBf8JCgf_CQgH_wkFB_8IBAb_BwUG_wkHCP8LCgv_CgoM_xES E_8fISP_IyUn_ygpLP8sKy__MTEy_zc3Nf85Njn_QDtC_0A9Qf86Oj3_PkE-_zo0 VP-Chqj_o7LS_7DB1_-3yNL_ssbN_6q_xv-ovsP_oLa5_4yipf-LoKL_h5me_3mI ... (truncated 25593 lines) ... hP99fH__eX6F_3p-hv9-f4j_fn6E_3t5fv92b3v_d2x-_3Vqd_9zanD_cWxv_2xq a_9sa3D_a2ly_3Ftdv90bnX_e3J4_4F2e_-Adnv_fnV7_3Bob_93cHf_f3d9_3xy eP9_c3j_f3F1_3NlZf9zZWP_h3N3_4x0fv8yIiv_GBMY_xYVF_8aGxz_HiAg_yAj JP8fHyL_IiAk_yAlIv8xOzP_f4SC_3Bwdf9kY2r_XFti_1RTW_9ISE7_Kiov_yMj KP8lJSn_ © 2026 ARIYAMA Keiji. All rights reserved.

Slide 39

Slide 39 text

Base64/Base64Url: 結果 1,228,854 バイトのデータが 1,638,472 文字(約133%) Base64 系(Base64Url)で目立った差はなかった Output encoded content length: 1638472 chars Output data length: 1228854 bytes Input transfer start delay (Kotlin -> JS start): 2.00 ms Output transfer delay (JS finish -> Kotlin receive): 16.00 ms Output Kotlin decode time: 35.04 ms Performance: inputSize=0B totalTime=513ms dataTransferTime=179ms jsDecodeTime=0ms jsEncodeTime=64ms wasmHeapSize=7MB outputImage=1228854B Pre-process: 0.0 ms, WASM: 97.0 ms, Post-process: 64.0 ms decodeImage() finished in 514 msec © 2026 ARIYAMA Keiji. All rights reserved.

Slide 40

Slide 40 text

Base85

Slide 41

Slide 41 text

Base85 4バイトのバイナリデータを符号化し5文字のASCIIコードを用いて文字を表す 派生形がPDFで利用されている(Ascii85) lqowN5&!@i001@S001zE004JJ001ev|nJ91aohxw000000000000000000000000 0000651PFSf1^)>o2MC8o2MC8l2MC5j2la^i2la~m2>%nt3;ZLv4f3@L6Aoc}bMw ~mc;UOzeDmmKfAi$Ygxfo>hubV_iSzualJoqllh{~bj^WYikl0V2rr4M8Sq;S~(* lL**z;t3(*lR?&hT*U#{A0K!~DyrX&mR!Rt>EDQw_1pP8T7>J{KDZI~NHlBn><=8 ... (truncated 23993 lines) ... cVG#u+WGA3+!G#uEFFb$)yH5VEFHw~ZLI2R_MG#uyAE*BOfD;FqaE*BL9CMh#0Aq |^^z~TS+yYwi(Aq|Y*A{Qb{Ck>V4B^M*eC>JtrD;FRtD;FLqD;F4~z~U32CMiepE GaXnC>JnmC>JnkB^MI^w&D+!wb-s9CMiRzE*zeEeb_C182=u(7YLx?9rd65aPAMe b>X{dbl5;fb>X~kbl6wVgYI{TG8Y)eB^L~Zyx4Kyv*GzatMmk%px8(@fAimwde}O tdGo © 2026 ARIYAMA Keiji. All rights reserved.

Slide 42

Slide 42 text

Base85: 結果 1,228,854 バイトのデータが 1,536,068 文字(約125%) Base64に比べて文字数が少ないにもかかわらず、デコードにかかった時間は大きくなった Output encoded content length: 1536068 Output data length: 1228854 bytes Input transfer start delay (Kotlin -> JS start): 4.00 ms Output transfer delay (JS finish -> Kotlin receive): 14.00 ms Output Kotlin decode time: 119.40 ms Performance: inputSize=0B totalTime=622ms dataTransferTime=199ms jsDecodeTime=0ms jsEncodeTime=74ms wasmHeapSize=7MB outputImage=1228854B Pre-process: 0.0 ms, WASM: 110.0 ms, Post-process: 74.0 ms decodeImage() finished in 623 msec © 2026 ARIYAMA Keiji. All rights reserved.

Slide 43

Slide 43 text

Base32768

Slide 44

Slide 44 text

Base32768 とは: 概要と特徴 UTF-16環境で1文字あたり15ビットのデータを表現 より少ない文字でデータのエンコードが可能 https://github.com/qntm/base32768 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 45

Slide 45 text

Base32768 とは: エンコードデータの例 嘦芰㙀㐀㐀㓘㐀㐨㐀㐠㑀㐂㯷샼㘀唀㐀㐀㐀㐀㐀㐀㐀㐀㐀㐀㐀㐀㐀㰌㷾㤆㛿睂痿놐甿봤㸏쀈㘃瓁閠냰紸壼䨔㿿㤅㜿霢嘿맹㢏뽆娧삔㹋哥瞲샹秄髾氷 俿荍簿딃輗븀뮃쀺刞샏紧냳蚢裾㤍巿蛙榿뜘剿뺾塋쁥篍샕擱铵㿬哽㛚듿䂢蟿韨䡟륹臷뻦䔝쁻祅샚掮瓱䓐飼䀔㷿㜃㗿陡㒿릈浇뼼償삔㶊铩뗵䯺䤙䏾㸌 㧿甀甿녀唯봘㸑쀋㠆샃㔁铰鵈泼䨔㻿㢅睿阡族륈敇뼒㼑삄矄員斱䏸椰嫾䄌㱿硂雿뇰长뵀伟쀏㪇瓃皁샰륨磼匜䛿㨆㟿陁旟률椿뼜䐒삈㡅哢䔑瓸組棾䜑 䅿禄㛿눑㒧뵀唣쀐㰇샄坂哱䒠蓼唢䫿㬇㨿雡陯릀煗뼢䈘삉碆铢嗱瓸祀峾䘒㽿秄砟눑㒯뵀儡쀏㬇샄㗢䏱㱸糼崦䫿㶌糿馦墏먹緣뼦丟삇㠆哢䔁賸礼烾䘑 ... (truncated 10232 lines) ... 歿诙承뛦恟빵痏쁛抲瓖㼬蓵枻䏽掲鯿崩赿鷉媟뭚癿뾜襟삥䣖瓩㤕泺䔱峾硊忿薑鿟딄得뷽㺟쀼嘤샎鴨샳廒㟼魮狿刟蘿鮇夏몁跳뽜椻삔膍샨䣅㯺㴝哾籆 广蘑齟뒤䙟븕冩쁃媨瓔後蓳닪都䢐釿妣詿鹉䥯뮂癷뾪豠삩誗瓩觵铺㤍廾穊惿衕肿뗥矯빉毁쁏怰瓖㾭蓵濫䯽淆髿昳避긍屯밓䗃뾼陬삶僞铯泸蓼㸚囿㦈 繿闡닿뤨楧뼂㤏삄睄瓠䍰哷겣볾㛳㟿础垿넯赯봼䨳쀍㦉샂阡铰崸峼㘊㯾삀睿钟斿룗饏뻽받쁻滁샢䎑哸礄壾㴃㣿磁闿뉐赏볫닿뿲뾅삽咡铯鳸系뫺㷾뱹 瑿銛끿롶飷뻕觯쁳樸瓜抍샶顓擽跖뇿檴醿꼭豯벣滗뿶델샀銞샰㭧铻뫕룾녨泿鋜䋿룷烯뻱駱쁿溼瓟꼮铷倫䯽鯊飿碹鋿늎糯몑㲯뼰䜘삋㥅샣嚱鳸릀盾唣 䙿糇饟댢㔧부罅쀱劙샟놐擷㞃賽緆鿿挭趿龊檿뭂嘻뽔弯삑緊哤蝒蓸℉ © 2026 ARIYAMA Keiji. All rights reserved.

Slide 46

Slide 46 text

Base32768 - 結果 1,228,854 バイトのデータが 655,390 文字(=1,310,782 バイト, 約106%) Kotlinでのデコード時間が 185.79ms と、もっとも長い Output encoded content length: 655390 Output data length: 1228854 bytes Input transfer start delay (Kotlin -> JS start): 1.00 ms Output transfer delay (JS finish -> Kotlin receive): 16.00 ms Output Kotlin decode time: 185.79 ms Performance: inputSize=0B totalTime=539ms dataTransferTime=188ms jsDecodeTime=0ms jsEncodeTime=67ms wasmHeapSize=7MB outputImage=1228854B Pre-process: 0.0 ms, WASM: 104.0 ms, Post-process: 67.0 ms decodeImage() finished in 540 msec © 2026 ARIYAMA Keiji. All rights reserved.

Slide 47

Slide 47 text

変換後の文字数 文字列に変換後の文字数 (chars) 4,500,000 4,000,000 3,500,000 3,000,000 )srahc( 数字文 2,500,000 2,000,000 1,500,000 1,000,000 500,000 0 © 2026 ARIYAMA Keiji. All rights reserved. JsArray Base64Url Base85 DataChannel 方式 Base32768

Slide 48

Slide 48 text

JavaScript側のエンコードにかかる時間 JS エンコード処理時間 (ms) 80 70 60 50 )sm( 間時 40 30 20 10 0 © 2026 ARIYAMA Keiji. All rights reserved. JsArray Base64Url Base85 DataChannel 方式 Base32768

Slide 49

Slide 49 text

Kotlin側のデコードにかかる時間 Kotlin デコード処理時間 (ms) 200 180 160 140 )sm( 間時 120 100 80 60 40 20 0 © 2026 ARIYAMA Keiji. All rights reserved. JsArray Base64Url Base85 DataChannel 方式 Base32768

Slide 50

Slide 50 text

結論 データ転送量を削減しても、エンドポイントにおけるエンコード・デコードコストがボトルネックになる。 ブラウザ・アプリともにネイティブでの実装があるもの(Base64/Base64Url)を採用するのが望ましい。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 51

Slide 51 text

JavaScriptEngine 1.1.0

Slide 52

Slide 52 text

MessagePort API WebViewとアプリのやりとりを双方向 ByteArray で行う。 Kotlin val client = MessagePortClient { message -> if (message.type == Message.TYPE_ARRAY_BUFFER) { val bytes = message.arrayBuffer messageQueue.offer(bytes) } } val messagePort = isolate.createMessageChannel("jp2k_binary_port", executor, client) messagePort.postMessage(Message.createArrayBufferMessage(wasmBytes)) JavaScript globalThis.receiveBinaryMessage() © 2026 ARIYAMA Keiji. All rights reserved.

Slide 53

Slide 53 text

デコード速度 全体の総デコード時間比較 (ms) 900 800 700 )sm( 間時ドーコデ総 600 500 400 300 200 100 0 © 2026 ARIYAMA Keiji. All rights reserved. JsArray Base64Url Base85 方式 Base32768 MessagePort

Slide 54

Slide 54 text

データ転送方式の比較 方式 evaluateJavaScriptAsync provideNamedData MessagePort 入力 文字列 バイナリ バイナリ 出力 文字列 文字列 バイナリ Feature JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER JS_FEATURE_MESSAGE_PORTS 対応していないケースに備えて、引き続き文字列でデータを取り扱うフォールバックが必要。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 55

Slide 55 text

Binderのトランザクション制限 Binderでは約1MBの転送に限定される制限がある。 画像データのように巨大な入出力データを一度で扱うためには、 JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT がサポートされている必要がある。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 56

Slide 56 text

主要なFeatureとその役割 Feature名 JS_FEATURE_WASM_COMPILATION JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER JS_FEATURE_EVALUATE_WITHOUT_TRANSACTION_LIMIT JS_FEATURE_ISOLATE_MAX_HEAP_SIZE JS_FEATURE_PROMISE_RETURN JS_FEATURE_CONSOLE_MESSAGING JS_FEATURE_MESSAGE_PORTS JS_FEATURE_EVALUATE_FROM_FD JS_FEATURE_ISOLATE_TERMINATION © 2026 ARIYAMA Keiji. All rights reserved. 有効化される機能・API WASMバイナリのロードとコンパイル provideNamedDataによるバイナリ直接転送 evaluateJavaScriptAsync による入力・戻り値のトランザクション制限 超え Isolateごとの最大ヒープサイズの指定 evaluateJavaScriptAsync からの Promise の戻り値処理 console.log などのコンソール出力のフック MessagePortを用いた双方向通信 ファイルディスクリプタからの直接スクリプト評価 実行中のIsolateの強制終了 備考・未サポート時の影響 WASM実行不可 未サポート時は代替経路へフォールバック 未サポート時は入出力の巨大データでクラッ シュ メモリ枯渇によるクラッシュを防ぐ 非同期処理のハンドリングに必須 デバッグ用途 javascriptengine 1.1.0で追加 巨大ファイルの処理に有効 無限ループやタイムアウト時の安全な破棄

Slide 57

Slide 57 text

Graceful Degradation 機能が使えないブラウザのためにGraceful Degradation(段階的機能低下)の適用が望ましい。 Yes Yes スタート JS_FEATURE_MESSAGE_PORTS JS_FEATURE_WASM_COMPILATION No No © 2026 ARIYAMA Keiji. All rights reserved. MessagePort API WASM非対応 Yes provideNamedData No 文字列での入出力(Base64Url) JS_FEATURE_PROVIDE_CONSUME_ARRAY_BUFFER

Slide 58

Slide 58 text

FD(File Descriptor)による 1MB 制限回避 App / WebView / JsSandboxService0 外部パッケージ: org.chromium (提供元) org.chromium WebView 実装 JsSandboxService0(アプリ専用隔離プロセス・単一インスタンス) JsSandboxService0 提供 AIDL / IJsSandboxService アプリプロセス / クライアント JavaScript 実行 FD で受取 / 返却 bindService(BIND_EXTERNAL_SERVICE) 直接バインド = 隔離プロセスを起動 アプリ (WasmManager) JavaScriptSandbox カーネル pipe / RAM JavaScriptIsolate FD で受取 / 返却 (双方向・1MB 超でも通過) anonymous pipe (createPipe) FD(File Descriptor) は Parcel を経由して binder 越しに渡る。ペイロードは pipe(RAM) =1MB の制限がない。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 59

Slide 59 text

各方式と FD(FileDescriptor) 方式 出力 文字列 fd 化の切替 Feature feature で全件 pipe EVALUATE_WITHOUT_TRANSACTION_LIMIT - MessagePort 入力 文字列 バイナリ バイナリ evaluateJavaScriptAsync(FD) FD FD 常に pipe ≤64KiB=binder / >64KiB=FD 元から FD evaluateJavaScriptAsync provideNamedData バイナリ PROVIDE_CONSUME_ARRAY_BUFFER MESSAGE_PORTS EVALUATE_FROM_FD は feature に応じてデータのやり取りをFD経由に自動切り替え MessagePort はサイズに応じて切り替え binder は FD(Parcel)で参照を渡す ことで 1MB 上限回避 evaluateJavaScriptAsync © 2026 ARIYAMA Keiji. All rights reserved.

Slide 60

Slide 60 text

セキュリティ: FD 化データの可読性 FD 化されるデータは、anonymous pipe(RAM) に載る。ディスク上のファイルには保存されない。 保護機構 UID 分離 SELinux ptrace_scope 匿名 pipe 効く範囲 別 app が pipe FD を読めない 別 app が sandbox プロセスを触れない 別 app の /proc//fd アクセスを制限 第三者が fd を共有していない ただし、データは平文。 保護は「誰が fd / メモリに触れるか」のアクセス制御であり暗号化で はない。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 61

Slide 61 text

Agenda はじめに WASM in Android Apps データ転送の課題と最適化 まとめ © 2026 ARIYAMA Keiji. All rights reserved.

Slide 62

Slide 62 text

まとめ

Slide 63

Slide 63 text

WASMがもたらす多層防御 JNIによる既存C/C++資産活用の限界 画像ファイル等の解析における脆弱性が、アプリのクラッシュや任意コード実行の直接的なリスクとなる。 WASMによる堅牢な実行環境 システムコールを持たない命令レベルの隔離と、線形メモリ空間による境界チェック。 Jetpack JavaScriptEngineのIsolateと組み合わせることで、多層防御を実現できる。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 64

Slide 64 text

データ転送コストの壁 隔離プロセスゆえの入出力の課題 WASMを実行するプロセスが安全に隔離されている反面、アプリとの間でデータ転送が必要になる。 エンコード・デコード処理がボトルネックに 転送データ量を減らす手法(Base85やBase32768等)は、かえってデコードに時間がかかることがある。 現状では、ブラウザ・アプリ双方にネイティブ実装が存在する方式(Base64/Base64Url)を採用するのがも っとも実用的である。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 65

Slide 65 text

JavaScriptEngineの進化と最適化 Graceful Degradation(段階的機能低下)の実装 環境(WebViewのバージョン)によって利用可能な機能が異なる。 サポートしているFeatureを確認しながら、MessagePort → provideNamedData → 文字列(Base64)へと安 全にフォールバックする設計が不可欠である。 セキュリティ要件との調整 アプリと隔離プロセスとの間でどのようにデータが送受信されるか。攻撃のサーフェスはどこかを常に確認、 適切な対応を検討する必要がある。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 66

Slide 66 text

WASMはJNIの夢を見るか JNIを完全に置き換えるものではない ゼロコピーが可能なJNIと比較すると、パフォーマンス面では依然としてトレードオフが存在する。 セキュリティと資産再利用のベストプラクティス 外部からの非信頼データを処理する用途(画像解析、メディアデコーダー等)において、WASMは有力な選択 肢となる。 ネイティブ資産を安全にモバイルへ持ち込むためのプラットフォームとして、Jetpack JavaScriptEngineを通 じたWASMの活用は今後さらに重要になる。 © 2026 ARIYAMA Keiji. All rights reserved.

Slide 67

Slide 67 text

ご清聴ありがとうございました 本資料は有山圭二の著作物です。本資料の全部、または一部について、著作者から文書による許諾を得ずに複製することは禁じられています。 本資料の内容は、発表者個人の見解であり、所属または関与する組織を代表するものではありません。 各製品名・ブランド名、会社名などは、一般に各社の商標または登録商標です。本資料中では、©、®、™を割愛しています。