preface

Rust is such a good thing that Google is starting to use it for AOSP. We can also use Rust to write Native code that is C++. After all, Hello World is a big step for mankind.

Install the Rust

Rust’s documentation is really great, and translations are available for almost every learning need. It’s easy to install using the website Rust-lang.org, with one command:

curl --proto '=https'- tlsv1.2 - sSf https://sh.rustup.rs | shCopy the code

Configure the NDK

1. Make sure you have downloaded and installed the NDK toolkit in Android Studio SDK Manager.

The default directory is /Users/ your username /Library/Android/ SDK /ndk-bundle. The default directory can be ${HOME} instead. Of course, if your SDK is somewhere else, just click yours.

Create a directory named NDK (optional name) anywhere and run the py script in the NDK toolkit to compile the NDK development environment:

cd ~
mkdir NDK
Different architecture parameters are different, so you can configure it as needed. For example, I only need ARM64
The API parameter should be the version number of your application targetSDK, such as 30 in my case
If you are using Python 2.x, you are not sure if this will work
python ${HOME}/Library/Android/sdk/ndk-bundle/build/tools/make_standalone_toolchain.py --api 30 --arch arm64 --install-dir NDK/arm64
python ${HOME}/Library/Android/sdk/ndk-bundle/build/tools/make_standalone_toolchain.py --api 30 --arch arm --install-dir NDK/arm
python ${HOME}/Library/Android/sdk/ndk-bundle/build/tools/make_standalone_toolchain.py --api 30 --arch x86 --install-dir NDK/x86
Copy the code

3. Edit the configuration file of the Rust environment, that is, ~/.cargo/config. If no, create a new one and add the following contents:

If you don't need to build another schema, don't add it
${HOME} ${HOME} ${HOME
[target.aarch64-linux-android]
ar = "/Users/ your username /NDK/arm64/bin/aarch64-linux-android-ar"
linker = "/Users/ your username /NDK/arm64/bin/aarch64-linux-android-clang"

[target.armv7-linux-androideabi]
ar = "/Users/ your username /NDK/arm/bin/arm-linux-androideabi-ar"
linker = "/Users/ your username /NDK/arm/bin/arm-linux-androideabi-clang"

[target.i686-linux-android]
ar = "/Users/ your username /NDK/x86/bin/i686-linux-android-ar"
linker = "/Users/ your username /NDK/x86/bin/i686-linux-android-clang"
Copy the code

4. Add the build toolchain as configured in Step 3:

rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android
Copy the code

Write the Demo

Rust-android-libs is my custom name for Coding:

cargo new rust-android-libs --lib
Copy the code

Go to the directory, edit the Cargo. Toml configuration file and modify it as follows:

[package]
name = "rust-android-libs"
version = "0.1.0 from"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
jni = { version = "0.19.0", default-features = false }

[lib]
crate_type = ["cdylib"]
Copy the code

For the JNI version of Rust, you can refer to the official documentation: docs.rs, which is currently 0.19.0. The following crate_type configuration is also based on the documentation.

SRC /lib.rs

#! [cfg(target_os ="android")]
#! [allow(non_snake_case)]

use jni::JNIEnv;
use jni::objects::{JClass, JString};
use jni::sys::jstring;

#[no_mangle]
pub extern "C" fn Java_com_xxx_xxx_Yyy_getTestStr(
    env: JNIEnv, _: JClass,
) -> jstring {
    env.new_string("Hello World!")
        .expect("Couldn't create java string!")
        .into_inner()
}

#[no_mangle]
pub extern "C" fn Java_com_xxx_xxx_Yyy_getTestStrWithInput(
    env: JNIEnv, _: JClass, input: JString,
) -> jstring {
    let input: String = env.get_string(input)
        .expect("Couldn't get java string!")
        .into();
    let output = env.new_string(format!("Hello, {}!", input))
        .expect("Couldn't create java string!");
    output.into_inner()
}
Copy the code

As shown above, we wrote two methods, one that returns a String directly and one that returns a concatenated String with arguments. Method naming rules and writing C++ JNI code, take this as an example, we need to create a corresponding Java/Kotlin code package named com.xxx. XXX, named Yyy class, which has two methods:

package com.xxx.xxx

object Yyy {
    init {
        // Since the so product compiled later is librust_android_libs.so, the loading name here is as follows
        System.loadLibrary("rust_android_libs")}external fun getTestStr(a): String
    external fun getTestStrWithInput(input: String): String
}
Copy the code

4. Build the Rust project as required. If you do not use emulators, x86 is generally not considered:

cargo build --target aarch64-linux-android --release
cargo build --target armv7-linux-androideabi --release
cargo build --target i686-linux-android --release
Copy the code

After compiling successfully, you can find the desired so file in /target/ aARCH64-linux-android /release/librust_android_libs.so, and copy it to the Android project.

Generally speaking… / app/SRC/main/jniLibs/arm64 – v8a/directory, architecture can according to your need. Also remember to configure build.gradle:

. android { compileSdkVersion30
    defaultConfig {
        ...

        ndk {
            abiFilters 'arm64-v8a'}}// If your AGP plugin is at least 7.0, you may need to add it
    packagingOptions {
        jniLibs {
            useLegacyPackaging = true}}}...Copy the code

The latter

The whole process, or a little cumbersome, but also sort out a few questions:

For the same code logic, Rust compiles so libraries much larger than C++ compiles. For example, the code above is around 4MB for 64-bit architecture and a little over 3MB for 32-bit architecture, while the C++ compiles are over 1MB and 900 KB, respectively. This is a significant difference for a package size sensitive project. I do not know if there is an optimization method, because I am also new to Rust, so I do not know much about it.

We can use the strings command to compare the compiled so structure between the two environments
strings librust_android_libs.so
Copy the code

Complex business logic certainly needs debugging to help locate problems, but as our example above shows, Rust projects are separate from Android projects, and how to integrate compilation and breakpoint debugging directly into Android projects like C++ code needs further exploration.

3. There are not enough official documents on adapting Android application projects from Google and Rust. We can see that there are Native C++ templates to choose from in the New Project in Studio, which is a good demonstration for beginners. Hopefully there will be templates like Native Rust in the future.