Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
{{#if uses_cleartext_traffic}}android:usesCleartextTraffic="{{ uses_cleartext_traffic }}"{{/if}}
{{#if large_heap}}android:largeHeap="{{ large_heap }}"{{/if}}
android:networkSecurityConfig="@xml/network_security_config">
<activity android:configChanges="orientation|screenLayout|screenSize|keyboardHidden" android:exported="true"
<activity android:configChanges="orientation|screenLayout|screenSize|smallestScreenSize|keyboardHidden|keyboard|navigation|touchscreen|density" android:exported="true"
android:label="@string/app_name" android:launchMode="singleTask" android:name="dev.dioxus.main.MainActivity">
<meta-data android:name="android.app.lib_name" android:value="{{ lib_name }}" />
<meta-data android:name="android.app.func_name" android:value="ANativeActivity_onCreate" />
Expand Down
1 change: 1 addition & 0 deletions packages/desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ jni = "0.21.1"
ndk = { version = "0.9.0" }
ndk-sys = { version = "0.6.0" }
ndk-context = { version = "0.1.1" }
manganis = { workspace = true }

# use native tls on other platforms
[target.'cfg(not(target_os = "android"))'.dependencies]
Expand Down
37 changes: 21 additions & 16 deletions packages/desktop/src/mobile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,39 @@ pub extern "C" fn start_app() {
use std::any::Any;

// tao 0.35 dropped its automatic `ndk_context::initialize_android_context` call
// (see https://github.com/tauri-apps/tao/issues/1220). Many android-aware crates —
// including parts of wry itself — call `ndk_context::android_context()` and panic if
// it's uninitialized, which then poisons wry's static mutexes and turns the original
// panic into a confusing `PoisonError` at the next JNI callback. Initialize it here
// before handing off to wry's own setup.
//
// Guarded by `Once` because `WryActivity.onCreate` (and therefore this setup) runs
// again on activity re-creation — rotation, theme changes, back/foreground cycles —
// and `ndk_context::initialize_android_context` asserts `previous.is_none()`, which
// would abort the process on every re-entry. The global only needs the JavaVM + an
// activity-like Context pointer for consumers to attach a JNI thread; we don't need
// to refresh it per-activity.
// (see https://github.com/tauri-apps/tao/issues/1220), and many android-aware crates panic
// without it. It gets the Application context because activities are recreated while the
// process lives.
unsafe fn android_setup(
package: &str,
env: ::wry::prelude::JNIEnv<'_>,
mut env: ::wry::prelude::JNIEnv<'_>,
looper: &::ndk::looper::ThreadLooper,
activity: ::wry::prelude::GlobalRef,
) {
static NDK_CONTEXT_INIT: std::sync::Once = std::sync::Once::new();
NDK_CONTEXT_INIT.call_once(|| {
static APPLICATION: std::sync::OnceLock<::wry::prelude::GlobalRef> =
std::sync::OnceLock::new();
APPLICATION.get_or_init(|| {
let application = env
.call_method(
activity.as_obj(),
"getApplicationContext",
"()Landroid/content/Context;",
&[],
)
.and_then(|context| context.l())
.and_then(|context| env.new_global_ref(context))
.expect("an activity has an Application context");
let vm = env.get_java_vm().unwrap();
// SAFETY: `APPLICATION` keeps this global reference alive for the rest of the process.
unsafe {
::ndk_context::initialize_android_context(
vm.get_java_vm_pointer() as *mut _,
activity.as_obj().as_raw() as *mut _,
application.as_obj().as_raw() as *mut _,
);
}
application
});
::manganis::android::set_current_activity(activity.clone());
unsafe {
wry::android_setup(package, env, looper, activity);
}
Expand Down
52 changes: 29 additions & 23 deletions packages/manganis/manganis/src/android/activity.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
use jni::{JNIEnv, JavaVM, objects::JObject};
use std::sync::OnceLock;
use jni::{
JNIEnv, JavaVM,
objects::{GlobalRef, JObject},
};
use std::sync::{OnceLock, RwLock};

/// Cached reference to the Android activity.
static ACTIVITY: OnceLock<jni::objects::GlobalRef> = OnceLock::new();
/// The live Android activity, replaced whenever the system recreates it.
static CURRENT_ACTIVITY: RwLock<Option<GlobalRef>> = RwLock::new(None);
static JAVA_VM: OnceLock<JavaVM> = OnceLock::new();

/// Execute a JNI operation with a cached activity reference.
/// Make `activity` the one [`with_activity`] hands out, called by the renderer for every new activity.
pub fn set_current_activity(activity: GlobalRef) {
*CURRENT_ACTIVITY
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = Some(activity);
}

/// Execute a JNI operation with the current activity.
///
/// This function handles the boilerplate of getting the JavaVM and Activity
/// references, caching them for subsequent calls. It's the foundation for
/// most Android mobile API operations.
/// Without an activity registered through [`set_current_activity`], the closure receives the
/// `ndk_context` context.
///
/// # Arguments
///
Expand Down Expand Up @@ -44,20 +53,17 @@ where
};
let mut env = vm.attach_current_thread().ok()?;

let activity = if let Some(activity) = ACTIVITY.get() {
activity
} else {
let raw_activity = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
let global = env.new_global_ref(&raw_activity).ok()?;
match ACTIVITY.set(global) {
Ok(()) => ACTIVITY.get().unwrap(),
Err(global) => {
drop(global);
ACTIVITY.get()?
}
// The clone keeps the reference alive while `f` runs, even if the activity is replaced meanwhile.
let activity = CURRENT_ACTIVITY
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.clone();
match activity {
Some(activity) => f(&mut env, activity.as_obj()),
None => {
// SAFETY: `ndk_context` holds this global reference for as long as it is initialised.
let context = unsafe { JObject::from_raw(ctx.context() as jni::sys::jobject) };
f(&mut env, &context)
}
};

let activity_obj = activity.as_obj();
f(&mut env, &activity_obj)
}
}