今回は
前回は前提知識をなるべく活かす方向でC言語のレイヤーを挟みました。
今回は全体的な構成をシンプルに、なるべく使用言語などを少なくしたいのでRustでJNI用のライブラリを直接作成し呼び出します。
また、Pluginやビルドスクリプトの設定をしてRustのビルドも同時に行いたいと思います。
一応、比較のために今回の構成を図にすると以下の様になります。単純にJNIでRustで作成したライブラリを呼び出す形となります。
手順
早速、実際の手順を確認していきます。
前提として前回で作成したJNIを使用したプロジェクトを使用します。
大まかに下記の手順で進めます。
- Rust言語をビルドするgradle plugin (内部的にcargoを呼び出す)の設定
- Rust言語でJNIから呼ばれるライブラリの作成
Android Studioでプロジェクトのビルド設定
RustのソースファイルをAndroid Studioでビルドするには mozilla/rust-android-gradle のプラグインを導入します。
気をつける点として下記の項目を全て修正してからビルドスクリプトのsyncを行った方が良いと思われます。
1ファイルずつエラーを確認しながら進めると、1ファイルとして正しく記述されていても、全体として整合性が合わないためのエラーが出たりするので逆に混乱する場合があります。
-
libs.versions.toml
の修正 - プロジェクトルートの
build.gradle.kts
の修正 - モジュールの
build.gradle.kts
の修正 -
local.properties
の修正
libs.versions.toml
の修正
gradle/libs.versions.toml
にプラグインのバージョンとidを指定します。
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,6 +8,7 @@ espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
constraintlayout = "2.2.0"
+mozillaRust = "0.9.6"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -21,4 +22,5 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+mozilla-rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version.ref = "mozillaRust" }
プロジェクトルートの build.gradle.kts
の修正
プロジェクトのルートにある build.gradle.kts
を下記のように修正します。
diff a/build.gradle.kts b/build.gradle.kts
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -2,4 +2,5 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
+ alias(libs.plugins.mozilla.rust.android) apply false
}
\ No newline at end of file
モジュールの build.gradle.kts
の修正
app/build.gradle.kts
を下記のように編集します。
既存のcmakeを使用したC/C++のビルド設定を削除し、Rustのビルド設定をします。
cargo {...}
内の module
はビルド対象の Cargo.toml
があるパスを指定します。今回のプロジェクトルートから app/src/main/rust
となります。
同じく libname
は Cargo.toml
内の [package]
の name = "myapplication"
と同じ名前を設定します。また、これはJava/Kotlinから呼び出す時の System.loadLibrary("myapplication")
でも、同じライブラリ名を使用しますので全て同じ名前にする必要があります。
tasks.configureEach {...}
ではAndroid Studio上でビルドを実行した時、Rustのビルドも実行し、作成されたライブラリを適切にパッケージングできるよに指定しています。
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.mozilla.rust.android)
}
android {
@@ -33,12 +34,6 @@ android {
kotlinOptions {
jvmTarget = "11"
}
- externalNativeBuild {
- cmake {
- path = file("src/main/cpp/CMakeLists.txt")
- version = "3.22.1"
- }
- }
buildFeatures {
viewBinding = true
}
@@ -53,4 +48,17 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
+}
+
+cargo {
+ module = "src/main/rust"
+ libname = "myapplication"
+ targets = listOf("arm", "arm64", "x86", "x86_64")
+}
+
+tasks.configureEach {
+ if (name == "mergeDebugJniLibFolders" || name == "mergeReleaseJniLibFolders") {
+ dependsOn("cargoBuild")
+ inputs.dir(layout.buildDirectory.dir("rustJniLibs/android"))
+ }
}
\ No newline at end of file
local.properties
の修正
mozilla/rust-android-gradle ではRustのビルド時にシステムのPythonを実行しています。
最近のUbuntuでは標準で python
ではなく python3
と呼び出す必要があります。開発環境にあったPythonコマンドをプロジェクトルートの local.properties
に追加する必要があります。
rust.pythoncommand=python3
Rustライブラリの用意
今回はRustのソースファイルを app/src/main/rust
に新規作成します。
$ cd <path to project root>/app/src/main
$ cargo new --lib rust
上記のコマンドで rust
ディレクトリが作成され、その中に作成された以下のファイルを下記の様に修正します。
app/src/main/rust/Cargo.toml
の修正
-
[package]
のname
を"myapplication"
と変更し - JNIから呼び出せるように
[lib]
のcrate-type
を変更 - RustからJNIのAPIを呼び出せるように
[dependencies]
にjni
を追加
diff --git a/app/src/main/rust/Cargo.toml b/app/src/main/rust/Cargo.toml
--- a/app/src/main/rust/Cargo.toml
+++ b/app/src/main/rust/Cargo.toml
@@ -1,6 +1,10 @@
[package]
-name = "rust"
+name = "myapplication"
version = "0.1.0"
edition = "2021"
+[lib]
+crate-type = ["cdylib"]
+
[dependencies]
+jni = "0.21.1"
app/src/main/rust/src/lib.rs
の修正
前回、C言語で作成した部分をRustで置き換えます。
Java/KotlinからJNIで呼び出し可能な様に #[no_mangle]
や extern "C"
を追加し、関数名もJNIの規則沿ったものに変更します。
diff --git a/app/src/main/rust/src/lib.rs b/app/src/main/rust/src/lib.rs
index b93cf3f..a7ea608 100644
--- a/app/src/main/rust/src/lib.rs
+++ b/app/src/main/rust/src/lib.rs
@@ -1,14 +1,32 @@
-pub fn add(left: u64, right: u64) -> u64 {
+use jni::JNIEnv;
+use jni::objects::JClass;
+use jni::sys::jstring;
+
+pub fn rustadd(left: u64, right: u64) -> u64 {
left + right
}
+#[no_mangle]
+pub extern "C" fn Java_com_example_myapplication_MainActivity_stringFromJNI(
+ env: JNIEnv,
+ _: JClass,
+) -> jstring {
+ let x: u64 = 1;
+ let y: u64 = 2;
+ let z: u64 = rustadd(x, y);
+
+ let buf = format!("{}+{}={} from rust library", x, y, z);
+ let output = env.new_string(buf).expect("Couldn't create java string!");
+ output.to_owned()
+}
+
まとめ
以上で、Android Studioでプロジェクト全体をビルドする時にRustのコードもコンパイルしJNI用のライブラリを作成して、適切に配置してapkなどのパッケージファイルにも同梱されるように設定できたかと思います。
環境構築で躓く点はあったものの、一度環境が構築できればビルド自体は既存のプロジェクトに統合されており開発環境としてはシンプルになり良かったと思います。
しかし、現状ではAndroid Studio上でRustのコードの編集時にコード補完などができていない状態で、別のエディタを使用しています。
Android Studioの元となるIntelliJ IDEAを開発しているJetBrainsではRust用IDEのRustRoverもあるのでなんとかなりそうなのですが(又はLSPサーバーであるrust-analyzerとの連携など)、現時点で連携方法がわからず、2つのエディタを行き来している状態となっています。