84 lines
2.8 KiB
Rust
84 lines
2.8 KiB
Rust
//! Android-specific JNI lifecycle helpers.
|
|
//!
|
|
//! On Android the Flutter engine starts our cdylib but does not push
|
|
//! anything into `ndk_context` (the global the Android Oboe backend
|
|
//! reads to find Android audio services). Without that, the first
|
|
//! audio call hangs / fails.
|
|
//!
|
|
//! We solve this two ways:
|
|
//!
|
|
//! 1. `JNI_OnLoad` captures the `JavaVM*` as soon as the library is
|
|
//! loaded.
|
|
//! 2. We expose a `Java_app_chanora_chanora_1flutter_MainActivity_initChanoraContext`
|
|
//! JNI function the Kotlin `MainActivity.onCreate` calls with its
|
|
//! application Context. That JNI function pushes both the
|
|
//! `JavaVM*` and a `Context` global ref into `ndk_context`.
|
|
//!
|
|
//! After that, the Android Oboe backend can open the default
|
|
//! input/output devices.
|
|
|
|
#![cfg(target_os = "android")]
|
|
|
|
use std::sync::Once;
|
|
|
|
use jni::objects::{JClass, JObject};
|
|
use jni::sys::{jint, JNI_VERSION_1_6};
|
|
use jni::JNIEnv;
|
|
use log::{error, info};
|
|
|
|
/// Stash the JavaVM pointer between JNI_OnLoad and initContext.
|
|
static mut JAVA_VM: *mut std::ffi::c_void = std::ptr::null_mut();
|
|
static INIT_ONCE: Once = Once::new();
|
|
|
|
/// JNI entry point invoked by the Android runtime when the cdylib is
|
|
/// loaded via `System.loadLibrary` (which `flutter_rust_bridge` does
|
|
/// at `RustLib.init`).
|
|
#[no_mangle]
|
|
pub extern "system" fn JNI_OnLoad(
|
|
vm: *mut std::ffi::c_void,
|
|
_reserved: *mut std::ffi::c_void,
|
|
) -> jint {
|
|
// Safety: Android guarantees `vm` is a valid JavaVM* for the
|
|
// lifetime of the library.
|
|
unsafe {
|
|
JAVA_VM = vm;
|
|
}
|
|
JNI_VERSION_1_6
|
|
}
|
|
|
|
/// Called by `MainActivity.onCreate` with the application Context.
|
|
/// Initialises the `ndk_context` so the Oboe backend can locate
|
|
/// the Android audio services.
|
|
///
|
|
/// Symbol mangling note: Kotlin / JNI mangles the underscore in
|
|
/// `chanora_flutter` to `chanora_1flutter` because the literal `_`
|
|
/// in a JNI symbol means package separator.
|
|
#[no_mangle]
|
|
pub extern "system" fn Java_app_chanora_chanora_1flutter_MainActivity_initChanoraContext<'local>(
|
|
env: JNIEnv<'local>,
|
|
_class: JClass<'local>,
|
|
context: JObject<'local>,
|
|
) {
|
|
INIT_ONCE.call_once(|| {
|
|
let global = match env.new_global_ref(&context) {
|
|
Ok(g) => g,
|
|
Err(e) => {
|
|
error!("initChanoraContext: new_global_ref failed: {e}");
|
|
return;
|
|
}
|
|
};
|
|
let raw_ctx = global.as_obj().as_raw() as *mut std::ffi::c_void;
|
|
// Leak the global ref so it survives for the process lifetime
|
|
// (ndk_context borrows the pointer).
|
|
std::mem::forget(global);
|
|
unsafe {
|
|
ndk_context::initialize_android_context(JAVA_VM, raw_ctx);
|
|
}
|
|
info!(
|
|
"initChanoraContext: ndk_context initialised (vm={:p}, ctx={:p})",
|
|
unsafe { JAVA_VM },
|
|
raw_ctx
|
|
);
|
|
});
|
|
}
|