Skip to content

Commit a7a4f64

Browse files
committed
Update to jni 0.22 and jni-sys 0.4
1 parent e686e80 commit a7a4f64

11 files changed

Lines changed: 419 additions & 519 deletions

File tree

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ resolver = "2"
33
members = ["android-activity"]
44

55
exclude = ["examples"]
6+
7+
[patch.crates-io]
8+
jni = { git = "https://github.com/jni-rs/jni-rs.git", branch = "release-0.22" }

android-activity/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ api-level-33 = ["api-level-30", "ndk/api-level-33"]
3636

3737
[dependencies]
3838
log = "0.4"
39-
jni-sys = "0.3"
4039
cesu8 = "1"
41-
jni = "0.21"
40+
jni = "0.22"
4241
ndk-sys = "0.6.0"
4342
ndk = { version = "0.9.0", default-features = false }
4443
ndk-context = "0.1.1"

android-activity/src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ pub(crate) enum InternalAppError {
2727
JniError(jni::errors::JniError),
2828
#[error("A Java Exception was thrown via a JNI method call")]
2929
JniException(String),
30+
// For internal errors that don't lead to a Java exception but are
31+
// still JNI related.
32+
#[error("A bad argument was passed to a JNI method: {0}")]
33+
JniBadArgument(String),
3034
#[error("A Java VM error")]
3135
JvmError(jni::errors::Error),
3236
#[error("Input unavailable")]
@@ -51,6 +55,7 @@ impl From<InternalAppError> for AppError {
5155
match value {
5256
InternalAppError::JniError(err) => AppError::JavaError(err.to_string()),
5357
InternalAppError::JniException(msg) => AppError::JavaError(msg),
58+
InternalAppError::JniBadArgument(msg) => AppError::JavaError(msg),
5459
InternalAppError::JvmError(err) => AppError::JavaError(err.to_string()),
5560
InternalAppError::InputUnavailable => AppError::InputUnavailable,
5661
}

android-activity/src/game_activity/ffi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#![allow(deref_nullptr)]
1313
#![allow(dead_code)]
1414

15-
use jni_sys::*;
15+
use jni::sys::*;
1616
use libc::{pthread_cond_t, pthread_mutex_t, pthread_t};
1717
use ndk_sys::{AAssetManager, AConfiguration, ALooper, ALooper_callbackFunc, ANativeWindow, ARect};
1818

android-activity/src/game_activity/mod.rs

Lines changed: 79 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ use std::sync::Weak;
88
use std::sync::{Arc, Mutex, RwLock};
99
use std::time::Duration;
1010

11+
use jni::objects::JObject;
12+
use jni::refs::Global;
1113
use libc::c_void;
1214
use log::{error, trace};
1315

14-
use jni_sys::*;
16+
use jni::sys::*;
1517

1618
use ndk_sys::ALooper_wake;
1719
use ndk_sys::{ALooper, ALooper_pollAll};
@@ -21,9 +23,11 @@ use ndk::configuration::Configuration;
2123
use ndk::native_window::NativeWindow;
2224

2325
use crate::error::InternalResult;
24-
use crate::input::{Axis, KeyCharacterMap, KeyCharacterMapBinding};
25-
use crate::jni_utils::{self, CloneJavaVM};
26-
use crate::util::{abort_on_panic, forward_stdio_to_logcat, log_panic, try_get_path_from_ptr};
26+
use crate::input::{device_key_character_map, Axis, KeyCharacterMap};
27+
use crate::util::{
28+
abort_on_panic, forward_stdio_to_logcat, init_android_main_thread, log_panic,
29+
try_get_path_from_ptr,
30+
};
2731
use crate::{
2832
AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
2933
};
@@ -119,32 +123,31 @@ impl AndroidAppWaker {
119123
}
120124

121125
impl AndroidApp {
122-
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, jvm: CloneJavaVM) -> Self {
123-
let mut env = jvm.get_env().unwrap(); // We attach to the thread before creating the AndroidApp
124-
125-
let key_map_binding = match KeyCharacterMapBinding::new(&mut env) {
126-
Ok(b) => b,
127-
Err(err) => {
128-
panic!("Failed to create KeyCharacterMap JNI bindings: {err:?}");
129-
}
130-
};
131-
132-
// Note: we don't use from_ptr since we don't own the android_app.config
133-
// and need to keep in mind that the Drop handler is going to call
134-
// AConfiguration_delete()
135-
let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
126+
pub(crate) unsafe fn from_ptr(ptr: NonNull<ffi::android_app>, jvm: jni::JavaVM) -> Self {
127+
// We attach to the thread before creating the AndroidApp
128+
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
129+
if let Err(err) = crate::input::jni_init(env) {
130+
panic!("Failed to init JNI bindings: {err:?}");
131+
};
136132

137-
Self {
138-
inner: Arc::new(RwLock::new(AndroidAppInner {
139-
jvm,
140-
native_app: NativeAppGlue { ptr },
141-
config: ConfigurationRef::new(config),
142-
native_window: Default::default(),
143-
key_map_binding: Arc::new(key_map_binding),
144-
key_maps: Mutex::new(HashMap::new()),
145-
input_receiver: Mutex::new(None),
146-
})),
147-
}
133+
// Note: we don't use from_ptr since we don't own the android_app.config
134+
// and need to keep in mind that the Drop handler is going to call
135+
// AConfiguration_delete()
136+
let config =
137+
Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
138+
139+
Ok(Self {
140+
inner: Arc::new(RwLock::new(AndroidAppInner {
141+
jvm: jvm.clone(),
142+
native_app: NativeAppGlue { ptr },
143+
config: ConfigurationRef::new(config),
144+
native_window: Default::default(),
145+
key_maps: Mutex::new(HashMap::new()),
146+
input_receiver: Mutex::new(None),
147+
})),
148+
})
149+
})
150+
.expect("Failed to create AndroidApp instance")
148151
}
149152
}
150153

@@ -251,14 +254,11 @@ impl NativeAppGlue {
251254

252255
#[derive(Debug)]
253256
pub struct AndroidAppInner {
254-
pub(crate) jvm: CloneJavaVM,
257+
pub(crate) jvm: jni::JavaVM,
255258
native_app: NativeAppGlue,
256259
config: ConfigurationRef,
257260
native_window: RwLock<Option<NativeWindow>>,
258261

259-
/// Shared JNI bindings for the `KeyCharacterMap` class
260-
key_map_binding: Arc<KeyCharacterMapBinding>,
261-
262262
/// A table of `KeyCharacterMap`s per `InputDevice` ID
263263
/// these are used to be able to map key presses to unicode
264264
/// characters
@@ -533,11 +533,7 @@ impl AndroidAppInner {
533533
let key_map = match guard.entry(device_id) {
534534
std::collections::hash_map::Entry::Occupied(occupied) => occupied.get().clone(),
535535
std::collections::hash_map::Entry::Vacant(vacant) => {
536-
let character_map = jni_utils::device_key_character_map(
537-
self.jvm.clone(),
538-
self.key_map_binding.clone(),
539-
device_id,
540-
)?;
536+
let character_map = device_key_character_map(self.jvm.clone(), device_id)?;
541537
vacant.insert(character_map.clone());
542538
character_map
543539
}
@@ -876,7 +872,7 @@ pub unsafe extern "C" fn Java_com_google_androidgamesdk_GameActivity_initializeN
876872
jasset_mgr: jobject,
877873
saved_state: jbyteArray,
878874
java_config: jobject,
879-
) -> jni_sys::jlong {
875+
) -> jlong {
880876
Java_com_google_androidgamesdk_GameActivity_initializeNativeCode_C(
881877
env,
882878
java_game_activity,
@@ -910,53 +906,56 @@ pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
910906
abort_on_panic(|| {
911907
let _join_log_forwarder = forward_stdio_to_logcat();
912908

913-
let jvm = unsafe {
909+
let (jvm, jni_activity) = unsafe {
914910
let jvm = (*(*native_app).activity).vm;
915911
let activity: jobject = (*(*native_app).activity).javaGameActivity;
916912
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
917-
918-
let jvm = CloneJavaVM::from_raw(jvm).unwrap();
919-
// Since this is a newly spawned thread then the JVM hasn't been attached
920-
// to the thread yet. Attach before calling the applications main function
921-
// so they can safely make JNI calls
922-
jvm.attach_current_thread_permanently().unwrap();
923-
jvm
913+
(jni::JavaVM::from_raw(jvm), activity)
924914
};
915+
// Note: At this point we can assume jni::JavaVM::singleton is initialized
916+
917+
// Note: the GameActivity implementation will have already attached the main thread to the
918+
// JVM before calling _rust_glue_entry so we don't to set the thread name via
919+
// attach_current_thread_with_config since that won't actually create a new attachment.
920+
//
921+
// Calling .attach_current_thread will ensure that the `jni` crate knows about the
922+
// attachment, as a convenience.
923+
jvm.attach_current_thread(|env| -> jni::errors::Result<()> {
924+
// SAFETY: We know jni_activity is a valid JNI global ref to an Activity instance
925+
let jni_activity = unsafe { env.as_cast_raw::<Global<JObject>>(&jni_activity)? };
926+
927+
if let Err(err) = init_android_main_thread(&jvm, &jni_activity) {
928+
eprintln!("Failed to name Java thread and set thread context class loader: {err}");
929+
}
925930

926-
unsafe {
927-
// Name thread - this needs to happen here after attaching to a JVM thread,
928-
// since that changes the thread name to something like "Thread-2".
929-
let thread_name = std::ffi::CStr::from_bytes_with_nul(b"android_main\0").unwrap();
930-
libc::pthread_setname_np(libc::pthread_self(), thread_name.as_ptr());
931-
932-
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap(), jvm.clone());
933-
934-
// We want to specifically catch any panic from the application's android_main
935-
// so we can finish + destroy the Activity gracefully via the JVM
936-
catch_unwind(|| {
937-
// XXX: If we were in control of the Java Activity subclass then
938-
// we could potentially run the android_main function via a Java native method
939-
// springboard (e.g. call an Activity subclass method that calls a jni native
940-
// method that then just calls android_main()) that would make sure there was
941-
// a Java frame at the base of our call stack which would then be recognised
942-
// when calling FindClass to lookup a suitable classLoader, instead of
943-
// defaulting to the system loader. Without this then it's difficult for native
944-
// code to look up non-standard Java classes.
945-
android_main(app);
946-
})
947-
.unwrap_or_else(log_panic);
948-
949-
// Let JVM know that our Activity can be destroyed before detaching from the JVM
950-
//
951-
// "Note that this method can be called from any thread; it will send a message
952-
// to the main thread of the process where the Java finish call will take place"
953-
ffi::GameActivity_finish((*native_app).activity);
931+
unsafe {
932+
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap(), jvm.clone());
933+
// We want to specifically catch any panic from the application's android_main
934+
// so we can finish + destroy the Activity gracefully via the JVM
935+
catch_unwind(|| {
936+
// XXX: If we were in control of the Java Activity subclass then
937+
// we could potentially run the android_main function via a Java native method
938+
// springboard (e.g. call an Activity subclass method that calls a jni native
939+
// method that then just calls android_main()) that would make sure there was
940+
// a Java frame at the base of our call stack which would then be recognised
941+
// when calling FindClass to lookup a suitable classLoader, instead of
942+
// defaulting to the system loader. Without this then it's difficult for native
943+
// code to look up non-standard Java classes.
944+
android_main(app);
945+
})
946+
.unwrap_or_else(log_panic);
947+
948+
// Let JVM know that our Activity can be destroyed before detaching from the JVM
949+
//
950+
// "Note that this method can be called from any thread; it will send a message
951+
// to the main thread of the process where the Java finish call will take place"
952+
ffi::GameActivity_finish((*native_app).activity);
954953

955-
// This should detach automatically but lets detach explicitly to avoid depending
956-
// on the TLS trickery in `jni-rs`
957-
jvm.detach_current_thread();
954+
ndk_context::release_android_context();
955+
}
958956

959-
ndk_context::release_android_context();
960-
}
957+
Ok(())
958+
})
959+
.expect("Failed to attach thread to JVM");
961960
})
962961
}

0 commit comments

Comments
 (0)