Libraries and tools for Rust programming on Android targets:
Name | Description | Badges |
---|---|---|
ndk-sys |
Raw FFI bindings to the NDK | |
ndk |
Safe abstraction of the bindings | |
ndk-context |
Android handles | |
ndk-glue |
Startup code | |
ndk-build |
Everything for building apk's | |
cargo-apk |
Build tool |
See ndk-examples
for examples using the NDK and the README files of the crates for more details.
ndk
and ndk-sys
aim to support at least the LTS
and Rolling Release
branches of the NDK, as described on their wiki. Additionally the Beta Release
might be supported to prepare for an upcoming release.
As of writing (2022-10-10) the following NDKs are tested:
Branch | Version | Status | Working |
---|---|---|---|
r18 | 18.1.5063045 | Deprecated | ❌ |
r19 | 19.2.5345600 | Deprecated | ✔️ |
r20 | 20.1.5948944 | Deprecated | ✔️ |
r21 | 21.4.7075529 | Deprecated | ✔️ |
r22 | 22.1.7171670 | Deprecated | ✔️ |
r23 | 23.1.7779620 | Deprecated | ✔️ |
r24 | 24.0.8215888 | Deprecated | ✔️ |
r25 | 25.1.8937393 | LTS | ✔️ |
Quick start setting up a new project with support for Android, using the ndk-glue
layer for communicating with the Android framework through NativeActivity
and cargo-apk
for packaging a crate in an Android .apk
file.
This short guide can also be used as a reference for converting existing crates to be runnable on Android.
Make sure the Android NDK is installed, together with a target platform (30
by default), build-tools
and platform-tools
, using either the sdkmanager
or Android Studio.
$ cargo new hello_world_android --lib
Never name your project android
as this results in a target binary named libandroid.so
which is also the name of Android's framework library: this will fail to link.
Add the ndk-glue
dependency to your crate.
Cargo.toml
# This dependency will only be included when targeting Android
[target.'cfg(target_os = "android")'.dependencies]
ndk-glue = "xxx" # Substitute this with the latest ndk-glue version you wish to use
Then configure the library target to be compiled to a Rust lib
(for use in an executable on desktop) and a cdylib
to create a native binary that can be bundled in the final .apk
and loaded by Android.
Cargo.toml
[lib]
crate-type = ["lib", "cdylib"]
Create a main
function holding your code in the library portion of the crate, and wrap it in the ndk_glue::main
attribute macro when targeting Android.
src/lib.rs
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
pub fn main() {
println!("Hello World");
}
See the ndk-macro
documentation for more options.
Additionally, to make this crate runnable outside of Android, create a binary that calls the main function in the library.
src/main.rs
fn main() {
hello_world_android::main()
}
As a sanity check, run this binary to make sure everything is set up correctly:
$ cargo run
Install cargo apk
for building, running and debugging your application:
$ cargo install cargo-apk
We can now directly execute our Hello World
application on a real connected device or an emulator:
$ cargo apk run
If the crate includes a runnable binary as suggested above, you will likely be greeted by the following error:
$ cargo apk run
error: extra arguments to `rustc` can only be passed to one target, consider filtering
the package by passing, e.g., `--lib` or `--bin NAME` to specify a single target
Error: Command `cargo rustc --target aarch64-linux-android -- -L hello_world_android/target/cargo-apk-temp-extra-link-libraries` had a non-zero exit code.
To solve this, add --lib
to the run invocation, like so:
$ cargo apk run --lib
ndk-glue
redirects stdout and stderr to Android logcat
, including the println!("Hello World")
by the example above. See Logging and stdout below how to access it.
Android native apps have no easy access to Android's User Interface functionality (bar JNI interop). Applications can instead draw pixels directly to the window using ANativeWindow_lock
, or use a graphics API like OpenGL or Vulkan for high performance rendering.
Stdout is redirected to the android log api when using ndk-glue
. Any logger that logs to
stdout, like println!
, should therefore work.
To filter on this output in logcat
:
$ adb logcat RustStdoutStderr:D *:S
Enable the "logger"
feature on the ndk-glue
macro and configure its log tag and debug level through the attribute macro:
src/lib.rs
#[cfg_attr(target_os = "android", ndk_glue::main(logger(level = "debug", tag = "my-tag")))]
pub fn main() {
log!("hello world");
}
Android APKs contain a file called AndroidManifest.xml
, which has things like permission requests and feature declarations, plus configuration of activities, intents, resource folders and more. This file is autogenerated by cargo-apk
. To control what goes in it through Cargo.toml, refer to cargo-apk
's README.
The macro ndk_glue::main
tries to determine crate names from current Cargo.toml.
You can override this names with specific paths like so:
#[ndk_glue::main(
ndk_glue = "path::to::ndk_glue",
)]
fn main() {}
Java Native Interface (JNI) allows executing Java code in a VM from native applications. To access
the JNI use the AndroidContext
from the ndk-context
crate. ndk-examples
contains a jni_audio
example which will print out all output audio devices in the log.
jni
, JNI bindings for Rust