Skip to content

1ok1/droid-rust-starter

Repository files navigation

Droid-Rust

Droid-Rust is a starter project for Android NDK with Rust. If you would like to clone the project and directly start working on it follow the below steps.

  1. Clone the repo and execute the below git commands.
    git submodule init
    git submodule update
    
    This will clone the submodule[Rust ndk] inside the Android project
  2. Set Gradle JDK by following step 2
  3. Sync and Run, you are good to go.

Output

Step 1: Add the Path in Zshrc or bashrc

# Path to NDK
export ANDROID_HOME=$HOME/Library/Android/sdk
export NDK_HOME=$ANDROID_HOME/ndk-bundle
export PATH=$NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin:$PATH
export PATH="$HOME/.cargo/bin:$PATH"

Step 2: Create a new Android Studio Project, For people who are using Gradle 7.0.0+ Java 11 has been made mandatory either upgrade your system JAVA or install newer version of JAVA in paraller and add the PATH in gradle.properties

To install jdk with AdoptOpenJDK goto https://github.com/AdoptOpenJDK/homebrew-openjdk and follow the steps provided.

    org.gradle.java.home=/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home

Followed by setting the Gradle JDK to JAVA 11 Output

Step 3: Install rust in your system.

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

Step4: Verify rust installation

   rustc --version

Step 5: Setup a rust project

cargo new <rust> // Rust is the project name

Step 6: Add the below rust ndk config to Cargo.toml

[package]
name = "rust"
version = "0.1.0"
edition = "2018"

[target.'cfg(target_os="android")'.dependencies]
jni = { version = "0.5", default-features = false }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "rust"
crate-type = ["dylib"]

[dependencies]
// To Configure Rust libs

Step 7: Intergrate the Rust project as NDK in Android Project

In root build.gradle add
buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath 'org.mozilla.rust-android-gradle:plugin:0.9.0'
    }
}

Step 8: In App build.gralde add Rust

plugins {
    id 'org.mozilla.rust-android-gradle.rust-android'
}

Step 9: cargo gradle configuration

  cargo {
    module  = "../rust"       // directory containing your Cargo.toml
    libname = "rust"          // Cargo.toml's [package] name.
    targets = ['arm', 'arm64', 'x86', 'x86_64']
    prebuiltToolchains = true
    verbose = true
}

Step 10: Install the rust toolchains for your platforms

rustup target add armv7-linux-androideabi   # for arm
rustup target add i686-linux-android        # for x86
rustup target add aarch64-linux-android     # for arm64
rustup target add x86_64-linux-android      # for x86_64
rustup target add x86_64-unknown-linux-gnu  # for linux-x86-64
rustup target add x86_64-apple-darwin       # for darwin (macOS)
rustup target add x86_64-pc-windows-gnu     # for win32-x86-64-gnu
rustup target add x86_64-pc-windows-msvc    # for win32-x86-64-msvc

Step 11: To generate the .so files with gradle,

./gradlew cargoBuild

or

// Added the compile stage for both debug and release of JAVA and KOTLIN
tasks.whenTaskAdded { task ->
    println(task.name)
    if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease'
            || task.name == 'compileDebugKotlin' || task.name == 'compileReleaseKotlin')) {
        task.dependsOn 'cargoBuild'
    }
}

Step 12: Create an Android Application class and configure in tag of your manifest file, then add System.loadLibrary(LIBRARY_NAME) in the respective ways

For JAVA:
 In onCreate of Application class    
        System.loadLibrary(LIBRARY_NAME)

For Kotlin:
    companion object {
        private val LIBRARY_NAME = "rust"

        init {
            print(loadNativeLibrary())
        }

        fun loadNativeLibrary(): Boolean {
            try {
                Log.i(TAG, "Attempting to load library: $LIBRARY_NAME")
                System.loadLibrary(LIBRARY_NAME)
            } catch (e: Exception) {
                Log.i(TAG, "Exception loading native library: $e")
                return false
            }
            return true
        }
    }

Step 13: Rename the main.rs to lib.rs

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};


/// Expose the JNI interface for android below
#[cfg(target_os="android")]
#[allow(non_snake_case)]
pub mod android {
    extern crate jni;

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

    #[no_mangle]
    pub unsafe extern fn Java_com_lok1_rustndkexample_Greetings_greeting(env: JNIEnv, _: JClass, java_pattern: JString) -> jstring {
        let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
        let world_ptr = CString::from_raw(world);
        let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");
        output.into_inner()
    }

    #[no_mangle]
    pub unsafe extern fn Java_Greetings_welcome(env: JNIEnv, _: JClass, java_pattern: JString) -> jstring {
        let world = rust_greeting(env.get_string(java_pattern).expect("invalid pattern string").as_ptr());
        let world_ptr = CString::from_raw(world);
        let output = env.new_string(world_ptr.to_str().unwrap()).expect("Couldn't create java string!");
        output.into_inner()
    }

    #[no_mangle]
    pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {
        let c_str = unsafe { CStr::from_ptr(to) };
        let recipient = match c_str.to_str() {
            Err(_) => "there",
            Ok(string) => string,
        };
        CString::new("Hello There - ".to_owned() + recipient).unwrap().into_raw()
    }
}

Step 14: For setup of rust function in JAVA or Kotlin

For JAVA:
public class Greetings {
    private static native String greeting(final String pattern);

    public String sayHello(String to) {
        return greeting(to);
    }
}
For Kotlin:
/** This file is used as a namespace for all the exported Rust functions. */
@file:JvmName("Greetings")

external fun welcome(pattern: String): String?

Step 15: To call the function in Activity or fragment

val g = Greetings()
val r: String = g.sayHello("Loki")
binding.iclContentMain.textView.text = welcome(" Lokesh \n") + r

After Integration your result will be like the image shown in the beginning.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published