Skip to content

Commit 79e03e0

Browse files
authored
Merge pull request #81 from rib/finish-activity
Call Activity.finish() when android_main returns
2 parents 120d2f6 + 3843a7c commit 79e03e0

File tree

4 files changed

+95
-57
lines changed

4 files changed

+95
-57
lines changed

android-activity/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
33
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
44

55
## [Unreleased]
6+
### Changed
7+
- The `Activity.finish()` method is now called when `android_main` returns so the `Activity` will be destroyed ([#67](https://github.com/rust-mobile/android-activity/issues/67))
68

79
## [0.4.1] - 2022-02-16
810
### Added

android-activity/src/game_activity/mod.rs

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::io::{BufRead, BufReader};
66
use std::marker::PhantomData;
77
use std::ops::Deref;
88
use std::os::unix::prelude::*;
9+
use std::panic::catch_unwind;
910
use std::ptr::NonNull;
1011
use std::sync::{Arc, RwLock};
1112
use std::time::Duration;
@@ -23,7 +24,7 @@ use ndk::asset::AssetManager;
2324
use ndk::configuration::Configuration;
2425
use ndk::native_window::NativeWindow;
2526

26-
use crate::util::{abort_on_panic, android_log};
27+
use crate::util::{abort_on_panic, android_log, log_panic};
2728
use crate::{
2829
util, AndroidApp, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect, WindowManagerFlags,
2930
};
@@ -603,7 +604,8 @@ extern "Rust" {
603604
// `app_main` function. This is run on a dedicated thread spawned
604605
// by android_native_app_glue.
605606
#[no_mangle]
606-
pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) {
607+
#[allow(unused_unsafe)] // Otherwise rust 1.64 moans about using unsafe{} in unsafe functions
608+
pub unsafe extern "C" fn _rust_glue_entry(native_app: *mut ffi::android_app) {
607609
abort_on_panic(|| {
608610
// Maybe make this stdout/stderr redirection an optional / opt-in feature?...
609611
let mut logpipe: [RawFd; 2] = Default::default();
@@ -627,34 +629,51 @@ pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) {
627629
}
628630
});
629631

630-
let jvm: *mut JavaVM = (*(*app).activity).vm;
631-
let activity: jobject = (*(*app).activity).javaGameActivity;
632-
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
632+
let jvm = unsafe {
633+
let jvm = (*(*native_app).activity).vm;
634+
let activity: jobject = (*(*native_app).activity).javaGameActivity;
635+
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
636+
637+
// Since this is a newly spawned thread then the JVM hasn't been attached
638+
// to the thread yet. Attach before calling the applications main function
639+
// so they can safely make JNI calls
640+
let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut();
641+
if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread {
642+
attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut());
643+
}
633644

634-
let app = AndroidApp::from_ptr(NonNull::new(app).unwrap());
645+
jvm
646+
};
635647

636-
// Since this is a newly spawned thread then the JVM hasn't been attached
637-
// to the thread yet. Attach before calling the applications main function
638-
// so they can safely make JNI calls
639-
let mut jenv_out: *mut core::ffi::c_void = std::ptr::null_mut();
640-
if let Some(attach_current_thread) = (*(*jvm)).AttachCurrentThread {
641-
attach_current_thread(jvm, &mut jenv_out, std::ptr::null_mut());
642-
}
648+
unsafe {
649+
let app = AndroidApp::from_ptr(NonNull::new(native_app).unwrap());
650+
651+
// We want to specifically catch any panic from the application's android_main
652+
// so we can finish + destroy the Activity gracefully via the JVM
653+
catch_unwind(|| {
654+
// XXX: If we were in control of the Java Activity subclass then
655+
// we could potentially run the android_main function via a Java native method
656+
// springboard (e.g. call an Activity subclass method that calls a jni native
657+
// method that then just calls android_main()) that would make sure there was
658+
// a Java frame at the base of our call stack which would then be recognised
659+
// when calling FindClass to lookup a suitable classLoader, instead of
660+
// defaulting to the system loader. Without this then it's difficult for native
661+
// code to look up non-standard Java classes.
662+
android_main(app);
663+
})
664+
.unwrap_or_else(|panic| log_panic(panic));
665+
666+
// Let JVM know that our Activity can be destroyed before detaching from the JVM
667+
//
668+
// "Note that this method can be called from any thread; it will send a message
669+
// to the main thread of the process where the Java finish call will take place"
670+
ffi::GameActivity_finish((*native_app).activity);
643671

644-
// XXX: If we were in control of the Java Activity subclass then
645-
// we could potentially run the android_main function via a Java native method
646-
// springboard (e.g. call an Activity subclass method that calls a jni native
647-
// method that then just calls android_main()) that would make sure there was
648-
// a Java frame at the base of our call stack which would then be recognised
649-
// when calling FindClass to lookup a suitable classLoader, instead of
650-
// defaulting to the system loader. Without this then it's difficult for native
651-
// code to look up non-standard Java classes.
652-
android_main(app);
653-
654-
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
655-
detach_current_thread(jvm);
656-
}
672+
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
673+
detach_current_thread(jvm);
674+
}
657675

658-
ndk_context::release_android_context();
676+
ndk_context::release_android_context();
677+
}
659678
})
660679
}

android-activity/src/native_activity/glue.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ use std::{
88
io::{BufRead, BufReader},
99
ops::Deref,
1010
os::unix::prelude::{FromRawFd, RawFd},
11+
panic::catch_unwind,
1112
ptr::{self, NonNull},
1213
sync::{Arc, Condvar, Mutex, Weak},
1314
};
1415

1516
use log::Level;
1617
use ndk::{configuration::Configuration, input_queue::InputQueue, native_window::NativeWindow};
1718

18-
use crate::{util::abort_on_panic, util::android_log, ConfigurationRef};
19+
use crate::{
20+
util::android_log,
21+
util::{abort_on_panic, log_panic},
22+
ConfigurationRef,
23+
};
1924

2025
use super::{AndroidApp, Rect};
2126

@@ -803,6 +808,7 @@ unsafe extern "C" fn on_content_rect_changed(
803808

804809
/// This is the native entrypoint for our cdylib library that `ANativeActivity` will look for via `dlsym`
805810
#[no_mangle]
811+
#[allow(unused_unsafe)] // Otherwise rust 1.64 moans about using unsafe{} in unsafe functions
806812
extern "C" fn ANativeActivity_onCreate(
807813
activity: *mut ndk_sys::ANativeActivity,
808814
saved_state: *const libc::c_void,
@@ -874,15 +880,26 @@ extern "C" fn ANativeActivity_onCreate(
874880
rust_glue.notify_main_thread_running();
875881

876882
unsafe {
877-
// XXX: If we were in control of the Java Activity subclass then
878-
// we could potentially run the android_main function via a Java native method
879-
// springboard (e.g. call an Activity subclass method that calls a jni native
880-
// method that then just calls android_main()) that would make sure there was
881-
// a Java frame at the base of our call stack which would then be recognised
882-
// when calling FindClass to lookup a suitable classLoader, instead of
883-
// defaulting to the system loader. Without this then it's difficult for native
884-
// code to look up non-standard Java classes.
885-
android_main(app);
883+
// We want to specifically catch any panic from the application's android_main
884+
// so we can finish + destroy the Activity gracefully via the JVM
885+
catch_unwind(|| {
886+
// XXX: If we were in control of the Java Activity subclass then
887+
// we could potentially run the android_main function via a Java native method
888+
// springboard (e.g. call an Activity subclass method that calls a jni native
889+
// method that then just calls android_main()) that would make sure there was
890+
// a Java frame at the base of our call stack which would then be recognised
891+
// when calling FindClass to lookup a suitable classLoader, instead of
892+
// defaulting to the system loader. Without this then it's difficult for native
893+
// code to look up non-standard Java classes.
894+
android_main(app);
895+
})
896+
.unwrap_or_else(|panic| log_panic(panic));
897+
898+
// Let JVM know that our Activity can be destroyed before detaching from the JVM
899+
//
900+
// "Note that this method can be called from any thread; it will send a message
901+
// to the main thread of the process where the Java finish call will take place"
902+
ndk_sys::ANativeActivity_finish(activity);
886903

887904
if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread {
888905
detach_current_thread(jvm);

android-activity/src/util.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,25 @@ pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) {
3131
}
3232
}
3333

34+
pub(crate) fn log_panic(panic: Box<dyn std::any::Any + Send>) {
35+
let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") };
36+
37+
if let Some(panic) = panic.downcast_ref::<String>() {
38+
if let Ok(msg) = CString::new(panic.clone()) {
39+
android_log(Level::Error, rust_panic, &msg);
40+
}
41+
} else if let Ok(panic) = panic.downcast::<&str>() {
42+
if let Ok(msg) = CString::new(*panic) {
43+
android_log(Level::Error, rust_panic, &msg);
44+
}
45+
} else {
46+
let unknown_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") };
47+
android_log(Level::Error, unknown_panic, unsafe {
48+
CStr::from_bytes_with_nul_unchecked(b"\0")
49+
});
50+
}
51+
}
52+
3453
/// Run a closure and abort the program if it panics.
3554
///
3655
/// This is generally used to ensure Rust callbacks won't unwind past the JNI boundary, which leads
@@ -45,26 +64,7 @@ pub(crate) fn abort_on_panic<R>(f: impl FnOnce() -> R) -> R {
4564
//
4665
// Just in case our attempt to log a panic could itself cause a panic we use a
4766
// second catch_unwind here.
48-
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
49-
// Try logging the panic, but abort if that fails.
50-
let rust_panic = unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") };
51-
52-
if let Some(panic) = panic.downcast_ref::<String>() {
53-
if let Ok(msg) = CString::new(panic.clone()) {
54-
android_log(Level::Error, rust_panic, &msg);
55-
}
56-
} else if let Ok(panic) = panic.downcast::<&str>() {
57-
if let Ok(msg) = CString::new(*panic) {
58-
android_log(Level::Error, rust_panic, &msg);
59-
}
60-
} else {
61-
let unknown_panic =
62-
unsafe { CStr::from_bytes_with_nul_unchecked(b"UnknownPanic\0") };
63-
android_log(Level::Error, unknown_panic, unsafe {
64-
CStr::from_bytes_with_nul_unchecked(b"\0")
65-
});
66-
}
67-
}));
67+
let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic)));
6868
std::process::abort();
6969
})
7070
}

0 commit comments

Comments
 (0)