Android Rust 模式

此页面包含有关Android 日志记录的信息,提供Rust AIDL 示例,告诉您如何从 C 调用 Rust ,并提供使用 CXX 的 Rust/C++ 互操作的说明。

安卓日志记录

以下示例显示如何将消息记录到logcat (设备上)或stdout (主机上)。

Android.bp模块中,添加libloggerliblog_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!");
}

也就是说,添加上面显示的两个依赖项( libloggerliblog_rust ),调用init方法一次(如果需要,您可以多次调用它),并使用提供的宏记录消息。请参阅记录器箱以获取可能的配置选项列表。

记录器箱提供了一个 API 来定义您想要记录的内容。根据代码是在设备上运行还是在主机上运行(例如主机端测试的一部分),使用android_loggerenv_logger记录消息。

Rust AIDL 示例

本节提供了将 AIDL 与 Rust 结合使用的 Hello World 风格示例。

使用 Android 开发人员指南AIDL 概述部分作为起点,创建external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl并在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) 提供 Rust 与 Java 的互操作性。它为 Rust 定义了必要的类型定义,以生成直接插入 Java 的 JNI( JNIEnvJClassJString等)的 Rust cdylib库。与通过cxx执行代码生成的 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板条箱在 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;
}