Checked bidirectional Java/MoonBit JNI runtime and Android host bridge.
| Package | Purpose |
|---|---|
| Nanaloveyuki/ajni | Checked class, type, method and native-method declarations |
| Nanaloveyuki/ajni/runtime | Sessions, objects, calls, fields, arrays, exceptions and callbacks |
| Nanaloveyuki/ajni/unsafe | Explicit raw JNI host/ref/function-pointer operations |
| Nanaloveyuki/ajni/invocation | Optional desktop JVM creation using dynlib |
| Nanaloveyuki/ajni/android | Android host adapter |
| Nanaloveyuki/ajni/webview | Optional Android WebView integration |
moon add Nanaloveyuki/ajniimport {
"Nanaloveyuki/ajni/webview",
}let string = try! @ajni.JniClass::parse("java/lang/String")
let signature = try! @ajni.JniMethod::new(
[@ajni.JniType::object(string)],
return_type=@ajni.JniType::boolean(),
)
let registration = try! @ajni.NativeMethod::new("nativeAcceptsString", signature)
println(registration.descriptor()) // (Ljava/lang/String;)Zlet session = @runtime.Session::current()
let builder = session.find_class(@ajni.JniClass::parse("java/lang/StringBuilder"))
let instance = builder.lookup_constructor([]).new([])
let append = builder.instance_method("append", @ajni.JniMethod::new(
[@ajni.JniType::object(@ajni.JniClass::parse("java/lang/String"))],
return_type=@ajni.JniType::object(@ajni.JniClass::parse("java/lang/StringBuilder")),
))
ignore(append.call([@runtime.Object(Some(session.new_string("MoonBit")))], receiver=instance))
let callback = session.callback(
@ajni.JniMethod::new([@ajni.JniType::int()], return_type=@ajni.JniType::int()),
(args) => match args[0] {
Int(value) => @runtime.Int(value + 1)
_ => raise @runtime.InvalidArgument("Expected int")
},
)
// Pass callback.object() to Java; AjniCallback.invoke(41) returns Integer(42).import {
"Nanaloveyuki/ajni/android",
}
let subscription = @android.install_event_handler(event => match event {
@android.AndroidEvent::Lifecycle(@android.Lifecycle::Resumed) => println("resumed")
@android.AndroidEvent::UiTask => println("Android main Looper callback")
_ => ()
})
// Remove the observer before application shutdown.
@android.remove_event_handler(subscription)import dev.nanaloveyuki.ajni.host.NativeBridge
NativeBridge.initialize(applicationContext)
NativeBridge.attachWebViewContainer(container)
// Forward every Activity lifecycle state on the main thread.
NativeBridge.lifecycle(state)
// During Activity teardown:
NativeBridge.detachWebViewContainer(container)
NativeBridge.shutdown()import {
"Nanaloveyuki/ajni/webview",
}
let subscription = @webview.install_event_handler(event => match event.kind {
@webview.EventKind::Created(_) => println("ready")
@webview.EventKind::ScriptResult(request_id, json) =>
println("\{request_id}: \{json}")
@webview.EventKind::PageMessage(body, origin, _is_main_frame) =>
println("\{origin}: \{body}")
@webview.EventKind::AssetRequest(request) => println(request.path)
@webview.EventKind::OperationFailed(operation_id, message) =>
println("\{operation_id}: \{message}")
_ => ()
})
try! @webview.create(
1L,
"https://app.example.test",
@webview.InitialContent::Url("https://app.example.test/assets/index.html"),
document_start_scripts=["globalThis.appReady = true"],
operation_id="create-browser",
)
try! @webview.eval(1L, "document.title", "title-request")
try! @webview.destroy(1L, operation_id="destroy-browser")
@webview.remove_event_handler(subscription)moon fmt --check
moon check --target native
moon test --target native --build-only
moon info --target native
python3 scripts/check-package-files.pymoon update
python3 scripts/run-jvm-smoke.pypython3 scripts/check-jni-contract.py --class-dir android/host/build/tmp/kotlin-classes/debug \
--ndk "$ANDROID_NDK_HOME" --moon-home "$MOON_HOME"$env:ANDROID_SDK_ROOT = "C:\path\to\Android\Sdk"
$env:ANDROID_NDK_HOME = "C:\path\to\android-ndk-r29"
$env:ANDROID_SERIAL = "emulator-5554"
.\android\gradlew.bat -p android :app:connectedDebugAndroidTest///|
test {
let string = try! @ajni.JniClass::parse("java/lang/String")
let signature = try! @ajni.JniMethod::new(
[@ajni.JniType::object(string)],
return_type=@ajni.JniType::boolean(),
)
let registration = try! @ajni.NativeMethod::new(
"nativeAcceptsString", signature,
)
assert_eq(registration.descriptor(), "(Ljava/lang/String;)Z")
}pub(all) suberror JniError {
NotInitialized
RuntimeDestroyed
NativeFailure(String)
JavaException(String)
InvalidClassName(String)
InvalidNativeMethodName(String)
DescriptorTooLong
TooManyParameterSlots
TooManyArrayDimensions
} derive(Debug)fn decode_utf8(bytes : BytesView) -> Stringfn encode_utf8(text : String) -> BytesInstall
Download zipChecked bidirectional Java/MoonBit JNI runtime and Android host bridge.