app: do proper JVM thread mgmt. previously we call AttachThread() everywhere which is wrong, it should be called once per thread and then DetachThread() to cleanup. use thread local storage + Drop to make a thread guard for this.

This commit is contained in:
jkds
2026-01-05 06:16:41 +01:00
parent 6e23e98d26
commit a9e1f04a9f
6 changed files with 89 additions and 26 deletions

View File

@@ -22,7 +22,7 @@ DEBUG_FEATURES = --features=enable-filelog,enable-plugins
#DEV_FEATURES = --features=enable-filelog,enable-netdebug,emulate-android
#DEV_FEATURES = --features=enable-filelog,enable-netdebug,enable-plugins
DEV_FEATURES = --features=schema-test
DEV_FEATURES = --no-default-features --features=schema-app
default: build-release
./darkfi-app

View File

@@ -22,13 +22,15 @@ use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
pub mod insets;
pub mod textinput;
mod util;
pub(self) mod util;
pub mod vid;
use util::get_jni_env;
macro_rules! call_mainactivity_int_method {
($method:expr, $sig:expr $(, $args:expr)*) => {{
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
ndk_utils::call_int_method!(env, android::ACTIVITY, $method, $sig $(, $args)*)
}
}};
@@ -36,7 +38,7 @@ macro_rules! call_mainactivity_int_method {
macro_rules! call_mainactivity_str_method {
($method:expr) => {{
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let text = ndk_utils::call_object_method!(
env,
android::ACTIVITY,
@@ -50,7 +52,7 @@ macro_rules! call_mainactivity_str_method {
macro_rules! call_mainactivity_float_method {
($method:expr) => {{
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
ndk_utils::call_method!(CallFloatMethod, env, android::ACTIVITY, $method, "()F")
}
}};
@@ -58,7 +60,7 @@ macro_rules! call_mainactivity_float_method {
macro_rules! call_mainactivity_bool_method {
($method:expr) => {{
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
ndk_utils::call_method!(CallBooleanMethod, env, android::ACTIVITY, $method, "()Z") !=
0u8
}

View File

@@ -23,7 +23,7 @@ use std::{
sync::{Arc, OnceLock},
};
use super::{AndroidTextInputState, SharedStatePtr};
use super::{super::util::get_jni_env, AndroidTextInputState, SharedStatePtr};
macro_rules! t { ($($arg:tt)*) => { trace!(target: "android::textinput::gametextinput", $($arg)*); } }
macro_rules! w { ($($arg:tt)*) => { warn!(target: "android::textinput::gametextinput", $($arg)*); } }
@@ -57,7 +57,7 @@ pub struct GameTextInput {
impl GameTextInput {
pub fn new() -> Self {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let find_class = (**env).FindClass.unwrap();
@@ -186,7 +186,7 @@ impl GameTextInput {
return
};
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let jstate = self.state_to_java(state);
call_void_method!(env, input_connection, "setState", "(Ltextinput/State;)V", jstate);
@@ -201,7 +201,7 @@ impl GameTextInput {
return Err(())
};
let is_success = unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
call_bool_method!(env, input_connection, "setSelection", "(II)Z", start, end)
};
if is_success == 0u8 {
@@ -212,7 +212,7 @@ impl GameTextInput {
pub fn set_input_connection(&self, input_connection: ndk_sys::jobject) {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let mut ic = self.input_connection.write();
if let Some(old_ref) = *ic {
let delete_global_ref = (**env).DeleteGlobalRef.unwrap();
@@ -242,7 +242,7 @@ impl GameTextInput {
return
};
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let call_void_method = (**env).CallVoidMethod.unwrap();
call_void_method(
env,
@@ -260,7 +260,7 @@ impl GameTextInput {
return
};
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let call_void_method = (**env).CallVoidMethod.unwrap();
call_void_method(
env,
@@ -278,7 +278,7 @@ impl GameTextInput {
return
};
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let call_void_method = (**env).CallVoidMethod.unwrap();
call_void_method(env, input_connection, self.restart_input_method);
}
@@ -286,7 +286,7 @@ impl GameTextInput {
fn state_to_java(&self, state: &AndroidTextInputState) -> ndk_sys::jobject {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let new_string_utf = (**env).NewStringUTF.unwrap();
let text_str = CString::new(state.text.as_str()).unwrap();
let jtext = new_string_utf(env, text_str.as_ptr());
@@ -317,7 +317,7 @@ impl GameTextInput {
fn state_from_java(&self, event_state: ndk_sys::jobject) -> AndroidTextInputState {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let get_object_field = (**env).GetObjectField.unwrap();
let jtext =
get_object_field(env, event_state, self.state_class_info.text) as ndk_sys::jstring;
@@ -355,7 +355,7 @@ impl GameTextInput {
impl Drop for GameTextInput {
fn drop(&mut self) {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let delete_global_ref = (**env).DeleteGlobalRef.unwrap();
if self.input_connection_class != std::ptr::null_mut() {
delete_global_ref(env, self.input_connection_class);

View File

@@ -16,7 +16,57 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use miniquad::native::android::ndk_sys;
use miniquad::native::android::{self, ndk_sys};
use std::cell::RefCell;
thread_local! {
static JNI_ENV: RefCell<JniEnvHolder> = RefCell::new(JniEnvHolder {
env: std::ptr::null_mut(),
vm: std::ptr::null_mut(),
});
}
struct JniEnvHolder {
env: *mut ndk_sys::JNIEnv,
vm: *mut ndk_sys::JavaVM,
}
impl Drop for JniEnvHolder {
fn drop(&mut self) {
assert!(!self.env.is_null());
unsafe {
let detach_current_thread = (**self.vm).DetachCurrentThread.unwrap();
let _ = detach_current_thread(self.vm);
}
}
}
/// Get the JNIEnv for the current thread, attaching if necessary.
/// The returned pointer is cached per-thread and automatically detached
/// when the thread exits.
pub unsafe fn get_jni_env() -> *mut ndk_sys::JNIEnv {
JNI_ENV.with(|holder| {
let mut holder = holder.borrow_mut();
if !holder.env.is_null() {
return holder.env;
}
// Call miniquad's attach_jni_env to get the env
let env = android::attach_jni_env();
assert!(!env.is_null());
// Retrieve the JavaVM from the JNIEnv
let get_java_vm = (**env).GetJavaVM.unwrap();
let mut vm: *mut ndk_sys::JavaVM = std::ptr::null_mut();
let res = get_java_vm(env, &mut vm);
assert!(res == 0);
assert!(!vm.is_null());
holder.env = env;
holder.vm = vm;
env
})
}
/// Check for pending Java exceptions and log them
///

View File

@@ -25,6 +25,8 @@ use std::{
sync::{mpsc, LazyLock},
};
use super::util::get_jni_env;
pub struct DecodedFrame {
pub width: usize,
pub height: usize,
@@ -113,7 +115,7 @@ pub struct VideoDecoderHandle {
impl Drop for VideoDecoderHandle {
fn drop(&mut self) {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let delete_local_ref = (**env).DeleteLocalRef.unwrap();
delete_local_ref(env, self.obj);
}
@@ -122,7 +124,7 @@ impl Drop for VideoDecoderHandle {
pub fn videodecoder_init(path: &str) -> Option<VideoDecoderHandle> {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
let decoder_obj = ndk_utils::call_object_method!(
env,
@@ -156,14 +158,14 @@ pub fn videodecoder_init(path: &str) -> Option<VideoDecoderHandle> {
pub fn videodecoder_set_id(decoder_obj: ndk_sys::jobject, id: usize) {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
ndk_utils::call_void_method!(env, decoder_obj, "setDecoderId", "(I)V", id as i32);
}
}
pub fn videodecoder_decode_all(decoder_obj: ndk_sys::jobject) -> i32 {
unsafe {
let env = android::attach_jni_env();
let env = get_jni_env();
ndk_utils::call_int_method!(env, decoder_obj, "decodeAll", "()I")
}
}

View File

@@ -18,6 +18,7 @@
use sled_overlay::sled;
use super::chat::populate_tree;
use crate::{
app::{
node::{
@@ -33,20 +34,28 @@ use crate::{
ui::{BaseEdit, BaseEditType, ChatView, Layer, Text, VectorArt, VectorShape, Video},
util::i18n::I18nBabelFish,
};
use super::chat::populate_tree;
const LIGHTMODE: bool = false;
#[cfg(target_os = "android")]
mod ui_consts {
pub const CHATDB_PATH: &str = "/data/data/darkfi.app/chatdb/";
use crate::android::get_appdata_path;
use std::path::PathBuf;
pub fn get_chatdb_path() -> PathBuf {
get_appdata_path().join("chatdb")
}
//pub const KING_PATH: &str = "king.png";
pub const VID_PATH: &str = "forest_720x1280.mp4";
}
#[cfg(not(target_os = "android"))]
mod ui_consts {
pub const CHATDB_PATH: &str = "chatdb";
use std::path::PathBuf;
pub fn get_chatdb_path() -> PathBuf {
"chatdb"
}
//pub const KING_PATH: &str = "assets/king.png";
pub const VID_PATH: &str = "assets/forest_1920x1080.ivf";
}
@@ -342,7 +351,7 @@ pub async fn make(app: &App, window: SceneNodePtr, i18n_fish: &I18nBabelFish) {
prop.set_f32(atom, Role::App, 3, 1.).unwrap();
}
let db = sled::open(CHATDB_PATH).expect("cannot open sleddb");
let db = sled::open(get_chatdb_path()).expect("cannot open sleddb");
let chat_tree = db.open_tree(b"chat").unwrap();
if chat_tree.is_empty() {
populate_tree(&chat_tree);