Call WasmEdge functions from an Android APK app

In this section, we will show you how to build a “regular” Android app (i.e., an APK file that can be installed on an Android device). The APK app embeds a WasmEdge Runtime. It can call WebAssembly functions through the embedded WasmEdge. The benefit is that developers can safely embed high-performance functions written in several different languages (e.g., Rust, JS, Grain, TinyGo etc) into a Kotlin application.

Quickstart

The demo project is available here. You can build the project using the Gradle tool or using the Android Stuido IDE.

Building Project with Gradle

  1. Setup environment variable ANDROID_HOME=path/to/your/android/sdk
  2. Run Command ./gradlew assembleRelease
  3. Sign your APK file with apksigner. The apk file is at ./app/build/outputs/apk/release. The apksigner utility is at $ANDROID_HOME/build-tools/$VERSION/apksigner.

Building Project with Android Studio

Open this folder with Android Studio 2020.3.1 or later.

For Release APK, click Menu -> Build -> Generate Signed Bundle/APK, select APK, setup keystore configuration and wait for build finished.

Review of the source code

The Android UI app is written in Kotlin, and it uses JNI (Java Native Interface) to load a C shared library, which in turn embeds WasmEdge.

Android UI

The Android UI application is located here. It is written in Kotlin using the Android SDK.

  1. class MainActivity : AppCompatActivity() {
  2. lateinit var lib: NativeLib
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.activity_main)
  6. val tv = findViewById<TextView>(R.id.tv_text)
  7. lib = NativeLib(this)
  8. Thread {
  9. val lines = Vector<String>()
  10. val idxArr = arrayOf(20, 25, 28, 30, 32)
  11. for (idx: Int in idxArr) {
  12. lines.add("running fib(${idx}) ...")
  13. runOnUiThread {
  14. tv.text = lines.joinToString("\n")
  15. }
  16. val begin = System.currentTimeMillis()
  17. val retVal = lib.wasmFibonacci(idx)
  18. val end = System.currentTimeMillis()
  19. lines.removeLast()
  20. lines.add("fib(${idx}) -> ${retVal}, ${end - begin}ms")
  21. runOnUiThread {
  22. tv.text = lines.joinToString("\n")
  23. }
  24. }
  25. }.start()
  26. }
  27. }

The native library

The Android UI app calls a NativeLib Kotlin object to access WasmEdge functions. The NativeLib source code is available here. It uses JNI (Java Native Interface) to load a C shared library called wasmedge_lib. It then calls the nativeWasmFibonacci function in wasmedge_lib to execute the fibonacci.wasm WebAssembly bytecode.

  1. class NativeLib(ctx : Context) {
  2. private external fun nativeWasmFibonacci(imageBytes : ByteArray, idx : Int ) : Int
  3. companion object {
  4. init {
  5. System.loadLibrary("wasmedge_lib")
  6. }
  7. }
  8. private var fibonacciWasmImageBytes : ByteArray = ctx.assets.open("fibonacci.wasm").readBytes()
  9. fun wasmFibonacci(idx : Int) : Int{
  10. return nativeWasmFibonacci(fibonacciWasmImageBytes, idx)
  11. }
  12. }

The C shared library

The C shared library source code wasmedge_lib.cpp is available here. It uses the WasmEdge C SDK to embed a WasmEdge VM and execute the WebAssembly function.

  1. extern "C" JNIEXPORT jint JNICALL
  2. Java_org_wasmedge_native_1lib_NativeLib_nativeWasmFibonacci(
  3. JNIEnv *env, jobject, jbyteArray image_bytes, jint idx) {
  4. jsize buffer_size = env->GetArrayLength(image_bytes);
  5. jbyte *buffer = env->GetByteArrayElements(image_bytes, nullptr);
  6. WasmEdge_ConfigureContext *conf = WasmEdge_ConfigureCreate();
  7. WasmEdge_ConfigureAddHostRegistration(conf, WasmEdge_HostRegistration_Wasi);
  8. WasmEdge_VMContext *vm_ctx = WasmEdge_VMCreate(conf, nullptr);
  9. const WasmEdge_String &func_name = WasmEdge_StringCreateByCString("fib");
  10. std::array<WasmEdge_Value, 1> params{WasmEdge_ValueGenI32(idx)};
  11. std::array<WasmEdge_Value, 1> ret_val{};
  12. const WasmEdge_Result &res = WasmEdge_VMRunWasmFromBuffer(
  13. vm_ctx, (uint8_t *)buffer, buffer_size, func_name, params.data(),
  14. params.size(), ret_val.data(), ret_val.size());
  15. WasmEdge_VMDelete(vm_ctx);
  16. WasmEdge_ConfigureDelete(conf);
  17. WasmEdge_StringDelete(func_name);
  18. env->ReleaseByteArrayElements(image_bytes, buffer, 0);
  19. if (!WasmEdge_ResultOK(res)) {
  20. return -1;
  21. }
  22. return WasmEdge_ValueGetI32(ret_val[0]);
  23. }

The WebAssembly function

The factorial.wat is a handwritten WebAssembly script to compute factorial numbers. It is compiled into WebAssembly using the WABT tool.

Build dependencies

Android Studio and Gradle use CMake to build the C shared library. The CMakeLists.txt file builds the WasmEdge source into Android shared library files and embeds them into the final APK application. In this case, there is no seperate step to install WasmEdge share libraries onto the Android device.