前言

继上一篇用 Rust 开发 iOS 开发折腾之后,我又折腾起 Android 了。

最开始我是先从 Android 上下手的,不过一直没成功,直到最近才成功了。

本文默认你已经把 Rust 安装好了。注意需要使用 stable channel ,我之前使用的是 nightly 所以一直失败。

nightly channel 会报 dl_iterate_phdr can't find 错误,这个错误一直误导了我,我一直以为是链接上的问题,所以折腾了很久。

直到我换了一台电脑试了一下,居然成功了,才发现使用的 channel 是不一样的。

添加架构支持

按照在 iOS 上的经验,这里需要添加对应架构的支持,如下所示

1
2
3
4
rustup target add aarch64-linux-android     # arm64
rustup target add armv7-linux-androideabi   # arm
rustup target add x86_64-linux-android      # x86_64
rustup target add i686-linux-android        # x86

新建一个Android工程配置rust插件

新建 Android 项目就不说了,这个例子会使用mozilla/rust-android-gradle这个插件来进行辅助开发,所以需要说一说插件的配置。在项目的 build.gradle 中加入如下配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath 'org.mozilla.rust-android-gradle:plugin:0.9.0'
    }
}

接着在 app module 中的 build.gradle 中加入如下配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'org.mozilla.rust-android-gradle.rust-android' // 新添加的
}


// 配置rust
cargo {
    module = "./rust" // 配置rust 所在的路径,也就是Cargo.toml所在的目录
    targets = ["arm64"] // 配置需要编译的架构
    libname = "rust" // 编译之后so的名字,这里需要和Cargo.toml 中lib.name一致
}

// 自动编译rust库
afterEvaluate {
    android.applicationVariants.all { variant ->
        def productFlavor = ""
        variant.productFlavors.each {
            productFlavor += "${it.name.capitalize()}"
        }
        def buildType = "${variant.buildType.name.capitalize()}"
        tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"])
    }
}

我们现在还没有创建 Rust 的项目,这里先把配置都先写上来了。

创建Rust库

Android 项目创建好之后,就可以创建 Rust 的库了

1
2
cd MyApplication/app            # 根据上面的配置我们的rust在app下
cargo new rust --lib            # 创建库项目,加上--lib

添加JNI依赖

由于我们需要在 KotlinRust 之间互相调用,所以还需要依赖 jni 这个 crate ,并且我们需要生成的是动态库

1
2
3
4
5
[dependencies]
jni = "0.19.0"                  # 添加jni依赖

[lib]
crate_type = ["dylib"]          # dylib 表示的是动态库

编写Kotlin代码

Kotlin 中调用 Rust 代码和调用 C/C++ 代码一样,先要把对应的 so 加载进来,并且需要把要调用的额函数声明为 externalJava 就是 native

接着就是调用了 Rust 函数了,然后把返回的字符串显示在 TextView 中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.sampleText.text = stringFromJNI() //显示从Rust中返回的字符串到TextView中
    }
    external fun stringFromJNI(): String

    companion object {
        init {
            System.loadLibrary("rust") //加载librust.so
        }
    }
}

编写Rust代码

上一步我们编写好了 Kotlin 代码,接下来就需要实现对应的 Rust 函数了。

根据 JNI 静态绑定的规则, Rust 中的函数名需要使用 Java_+包名_+类名_+函数名 所以 Kotlin 中对应的 Rust 的函数名为 Java_com_example_myapplication_MainActivity_stringFromJNI

我们需要函数名不被处理成别的这样才能被找到,所以需要加上 #[no_mangle] ,并且不采用 snake_case 进行命名,所以还要加上 #[allow(non_snake_case)]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
extern crate jni;

use jni::objects::{JObject};
use jni::sys::jstring;
use jni::JNIEnv;

#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn Java_com_example_myapplication_MainActivity_stringFromJNI(
    env: JNIEnv,
    _: JObject,
) -> jstring {
    let output = env.new_string("Hello Rust".to_owned()).unwrap();
    output.into_inner()
}

运行效果

代码都编写完了,点击运行得到如下效果

参考