Only init ndk-context once with an Application ref

Instead of initializing `ndk-context` with an `Activity` reference (for
the `android.context.Context` subclass) we now initialize with
an `android.app.Application` reference (also an
`android.context.Context` subclass).

The benefit of this is that we can strictly initialize `ndk-context`
once (via a `OnceLock`) so there's no risk of a panic in case an
application starts more than one Activity within the same process.

Fixes: #58
Fixes: #228
This commit is contained in:
Robert Bragg
2026-03-03 01:41:40 +00:00
parent 2b20da72bd
commit 0f49d96fa0
3 changed files with 32 additions and 7 deletions
@@ -970,7 +970,6 @@ pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
let (jvm, jni_activity) = unsafe {
let jvm = (*(*native_app).activity).vm;
let activity: jobject = (*(*native_app).activity).javaGameActivity;
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
(jni::JavaVM::from_raw(jvm), activity)
};
// Note: At this point we can assume jni::JavaVM::singleton is initialized
@@ -1011,8 +1010,6 @@ pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
// "Note that this method can be called from any thread; it will send a message
// to the main thread of the process where the Java finish call will take place"
ffi::GameActivity_finish((*native_app).activity);
ndk_context::release_android_context();
}
Ok(())
@@ -875,7 +875,6 @@ fn rust_glue_entry(rust_glue: NativeActivityGlue, activity: *mut ndk_sys::ANativ
let (jvm, jni_activity) = unsafe {
let jvm: *mut jni::sys::JavaVM = (*activity).vm.cast();
let jni_activity: jni::sys::jobject = (*activity).clazz as _; // Completely bogus name; this is the _instance_ not class pointer
ndk_context::initialize_android_context(jvm.cast(), jni_activity.cast());
(jni::JavaVM::from_raw(jvm), jni_activity)
};
// Note: At this point we can assume jni::JavaVM::singleton is initialized
@@ -923,8 +922,6 @@ fn rust_glue_entry(rust_glue: NativeActivityGlue, activity: *mut ndk_sys::ANativ
// "Note that this method can be called from any thread; it will send a message
// to the main thread of the process where the Java finish call will take place"
ndk_sys::ANativeActivity_finish(activity);
ndk_context::release_android_context();
}
rust_glue.notify_main_thread_stopped_running();
+32 -1
View File
@@ -1,5 +1,5 @@
use jni::{
jni_str,
jni_sig, jni_str,
objects::{JObject, JString, JThread},
vm::JavaVM,
};
@@ -12,6 +12,7 @@ use std::{
fd::{FromRawFd as _, RawFd},
raw::c_char,
},
sync::OnceLock,
};
pub fn try_get_path_from_ptr(path: *const c_char) -> Option<std::path::PathBuf> {
@@ -117,6 +118,24 @@ pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
})
}
static NDK_CONTEXT_ONCE: OnceLock<()> = OnceLock::new();
// Get the Application instance from the Activity
fn get_application<'local, 'any>(
env: &mut jni::Env<'local>,
activity: &JObject<'any>,
) -> jni::errors::Result<JObject<'local>> {
let app = env
.call_method(
activity,
jni_str!("getApplication"),
jni_sig!(() -> android.app.Application),
&[],
)?
.l()?;
Ok(app)
}
/// Name the Java Thread + native thread "android_main" and set the Java Thread context class loader
/// so that jni code can more-easily find non-system Java classes.
pub(crate) fn init_android_main_thread(
@@ -125,6 +144,18 @@ pub(crate) fn init_android_main_thread(
) -> jni::errors::Result<()> {
vm.with_local_frame(10, |env| -> jni::errors::Result<()> {
let activity_class = env.get_object_class(jni_activity)?;
if let Ok(application) = get_application(env, jni_activity) {
NDK_CONTEXT_ONCE.get_or_init(|| unsafe {
let app_global = env
.new_global_ref(application)
.expect("Failed to create global ref for Application");
// Make sure we don't delete the global reference via Drop
let app_global = app_global.into_raw();
ndk_context::initialize_android_context(vm.get_raw().cast(), app_global.cast());
});
}
let class_loader = activity_class.get_class_loader(env)?;
let thread = JThread::current_thread(env)?;