本頁包含 Android Logging 的相關資訊、Rust AIDL 範例,說明如何從 C 呼叫 Rust ,並提供使用 CXX 的 Rust/C++ 互通性操作說明。
Android 記錄功能
以下範例說明如何將訊息記錄到 logcat
(裝置上) 或 stdout
(主機上)。
在 Android.bp
模組中,將 liblogger
和 liblog_rust
新增為依附元件:
rust_binary {
name: "logging_test",
srcs: ["src/main.rs"],
rustlibs: [
"liblogger",
"liblog_rust",
],
}
接著,在 Rust 原始碼中新增下列程式碼:
use log::{debug, error, LevelFilter};
fn main() {
let _init_success = logger::init(
logger::Config::default()
.with_tag_on_device("mytag")
.with_max_level(LevelFilter::Trace),
);
debug!("This is a debug message.");
error!("Something went wrong!");
}
也就是說,您需要新增上述兩個依附元件 (liblogger
和 liblog_rust
)、呼叫 init
方法一次 (必要時可多次呼叫),並使用提供的巨集記錄訊息。如需可能設定選項的清單,請參閱 Logger crate。
Logger crate 提供的 API 可用來定義要記錄的資料。視程式碼是在裝置端執行,還是在主機上執行 (例如主機端測試的一部分),系統會使用 android_logger 或 env_logger 記錄訊息。
Rust AIDL 範例
本節提供將 AIDL 與 Rust 搭配使用的 Hello World 樣式範例。
以 Android 開發人員指南的「AIDL 簡介」一節為起點,在 IRemoteService.aidl
檔案中使用下列內容建立 external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl
:
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service, to do evil things with it. */
int getPid();
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
接著,在 external/rust/binder_example/aidl/Android.bp
檔案中定義 aidl_interface
模組。您必須明確啟用 Rust 後端,因為該後端並未預設啟用。
aidl_interface {
name: "com.example.android.remoteservice",
srcs: [ "aidl/com/example/android/*.aidl", ],
unstable: true, // Add during development until the interface is stabilized.
backend: {
rust: {
// By default, the Rust backend is not enabled
enabled: true,
},
},
}
AIDL 後端是 Rust 來源產生器,因此運作方式與其他 Rust 來源產生器相同,並產生 Rust 程式庫。產生的 Rust 程式庫模組可供其他 Rust 模組使用做為依附元件。例如使用產生的程式庫做為依附元件的範例,rust_library
可以在 external/rust/binder_example/Android.bp
中定義如下:
rust_library {
name: "libmyservice",
srcs: ["src/lib.rs"],
crate_name: "myservice",
rustlibs: [
"com.example.android.remoteservice-rust",
"libbinder_rs",
],
}
請注意,rustlibs
中使用的 AIDL 產生程式庫的模組名稱格式為 aidl_interface
模組名稱,後面接著 -rust
;在本例中為 com.example.android.remoteservice-rust
。
接著,您可以在 src/lib.rs
中參照 AIDL 介面,如下所示:
// Note carefully the AIDL crates structure:
// * the AIDL module name: "com_example_android_remoteservice"
// * next "::aidl"
// * next the AIDL package name "::com::example::android"
// * the interface: "::IRemoteService"
// * finally, the 'BnRemoteService' and 'IRemoteService' submodules
//! This module implements the IRemoteService AIDL interface
use com_example_android_remoteservice::aidl::com::example::android::{
IRemoteService::{BnRemoteService, IRemoteService}
};
use binder::{
BinderFeatures, Interface, Result as BinderResult, Strong,
};
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyService;
impl Interface for MyService {}
impl IRemoteService for MyService {
fn getPid(&self) -> BinderResult<i32> {
Ok(42)
}
fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64, _: &str) -> BinderResult<()> {
// Do something interesting...
Ok(())
}
}
最後,在 Rust 二進位檔中啟動服務,如下所示:
use myservice::MyService;
fn main() {
// [...]
let my_service = MyService;
let my_service_binder = BnRemoteService::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder.as_binder())
.expect("Failed to register service?");
// Does not return - spawn or perform any work you mean to do before this call.
binder::ProcessState::join_thread_pool()
}
非同步 Rust AIDL 範例
本節提供 Hello World 風格的範例,說明如何搭配使用 AIDL 和非同步 Rust。
延續上 RemoteService
範例,產生的 AIDL 後端程式庫包含非同步介面,可用於實作 AIDL 介面 RemoteService
的非同步伺服器實作。
產生的非同步伺服器介面 IRemoteServiceAsyncServer
可以實作如下:
use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::{
BnRemoteService, IRemoteServiceAsyncServer,
};
use binder::{BinderFeatures, Interface, Result as BinderResult};
/// This struct is defined to implement IRemoteServiceAsyncServer AIDL interface.
pub struct MyAsyncService;
impl Interface for MyAsyncService {}
#[async_trait]
impl IRemoteServiceAsyncServer for MyAsyncService {
async fn getPid(&self) -> BinderResult<i32> {
//Do something interesting...
Ok(42)
}
async fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64,_: &str,) -> BinderResult<()> {
//Do something interesting...
Ok(())
}
}
以下是非同步伺服器實作的啟動方式:
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
let my_service = MyAsyncService;
let my_service_binder = BnRemoteService::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder.as_binder())
.expect("Failed to register service?");
task::block_in_place(move || {
binder::ProcessState::join_thread_pool();
});
}
請注意,您需要使用 block_in_place 來退出非同步結構定義,讓 join_thread_pool
在內部使用 block_on。這是因為 #[tokio::main]
會在對 block_on
的呼叫中包裝程式碼,而 join_thread_pool
在處理傳入交易時,可能會呼叫 block_on
。從 block_on
內呼叫 block_on
會導致恐慌。您也可以手動建構 Tokio 執行階段,而非使用 #[tokio::main]
,然後在 block_on
方法之外呼叫 join_thread_pool
,藉此避免發生這種情況。
此外,Rust 後端產生的程式庫中有一個介面,可實作 RemoteService
的非同步用戶端 IRemoteServiceAsync
,實作方式如下:
use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::IRemoteServiceAsync;
use binder_tokio::Tokio;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let binder_service = binder_tokio::wait_for_interface::<dyn IRemoteServiceAsync<Tokio>>("myservice");
let my_client = binder_service.await.expect("Cannot find Remote Service");
let result = my_client.getPid().await;
match result {
Err(err) => panic!("Cannot get the process id from Remote Service {:?}", err),
Ok(p_id) => println!("PID = {}", p_id),
}
}
從 C 呼叫 Rust
這個範例說明如何從 C 呼叫 Rust。
Rust 程式庫範例
請按照下列方式在 external/rust/simple_printer/libsimple_printer.rs
中定義 libsimple_printer
檔案:
//! A simple hello world example that can be called from C
#[no_mangle]
/// Print "Hello Rust!"
pub extern fn print_c_hello_rust() {
println!("Hello Rust!");
}
Rust 程式庫必須定義依附的 C 模組可提取的標頭,因此請按照下列方式定義 external/rust/simple_printer/simple_printer.h
標頭:
#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H
void print_c_hello_rust();
#endif
請定義 external/rust/simple_printer/Android.bp
,如下所示:
rust_ffi {
name: "libsimple_c_printer",
crate_name: "simple_c_printer",
srcs: ["libsimple_c_printer.rs"],
// Define export_include_dirs so cc_binary knows where the headers are.
export_include_dirs: ["."],
}
C 二進位檔範例
按照以下方式定義 external/rust/c_hello_rust/main.c
:
#include "simple_printer.h"
int main() {
print_c_hello_rust();
return 0;
}
按照以下方式定義 external/rust/c_hello_rust/Android.bp
:
cc_binary {
name: "c_hello_rust",
srcs: ["main.c"],
shared_libs: ["libsimple_c_printer"],
}
最後,請呼叫 m c_hello_rust
進行建構。
Rust-Java 互通性
jni
crate 可透過 Java Native Interface (JNI) 提供 Rust 與 Java 的互通性。它會定義 Rust 所需的型別定義,產生可直接插入 Java JNI (JNIEnv
、JClass
、JString
等) 的 Rust cdylib
程式庫。與透過 cxx
執行 codegen 的 C++ 繫結不同,透過 JNI 進行的 Java 互通性不需要在建構期間執行程式碼產生步驟。因此不需要特殊的建構系統支援。Java 程式碼會像載入任何其他原生程式庫一樣,載入 Rust 提供的 cdylib
。
用量
jni
crate 說明文件涵蓋了 Rust 和 Java 程式碼中的用法。請按照入門指南中的範例操作。編寫 src/lib.rs
後,請返回這個頁面,瞭解如何使用 Android 的建構系統建構程式庫。
建構定義
Java 需要將 Rust 程式庫做為 cdylib
提供,以便以動態方式載入。Soong 中的 Rust 程式庫定義如下:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
// The jni crate is required
rustlibs: ["libjni"],
}
Java 程式庫會將 Rust 程式庫列為 required
依附元件,確保即使不是建構時間依附元件,也能與 Java 程式庫一併安裝到裝置上:
java_library {
name: "libhelloworld",
[...]
required: ["libhellorust"]
[...]
}
此外,如果您必須在 AndroidManifest.xml
檔案中加入 Rust 程式庫,請將程式庫新增至 uses_libs
,如下所示:
java_library {
name: "libhelloworld",
[...]
uses_libs: ["libhellorust"]
[...]
}
使用 CXX 的 Rust-C++ 互通性
CXX Crate 在 Rust 和 C++ 子集之間提供安全的 FFI。CXX 說明文件提供了常見的運作方式範例,建議您先閱讀這份文件,以便熟悉程式庫和 C++ 和 Rust 的連接方式。以下範例顯示如何在 Android 中使用。
如要讓 CXX 產生 Rust 呼叫的 C++ 程式碼,請定義 genrule
來叫用 CXX,並定義 cc_library_static
將其捆綁至程式庫。如果您打算讓 C++ 呼叫 Rust 程式碼,或是使用 C++ 和 Rust 之間共用的類型,請定義第二個 Genrule (產生含有 Rust 繫結的 C++ 標頭)。
cc_library_static {
name: "libcxx_test_cpp",
srcs: ["cxx_test.cpp"],
generated_headers: [
"cxx-bridge-header",
"libcxx_test_bridge_header"
],
generated_sources: ["libcxx_test_bridge_code"],
}
// Generate the C++ code that Rust calls into.
genrule {
name: "libcxx_test_bridge_code",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) > $(out)",
srcs: ["lib.rs"],
out: ["libcxx_test_cxx_generated.cc"],
}
// Generate a C++ header containing the C++ bindings
// to the Rust exported functions in lib.rs.
genrule {
name: "libcxx_test_bridge_header",
tools: ["cxxbridge"],
cmd: "$(location cxxbridge) $(in) --header > $(out)",
srcs: ["lib.rs"],
out: ["lib.rs.h"],
}
上述 cxxbridge
工具是用來產生橋接設定的 C++ 端。接著,我們會使用 libcxx_test_cpp
靜態程式庫,做為 Rust 可執行檔的依附元件:
rust_binary {
name: "cxx_test",
srcs: ["lib.rs"],
rustlibs: ["libcxx"],
static_libs: ["libcxx_test_cpp"],
}
在 .cpp
和 .hpp
檔案中,視需要使用 CXX 包裝函式類型定義 C++ 函式。例如,cxx_test.hpp
定義包含下列項目:
#pragma once
#include "rust/cxx.h"
#include "lib.rs.h"
int greet(rust::Str greetee);
cxx_test.cpp
包含
#include "cxx_test.hpp"
#include "lib.rs.h"
#include <iostream>
int greet(rust::Str greetee) {
std::cout << "Hello, " << greetee << std::endl;
return get_num();
}
如要從 Rust 使用此功能,請在 lib.rs
中定義 CXX 橋接器,如下所示:
#[cxx::bridge]
mod ffi {
unsafe extern "C++" {
include!("cxx_test.hpp");
fn greet(greetee: &str) -> i32;
}
extern "Rust" {
fn get_num() -> i32;
}
}
fn main() {
let result = ffi::greet("world");
println!("C++ returned {}", result);
}
fn get_num() -> i32 {
return 42;
}