本文包含有关 Android 日志记录的信息,并提供了一个 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, Level};
fn main() {
let init_success = logger::init(
logger::Config::default()
.with_tag_on_device("mytag")
.with_min_level(Level::Trace),
);
debug!("This is a debug message.");
error!("Something went wrong!");
}
也就是说,添加上述两个依赖项(liblogger
和 liblog_rust
),调用 init
方法一次(可以根据需要调用多次),并使用提供的宏记录日志消息。如需查看可能使用的配置选项列表,请参阅日志记录器 crate。
日志记录器 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 库模块用作依赖项。举一个将生成的库用作依赖项的例子,您可以按以下方式在 external/rust/binder_example/Android.bp
中定义 rust_library
:
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 示例
本部分提供了一个将 AIDL 与异步 Rust 搭配使用的 Hello World 式示例。
继续以 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 原生接口 (JNI) 提供与 Java 的 Rust 互操作性。它定义了 Rust 生成 Rust cdylib
库所需的必要类型定义,该库可直接插入 Java 的 JNI(JNIEnv
、JClass
、JString
等等)。与通过 cxx
执行代码生成的 C++ 绑定不同,通过 JNI 提供的 Java 互操作性不需要在构建期间执行代码生成步骤。因此,它不需要特殊的构建系统支持。与任何其他原生库一样,Java 代码会加载 Rust 提供的 cdylib
。
用法
jni
crate 文档中介绍了在 Rust 和 Java 代码中的用法。请参考文档中提供的开始使用示例。编写 src/lib.rs
之后,请返回到此页面,了解如何使用 Android 的构建系统构建库。
build 定义
Java 要求以 cdylib
形式提供 Rust 库,以便可以动态加载该库。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。
如需让 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;
}