Support an optional 'android_on_create' entrypoint

This adds support for an optional `android_on_crate` entrypoint which is
called from within the Activity.onCreate native method callback from the
Java main / UI thread.

This gives applications an opportunity initialize state while the
`Activity`'s class loader is on the stack, so `FindClass` will be able
to find application classes.

This can be a more-convenient place to initialize JNI bindings, without
needing to explicitly get the class loader from the Activity to be able
to look up application classes from the android_main thread.

This may also be convenient for initially using JNI to interact with
your new Activity in case you need to use SDK APIs that are only safe to
use from the Java main / UI thread.

The moves the thread initialization functions out of util.rs into a new
init.rs

While adding documentation for this feature, this also does a
more-general pass over the top-level crate documentation to try and
ensure it's up-to-date.

Fixes: #169
Addresses: #82
This commit is contained in:
Robert Bragg
2026-03-18 01:39:40 +00:00
parent 4acfd2d59c
commit c1d00b9191
7 changed files with 770 additions and 227 deletions
+53 -7
View File
@@ -37,22 +37,33 @@ Cargo.toml
[dependencies]
log = "0.4"
android_logger = "0.13"
android-activity = { version = "0.5", features = [ "native-activity" ] }
android-activity = { version = "0.6", features = [ "native-activity" ] }
[lib]
crate_type = ["cdylib"]
crate-type = ["cdylib"]
```
_Note: that you will need to either specify the **"native-activity"** feature or **"game-activity"** feature to identify which `Activity` base class your application is based on_
_Note: that you will need to either specify the **"native-activity"** feature or
**"game-activity"** feature to identify which `Activity` base class your
application is based on_
lib.rs
```rust
use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};
#[no_mangle]
#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
// `android_main` is tied to your `Activity` lifecycle, not your application lifecycle
// and so it may be called multiple times if your activity is destroyed and recreated.
//
// Use a `OnceLock` or similar to ensure that you don't attempt to initialize global state
// multiple times.
static APP_ONCE: OnceLock<()> = OnceLock::new();
APP_ONCE.get_or_init(|| {
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info));
});
loop {
app.poll_events(Some(std::time::Duration::from_millis(500)) /* timeout */, |event| {
@@ -62,6 +73,11 @@ fn android_main(app: AndroidApp) {
PollEvent::Main(main_event) => {
log::info!("Main event: {:?}", main_event);
match main_event {
// Once you receive a `Destroy` event, your `AndroidApp` will no longer
// be associated with any `Activity` and it's methods will effectively be no-ops.
//
// You should return from `android_main` and if your `Activity` gets recreated then
// a new `AndroidApp` will be passsed to a new invocation of `android_main`.
MainEvent::Destroy => { return; }
_ => {}
}
@@ -85,6 +101,36 @@ cargo apk run
adb logcat example:V *:S
```
## Optional `android_on_create` entry point
`android-activity` also supports an optional `android_on_create` entry point that gets called from the
`Activity.onCreate()` callback before `android_main()` is called, allowing for doing some setup work on the Java main
thread before the main Rust code starts running.
For example:
```rust
use std::sync::OnceLock;
use jni::{JavaVM, objects::JObject};
#[unsafe(no_mangle)]
fn android_on_create(state: &android_activity::OnCreateState) {
// `android_on_create` is tied to your `Activity` lifecycle, not your application lifecycle
// and so it may be called multiple times if your activity is destroyed and recreated.
//
// Use a `OnceLock` or similar to ensure that you don't attempt to initialize global state
// multiple times.
static APP_ONCE: OnceLock<()> = OnceLock::new();
APP_ONCE.get_or_init(|| {
// Initialize logging...
});
let vm = unsafe { JavaVM::from_raw(state.vm_as_ptr().cast()) };
let activity = state.activity_as_ptr() as jni::sys::jobject;
// Do some other setup work on the Java main thread before `android_main` starts running
}
```
## Full Examples
See [this collection of examples](https://github.com/rust-mobile/rust-android-examples) (based on both `GameActivity` and `NativeActivity`).
@@ -111,7 +157,7 @@ Even if you start out using `NativeActivity` for the convenience, it's likely th
Firstly; if you have a [Winit](https://crates.io/crates/winit) based application and also have an explicit dependency on `ndk-glue` your application will need to remove its dependency on `ndk-glue` for the 0.28 release of Winit which will be based on android-activity (Since glue crates, due to their nature, can't be compatible with alternative glue crates).
Winit-based applications can follow the [Android documentation](https://docs.rs/winit/latest/winit/platform/android/index.html) guidance for advice on how to switch over. Most Winit-based applications should aim to remove any explicit dependency on a specific glue crate (so not depend directly on `ndk-glue` or `android-activity` and instead rely on Winit to pull in the right glue crate). The main practical change will then be to add a `#[no_mangle]fn android_main(app: AndroidApp)` entry point.
Winit-based applications can follow the [Android documentation](https://docs.rs/winit/latest/winit/platform/android/index.html) guidance for advice on how to switch over. Most Winit-based applications should aim to remove any explicit dependency on a specific glue crate (so not depend directly on `ndk-glue` or `android-activity` and instead rely on Winit to pull in the right glue crate). The main practical change will then be to add a `#[unsafe(no_mangle)]fn android_main(app: AndroidApp)` entry point.
See the [Android documentation](https://docs.rs/winit/latest/winit/platform/android/index.html) for more details and also see the [Winit-based examples here](https://github.com/rust-mobile/rust-android-examples).
@@ -126,7 +172,7 @@ Middleware libraries can instead look at using the [ndk-context](https://crates.
The steps to switch a simple standalone application over from `ndk-glue` to `android-activity` (still based on `NativeActivity`) should be:
1. Remove `ndk-glue` from your Cargo.toml
2. Add a dependency on `android-activity`, like `android-activity = { version="0.5", features = [ "native-activity" ] }`
2. Add a dependency on `android-activity`, like `android-activity = { version="0.6", features = [ "native-activity" ] }`
3. Optionally add a dependency on `android_logger = "0.13.0"`
4. Update the `main` entry point to look like this: