Android Rust 模式

本頁提供 Android Logging 的相關資訊。 提供了 Rust AIDL 範例,說明如何 從 C 呼叫 Rust ,並提供操作說明 適用於使用 CXX 的 Rust/C++ 互通性

Android 記錄功能

以下範例說明如何將訊息記錄到 logcat (裝置端) 或 stdout (主持人)。

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

也就是說,請新增上述兩個依附元件 (libloggerliblog_rust)。 呼叫 init 方法一次 (必要時可以多次呼叫)。 藉此記錄訊息。詳情請參閱 記錄器 Crate ,查看可用的設定選項清單。

Logger crate 提供的 API 可用來定義要記錄的資料。視乎 程式碼是在裝置端或主機上執行 (例如 主機端測試),系統會使用 android_logger 記錄訊息 或 env_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_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_onjoin_thread_pool可能會打電話 block_on 來處理傳入交易。呼叫 block_on 內的 block_on 會導致恐慌。另一個好處是 就可以避免在建構 Tokio 執行階段時 手動 而不是使用 #[tokio::main],然後呼叫 join_thread_poolblock_on 方法之外。

此外,Trust 後端產生的程式庫中有一個介面 為 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 提供 Rust 與 Java 的互通性 介面 (JNI)。定義了 Rust 要產生的內容類型定義 直接插入 Java 的 JNI 的 Rust cdylib 程式庫 (JNIEnvJClassJString 等)。與透過 cxx 執行 Codegen 的 C++ 繫結不同, 透過 JNI 的 Java 互通性不需要產生程式碼步驟 執行容器的任務因此不需要特殊的建構系統支援。Java 程式碼會像載入任何其他原生程式庫一樣載入 Rust 提供的 cdylib

用量

Rust 和 Java 程式碼的使用方式都已在 jni Crate 說明文件。請 請參考入門指南 範例。編寫 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 可提供安全的 FFI Rust 和 C++ 的子集間的關係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 檔案中定義 C++ 函式。 視需要使用 CXX 包裝函式類型。 例如,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;
}