Glue for building Rust applications on Android with NativeActivity or GameActivity
Rust
404
347 commits
updated Jun 22, 2026
android-activityandroid-activity provides a "glue" layer for building native Rust
applications on Android, supporting multiple Activity base classes.
It's comparable to android_native_app_glue.c
for C/C++ applications and is an alternative to the ndk-glue crate.
android-activity provides a way to load your crate as a cdylib library via
the onCreate method of your Android Activity class; run an android_main
function in a separate thread from the Java main thread and marshal events (such
as lifecycle events and input events) between Java and your native thread.
So far it supports NativeActivity or GameActivity (from the
Android Game Development Kit) and there's also interest in supporting a first-party
RustActivity base class that could be better tailored to the needs of Rust
applications.
Cargo.toml:
[dependencies]
log = "0.4"
android_logger = "0.13"
android-activity = { version = "0.6", features = [ "native-activity" ] }
[lib]
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
lib.rs:
use std::sync::OnceLock;
use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};
// - Called on a dedicated Activity main loop thread, spawned after `android_on_create` returns
// - May be called multiple times if your Activity is destroyed and recreated.
// - Note: this symbol has a "Rust" ABI (default), not "C" ABI.
#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {
// `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| {
match event {
PollEvent::Wake => { log::info!("Early wake up"); },
PollEvent::Timeout => { log::info!("Hello, World!"); },
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 passed to a new invocation of `android_main`.
MainEvent::Destroy => { return; }
_ => {}
}
},
_ => {}
}
app.input_events(|event| {
log::info!("Input Event: {event:?}");
InputStatus::Unhandled
});
});
}
}
rustup target add aarch64-linux-android
cargo install cargo-apk
cargo apk run
adb logcat example:V *:S
Note: although cargo apk is convenient for this quick start example, it's
generally recommended that you should use a more-standard, Gradle-based build
system for your Android application and use something like cargo ndk for
building your Rust code into a cdylib that is then packaged via Gradle.
See this collection of
examples (based on both
GameActivity and NativeActivity).
Each example is a standalone Android Studio project that can serve as a convenient template for starting a new project.
For the examples based on middleware frameworks (Winit or Egui) they also aim to demonstrate how it's possible to write portable code that will run on Android and other systems.
android_on_create entry pointandroid-activity also supports an optional android_on_create entry point
that gets called from the Activity.onCreate() callback before android_main()
is called.
android_on_create is called from the Java main / UI thread before the
android_main thread is spawned.
Considering that many Android SDK APIs (such as android.view.View) must be
accessed from the main thread, android_on_create can be a good place to do any
setup work that needs to be done on the Java main thread.
For example:
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
}
(Note: there is also an AndroidApp::run_on_java_main_thread() method that
gives another way to run code on the Java main thread for some use cases)
To learn more about the NativeActivity class that's shipped with Android see
here.
To learn more about the GameActivity class that's part of the Android Game
Developer's Kit and also see a comparison with NativeActivity see
here
Generally speaking, if unsure, NativeActivity may be more convenient to start
with since you may not need to compile/link any Java or Kotlin code, but
GameActivity is likely to be the better longer-term choice, due to being based
on AppCompatActivity and having built in support for input methods (such as
onscreen keyboards).
The unique advantage of the NativeActivity class is that it's shipped as part
of the Android OS and so you can use it without needing to compile or link any
Java or Kotlin code.
NativeActivity is technically the only way to build a native Android
application purely in Rust without any Java or Kotlin code at all.
The most significant limitation of NativeActivity is that it doesn't have
built-in support for input methods (such as onscreen keyboards) and so it's
often not a good choice for applications that need to support text input.
Since some soft keyboards will deliver physical key events for basic ascii input
then NativeActivity can enable basic text input for prototyping but this is
unlikely to be sufficient for production applications.
For advanced use cases, it would be possible to provide custom InputConnection
support in conjunction with NativeActivity but this isn't something that
android-activity provides out of the box currently.
androidx.games:games-activity:4.4.0game-activity feature in Cargo.tomlGameActivity has built in support for input methods via the GameTextInput
library and so is a better choice for applications that need to support text
input.
GameActivity allows you to update the ImeOptions and actions associated with
the soft keyboard as well as receive IME span updates for tracking the user's
text input state.
GameActivity is based on the AppCompatActivity class, which is a standard
Jetpack / AndroidX class that offers a lot of built-in functionality to help
with compatibility across different Android versions and devices.
android-activity currently supports the GameActivity 4.4.0 Jetpack
library and is
backwards compatible with the previous 4.0.0 stable release. We can't
guarantee that the next 4.x stable release will be compatible, but it's fairly
likely that it will be.
Your Android package should depend on androidx.games:games-activity:4.4.0 from
Google's Maven repository.
Read the upstream GameActivity getting started guide for more details on how to add the GameActivity library to your project.
Important: Do not follow upstream instructions to enable native prefab
support for GameActivity that will compile and link the upstream C++ glue
layer as part of your build. The upstream glue layer is not directly compatible
with android-activity which provides its own native glue layer that integrates
with Rust.
I.e. you do not need to enable prefabs via your build.gradle file:
buildFeatures {
prefab true
}
and do not add a snippet like this to your CMakeLists.txt file:
find_package(game-activity REQUIRED CONFIG)
target_link_libraries(${PROJECT_NAME} PUBLIC log android
game-activity::game-activity_static)
It's not possible to subclass an Activity from Rust / JNI code alone.
Keep in mind that Android's design directs many events via the Activity class
which can only be processed by overloading some associated Activity method, so
if you want to handle those events then you will need to implement an Activity
subclass and overload the relevant methods.
Most moderately complex applications will eventually need to define their own
Activity subclass (either subclassing NativeActivity or GameActivity)
which will require compiling at least a small amount of Java or Kotlin code.
At the end of the day, Android's application programming model is fundamentally based around a Java VM running Java/Kotlin code that can optionally call into native code (not the other way around).
Prior to working on android-activity, the existing glue crates available for
building standalone Rust applications on Android were found to have a number of
technical limitations that this crate aimed to solve:
Support alternative Activity classes: Prior glue crates were based on
NativeActivity and their API precluded supporting alternatives. In
particular there was an interest in the GameActivity class in conjunction
with its GameTextInput library that can facilitate onscreen keyboard
support. This also allows building applications based on the standard
AppCompatActivity base class which isn't possible with NativeActivity.
Finally there was interest in paving the way towards supporting a first-party
RustActivity that could be best tailored towards the needs of Rust
applications on Android.
Encapsulate IPC + synchronization between the native thread and the JVM thread:
For example with ndk-glue the application itself needs to avoid
race conditions between the native and Java thread by following a locking
convention) and it wasn't clear how this would extend to support other
requests (like state saving) that also require synchronization.
Avoid static global state: Keeping in mind the possibility of supporting
applications with multiple native activities there was interest in having an
API that didn't rely on global statics to track top-level state. Instead of
having global getters for state then android-activity passes an explicit
app: AndroidApp argument to the entry point that encapsulates the state
connected with a single Activity.
It's possible to write an application with android-activity that can
gracefully handle repeated create -> run -> destroy cycles of the Activity
due to its avoidance of global state. Theoretically you could even run
multiple Activity instances at the same (though since NativeActivity and
GameActivity were designed for fullscreen games, that only need a single
Activity, this is not a common use case).
We aim to (at least) support stable releases of Rust from the last three months.
Rust has a 6 week release cycle which means we will support the last three
stable releases. For example, when Rust 1.69 is released we would limit our
rust-version to 1.67.
We will only bump the rust-version at the point where we either depend on a
new features or a dependency has increased its MSRV, and we won't be greedy. In
other words we will only set the MSRV to the lowest version that's needed.
MSRV updates are not considered to be inherently semver breaking (unless a new
feature is exposed in the public API) and so a rust-version change may happen
in patch releases.
Any single release of android-activity will support a specific version of the
Game Activity Jetpack / AndroidX library (documented above).
The required version of the Game Activity library does not form part of our Rust
semver contract, since it doesn't affect the public Rust API of
android-activity.
This means that a new patch release of android-activity may update the
required version of GameActivity, which may require users to update how they
package their application.
This is similar to how MSRV updates work, where new toolchain requirements can affect how you build your application but that change is orthogonal to the public API of the crate.
495 followers · starred Mar 2024
279 followers · starred Nov 2022
253 followers · starred Feb 2024
116 followers · starred Apr 2023
Glue for building Rust applications on Android with NativeActivity or GameActivity
Rust
404
347 commits
updated Jun 22, 2026
android-activityandroid-activity provides a "glue" layer for building native Rust
applications on Android, supporting multiple Activity base classes.
It's comparable to android_native_app_glue.c
for C/C++ applications and is an alternative to the ndk-glue crate.
android-activity provides a way to load your crate as a cdylib library via
the onCreate method of your Android Activity class; run an android_main
function in a separate thread from the Java main thread and marshal events (such
as lifecycle events and input events) between Java and your native thread.
So far it supports NativeActivity or GameActivity (from the
Android Game Development Kit) and there's also interest in supporting a first-party
RustActivity base class that could be better tailored to the needs of Rust
applications.
Cargo.toml:
[dependencies]
log = "0.4"
android_logger = "0.13"
android-activity = { version = "0.6", features = [ "native-activity" ] }
[lib]
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
lib.rs:
use std::sync::OnceLock;
use android_activity::{AndroidApp, InputStatus, MainEvent, PollEvent};
// - Called on a dedicated Activity main loop thread, spawned after `android_on_create` returns
// - May be called multiple times if your Activity is destroyed and recreated.
// - Note: this symbol has a "Rust" ABI (default), not "C" ABI.
#[unsafe(no_mangle)]
fn android_main(app: AndroidApp) {
// `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| {
match event {
PollEvent::Wake => { log::info!("Early wake up"); },
PollEvent::Timeout => { log::info!("Hello, World!"); },
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 passed to a new invocation of `android_main`.
MainEvent::Destroy => { return; }
_ => {}
}
},
_ => {}
}
app.input_events(|event| {
log::info!("Input Event: {event:?}");
InputStatus::Unhandled
});
});
}
}
rustup target add aarch64-linux-android
cargo install cargo-apk
cargo apk run
adb logcat example:V *:S
Note: although cargo apk is convenient for this quick start example, it's
generally recommended that you should use a more-standard, Gradle-based build
system for your Android application and use something like cargo ndk for
building your Rust code into a cdylib that is then packaged via Gradle.
See this collection of
examples (based on both
GameActivity and NativeActivity).
Each example is a standalone Android Studio project that can serve as a convenient template for starting a new project.
For the examples based on middleware frameworks (Winit or Egui) they also aim to demonstrate how it's possible to write portable code that will run on Android and other systems.
android_on_create entry pointandroid-activity also supports an optional android_on_create entry point
that gets called from the Activity.onCreate() callback before android_main()
is called.
android_on_create is called from the Java main / UI thread before the
android_main thread is spawned.
Considering that many Android SDK APIs (such as android.view.View) must be
accessed from the main thread, android_on_create can be a good place to do any
setup work that needs to be done on the Java main thread.
For example:
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
}
(Note: there is also an AndroidApp::run_on_java_main_thread() method that
gives another way to run code on the Java main thread for some use cases)
To learn more about the NativeActivity class that's shipped with Android see
here.
To learn more about the GameActivity class that's part of the Android Game
Developer's Kit and also see a comparison with NativeActivity see
here
Generally speaking, if unsure, NativeActivity may be more convenient to start
with since you may not need to compile/link any Java or Kotlin code, but
GameActivity is likely to be the better longer-term choice, due to being based
on AppCompatActivity and having built in support for input methods (such as
onscreen keyboards).
The unique advantage of the NativeActivity class is that it's shipped as part
of the Android OS and so you can use it without needing to compile or link any
Java or Kotlin code.
NativeActivity is technically the only way to build a native Android
application purely in Rust without any Java or Kotlin code at all.
The most significant limitation of NativeActivity is that it doesn't have
built-in support for input methods (such as onscreen keyboards) and so it's
often not a good choice for applications that need to support text input.
Since some soft keyboards will deliver physical key events for basic ascii input
then NativeActivity can enable basic text input for prototyping but this is
unlikely to be sufficient for production applications.
For advanced use cases, it would be possible to provide custom InputConnection
support in conjunction with NativeActivity but this isn't something that
android-activity provides out of the box currently.
androidx.games:games-activity:4.4.0game-activity feature in Cargo.tomlGameActivity has built in support for input methods via the GameTextInput
library and so is a better choice for applications that need to support text
input.
GameActivity allows you to update the ImeOptions and actions associated with
the soft keyboard as well as receive IME span updates for tracking the user's
text input state.
GameActivity is based on the AppCompatActivity class, which is a standard
Jetpack / AndroidX class that offers a lot of built-in functionality to help
with compatibility across different Android versions and devices.
android-activity currently supports the GameActivity 4.4.0 Jetpack
library and is
backwards compatible with the previous 4.0.0 stable release. We can't
guarantee that the next 4.x stable release will be compatible, but it's fairly
likely that it will be.
Your Android package should depend on androidx.games:games-activity:4.4.0 from
Google's Maven repository.
Read the upstream GameActivity getting started guide for more details on how to add the GameActivity library to your project.
Important: Do not follow upstream instructions to enable native prefab
support for GameActivity that will compile and link the upstream C++ glue
layer as part of your build. The upstream glue layer is not directly compatible
with android-activity which provides its own native glue layer that integrates
with Rust.
I.e. you do not need to enable prefabs via your build.gradle file:
buildFeatures {
prefab true
}
and do not add a snippet like this to your CMakeLists.txt file:
find_package(game-activity REQUIRED CONFIG)
target_link_libraries(${PROJECT_NAME} PUBLIC log android
game-activity::game-activity_static)
It's not possible to subclass an Activity from Rust / JNI code alone.
Keep in mind that Android's design directs many events via the Activity class
which can only be processed by overloading some associated Activity method, so
if you want to handle those events then you will need to implement an Activity
subclass and overload the relevant methods.
Most moderately complex applications will eventually need to define their own
Activity subclass (either subclassing NativeActivity or GameActivity)
which will require compiling at least a small amount of Java or Kotlin code.
At the end of the day, Android's application programming model is fundamentally based around a Java VM running Java/Kotlin code that can optionally call into native code (not the other way around).
Prior to working on android-activity, the existing glue crates available for
building standalone Rust applications on Android were found to have a number of
technical limitations that this crate aimed to solve:
Support alternative Activity classes: Prior glue crates were based on
NativeActivity and their API precluded supporting alternatives. In
particular there was an interest in the GameActivity class in conjunction
with its GameTextInput library that can facilitate onscreen keyboard
support. This also allows building applications based on the standard
AppCompatActivity base class which isn't possible with NativeActivity.
Finally there was interest in paving the way towards supporting a first-party
RustActivity that could be best tailored towards the needs of Rust
applications on Android.
Encapsulate IPC + synchronization between the native thread and the JVM thread:
For example with ndk-glue the application itself needs to avoid
race conditions between the native and Java thread by following a locking
convention) and it wasn't clear how this would extend to support other
requests (like state saving) that also require synchronization.
Avoid static global state: Keeping in mind the possibility of supporting
applications with multiple native activities there was interest in having an
API that didn't rely on global statics to track top-level state. Instead of
having global getters for state then android-activity passes an explicit
app: AndroidApp argument to the entry point that encapsulates the state
connected with a single Activity.
It's possible to write an application with android-activity that can
gracefully handle repeated create -> run -> destroy cycles of the Activity
due to its avoidance of global state. Theoretically you could even run
multiple Activity instances at the same (though since NativeActivity and
GameActivity were designed for fullscreen games, that only need a single
Activity, this is not a common use case).
We aim to (at least) support stable releases of Rust from the last three months.
Rust has a 6 week release cycle which means we will support the last three
stable releases. For example, when Rust 1.69 is released we would limit our
rust-version to 1.67.
We will only bump the rust-version at the point where we either depend on a
new features or a dependency has increased its MSRV, and we won't be greedy. In
other words we will only set the MSRV to the lowest version that's needed.
MSRV updates are not considered to be inherently semver breaking (unless a new
feature is exposed in the public API) and so a rust-version change may happen
in patch releases.
Any single release of android-activity will support a specific version of the
Game Activity Jetpack / AndroidX library (documented above).
The required version of the Game Activity library does not form part of our Rust
semver contract, since it doesn't affect the public Rust API of
android-activity.
This means that a new patch release of android-activity may update the
required version of GameActivity, which may require users to update how they
package their application.
This is similar to how MSRV updates work, where new toolchain requirements can affect how you build your application but that change is orthogonal to the public API of the crate.
495 followers · starred Mar 2024
279 followers · starred Nov 2022
253 followers · starred Feb 2024
116 followers · starred Apr 2023