Before you start reading, click here to clone the project.
In the realm of Android app development, prioritizing code correctness and security is essential. While managed languages like Java and Kotlin are well-suited for Android app development, system-level programming often requires languages like C and C++. However, memory safety bugs in C and C++ pose ongoing challenges, contributing to stability issues and security vulnerabilities within the Android ecosystem. To address these concerns, the Android Open Source Project (AOSP) now supports the Rust programming language for developing the OS itself.
Rust, renowned for its memory safety guarantees, concurrency support, and impressive performance, presents a powerful solution for preventing memory bugs and enhancing overall code correctness. Through a combination of compile-time and runtime checks, Rust ensures valid memory accesses while delivering performance on par with C and C++. By seamlessly integrating Rust native libraries into Android apps, we can tap into the unique capabilities of Rust to bolster security, stability, and performance.
This comprehensive guide will explore the seamless integration of Rust native libraries into Android projects, empowering developers to leverage Rust’s memory safety features effectively. We will delve into the necessary tools, libraries, and techniques that enable developers to harness the power of Rust, creating safer and more efficient Android applications.
💡 The following instructions are for Linux, you might have to make adjustments for Windows/MacOS for the following instructions.
- Project: rustapp
- droid Application ID: com.example.rustapp
- Native Library:
rustnat
Creating an Android Project
💡 If you already have an existing Android project set up, you can skip this section
Setting up an Android project can be done using Android Studio’s "New Project" feature, or you can choose to set it up manually. We will setup a simple app with a "Hello" button. Here are the steps for manual setup:
Install Gradle and JDK. 💡 Recommended: Install Gradle v8.0.2 and JDK 17 to avoid compatibility issues.
Create a new project directory called rustapp
and navigate into it. Now, initialize a Gradle project with default choices using command gradle init
inside a terminal.
We need to create or edit a few files.
-
Graddle settings file
settings.gradle
pluginManagement { repositories { google() mavenCentral() gradlePluginPortal() } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() } } rootProject.name = "Rust App" include ':app'
-
Grandle properties
gradle.properties
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 android.useAndroidX=true
-
Build configuration
app/build.gradle
plugins { id 'com.android.application' version '8.0.2' } android { namespace 'com.example.rustapp' compileSdk 33 defaultConfig { applicationId "com.example.rustapp" minSdk 24 targetSdk 33 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt') } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' }
-
App manifest file
app/src/main/AndroidManifest.xml
<?xml version = "1.0" encoding = "utf-8"?> <manifest xmlns:android ="<http://schemas.android.com/apk/res/android>" xmlns:tools ="<http://schemas.android.com/tools >"> <application android:label ="Rust App" android:theme="@style/Theme.AppCompat"> <activity android:name =".MainActivity" android:exported="true" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
app/src/main/res/layout/activity_main.xml
<?xml version = "1.0" encoding = "utf-8"?> <LinearLayout xmlns:android ="<http://schemas.android.com/apk/res/android>" xmlns:app ="<http://schemas.android.com/apk/res-auto>" xmlns:tools ="<http://schemas.android.com/tools>" android:layout_width ="match_parent" android:layout_height ="match_parent" android:gravity ="center" android:orientation ="vertical" tools:context =".MainActivity" > <Button android:id ="@+id/helloButton" android:layout_height ="wrap_content" android:layout_width ="wrap_content" android:text ="Hello" android:textSize ="20sp" /> </LinearLayout>
-
main activity file
app/src/main/java/com/example/rustapp/MainActivity.java
package com.example.rustapp; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
Once done, your project structure will look something like this:
rustapp
├─ app
│ ├─ build.gradle
│ └─ src
│ └─ main
│ ├─ AndroidManifest.xml
│ ├─ java
│ │ └─ com
│ │ └─ example
│ │ └─ rustapp
│ │ └─ MainActivity.java
│ └─ res
│ └─ layout
│ └─ activity_main.xml
├─ gradle
│ └─ wrapper
│ ├─ gradle-wrapper.jar
│ └─ gradle-wrapper.properties
├─ gradle.properties
├─ gradlew
├─ gradlew.bat
└─ settings.gradle
You can now build the app using ./gradlew assembleDebug
. To get a list of available commands, you can use ./gradlew tasks
.
Integrating Rust Libraries
We will create a simple Rust function that returns some text, which will be called by MainActivity.java
and displayed in a Toast. Following are the steps:
-
Install rustup
-
Install the stable toolchain
rustup toolchain install stable
-
Add the (required) android targets
rustup target add aarch64-linux-android rustup target add armv7-linux-androideabi rustup target add i686-linux-android rustup target add x86_64-linux-android
-
Set the following environment variables
export JAVA_HOME=/usrfunction that can be called from export ANDROID_HOME=$HOME/.android_sdk
💡 The
java
binary should be located in$JAVA_HOME/bin/
-
Install NDK
mkdir -p $ANDROID_HOME/ndk wget https://dl.google.com/android/repository/android-ndk-r25c-linux.zip -O temp.zip unzip -d $ANDROID_HOME/ndk temp.zip rm temp.zip
-
Accept the SDK license
mkdir $ANDROID_HOME/licenses echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" > $ANDROID_HOME/licenses/android-sdk-license
-
Initialize the Rust library
cargo init --lib --name rustnat
-
Create/Edit the following files:
-
Cargo.toml
[lib] crate-type = ["dylib"] [dependencies] jni = "0.21.1" [profile.release] codegen-units = 1 lto = true strip = true
-
src/lib.rs
#[allow(non_snake_case)] pub mod android { use jni::{objects::JClass, sys::jstring, JNIEnv}; #[no_mangle] pub extern "system" fn Java_com_example_rustapp_MainActivity_hello( env: JNIEnv, _: JClass, ) -> jstring { let message = env .new_string("Hello from Rust") .expect("Failed to create Java string"); message.into_raw() } }
💡 The function name follows a special format:
Java&application_id&class_name&function&
app/src/main/java/com/example/rustapp/MainActivity.java
import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { static { System.loadLibrary("rustnat"); } private static native String hello(); @Override protected void onCreate(Bundle savedInstanceState) { : : : Button helloButton = findViewById(R.id.helloButton); helloButton.setOnClickListener(view -> Toast.makeText(this, hello(), Toast.LENGTH_SHORT).show()); } }
app/build.gradle
plugins { : id 'org.mozilla.rust-android-gradle.rust-android' version '0.9.3' } android { ndkVersion '25.2.9519653' : : } cargo { module = '..' libname = 'rustnat' profile = gradle.startParameter.taskNames.any{it.toLowerCase().contains("debug")} ? "debug" : "release" targets = ['arm64'] // Available targets: 'arm', 'arm64', 'x86', 'x86_64' } preBuild.dependsOn 'cargoBuild'
-
💡 You can find the
ndk
version in$ANDROID_HOME/ndk
Now you can build the app.
References
- https://github.com/SubconsciousCompute/android_rust_service
- https://gendignoux.com/blog/2022/10/24/rust-library-android.html#including-the-rust-library-into-the-android-app
- https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html
- https://blog.devgenius.io/integrating-rust-with-android-development-ef341c2f9cca
- https://blog.logrocket.com/integrating-rust-module-android-app/
- https://github.com/ssrlive/rust_on_android
- https://github.com/gendx/android-rust-library
- https://github.com/suve/rust-on-android