Mẫu Android Rust

Trang này chứa thông tin về Nhật ký Android, cung cấp ví dụ về Rust AIDL, hướng dẫn bạn cách gọi Rust từ C và hướng dẫn Tương tác Rust/C++ bằng CXX.

Ghi nhật ký Android

Ví dụ sau cho thấy cách bạn có thể ghi nhật ký thông điệp vào logcat (trên thiết bị) hoặc stdout (trên máy chủ).

Trong mô-đun Android.bp, hãy thêm libloggerliblog_rust làm phần phụ thuộc:

rust_binary {
    name: "logging_test",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblogger",
        "liblog_rust",
    ],
}

Tiếp theo, trong nguồn Rust, hãy thêm mã này:

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!");
}

Tức là thêm hai phần phụ thuộc nêu trên (libloggerliblog_rust), gọi phương thức init một lần (bạn có thể gọi phương thức này nhiều lần nếu cần) và ghi nhật ký thông báo bằng macro được cung cấp. Xem kho ghi nhật ký để biết danh sách các tuỳ chọn cấu hình có thể có.

Thùng trình ghi nhật ký cung cấp API để xác định nội dung bạn muốn ghi. Tuỳ thuộc vào việc mã đang chạy trên thiết bị hay trên máy chủ (chẳng hạn như một phần của quy trình kiểm thử phía máy chủ), thông báo sẽ được ghi nhật ký bằng android_logger hoặc env_logger.

Ví dụ về Rust AIDL

Phần này cung cấp một ví dụ kiểu Hello World về cách sử dụng AIDL với Rust.

Sử dụng phần Tổng quan về AIDL trong Hướng dẫn dành cho nhà phát triển Android làm điểm xuất phát, hãy tạo external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl với các nội dung sau trong tệp 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);
}

Sau đó, trong tệp external/rust/binder_example/aidl/Android.bp, hãy xác định mô-đun aidl_interface. Bạn phải bật phần phụ trợ Rust một cách rõ ràng vì phần phụ trợ này không được bật theo mặc định.

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,
        },
    },
}

Phần phụ trợ AIDL là một trình tạo nguồn Rust, vì vậy, nó hoạt động giống như các trình tạo nguồn Rust khác và tạo ra một thư viện Rust. Mô-đun thư viện Rust đã tạo có thể được các mô-đun Rust khác sử dụng làm phần phụ thuộc. Ví dụ về cách sử dụng thư viện đã tạo làm phần phụ thuộc, bạn có thể xác định rust_library như sau trong 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",
    ],
}

Xin lưu ý rằng định dạng tên mô-đun cho thư viện do AIDL tạo được dùng trong rustlibs là tên mô-đun aidl_interface, theo sau là -rust; trong trường hợp này là com.example.android.remoteservice-rust.

Sau đó, bạn có thể tham chiếu giao diện AIDL trong src/lib.rs như sau:

// 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(())
    }
}

Cuối cùng, khởi động dịch vụ trong tệp nhị phân Rust như minh hoạ dưới đây:

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()
}

Ví dụ về Rust AIDL không đồng bộ

Phần này cung cấp ví dụ kiểu Hello World về cách sử dụng AIDL với Rust không đồng bộ.

Tiếp tục ở ví dụ về RemoteService, thư viện phụ trợ AIDL đã tạo sẽ bao gồm các giao diện không đồng bộ có thể dùng để triển khai máy chủ không đồng bộ cho giao diện AIDL RemoteService.

Bạn có thể triển khai giao diện máy chủ không đồng bộ IRemoteServiceAsyncServer được tạo như sau:

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(())
    }
}

Bạn có thể bắt đầu triển khai máy chủ không đồng bộ như sau:

#[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();
    });
}

Xin lưu ý rằng bạn cần có block_in_place để thoát khỏi ngữ cảnh không đồng bộ, cho phép join_thread_pool sử dụng block_on trong nội bộ. Điều này là do #[tokio::main] gói mã trong lệnh gọi đến block_onjoin_thread_pool có thể gọi block_on khi xử lý giao dịch đến. Việc gọi block_on từ bên trong block_on sẽ dẫn đến sự cố. Bạn cũng có thể tránh điều này bằng cách tạo môi trường thời gian chạy tokio theo cách thủ công thay vì sử dụng #[tokio::main], sau đó gọi join_thread_pool bên ngoài phương thức block_on.

Ngoài ra, thư viện do phần phụ trợ gỉ sắt tạo ra bao gồm một giao diện cho phép triển khai IRemoteServiceAsync ứng dụng không đồng bộ cho RemoteService. Bạn có thể triển khai như sau:

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),
    }
}

Gọi Rust từ C

Ví dụ này cho thấy cách gọi Rust từ C.

Thư viện Rust mẫu

Xác định tệp libsimple_printer trong external/rust/simple_printer/libsimple_printer.rs như sau:

//! 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!");
}

Thư viện Rust phải xác định các tiêu đề mà các mô-đun C phụ thuộc có thể lấy vào, vì vậy, hãy xác định tiêu đề external/rust/simple_printer/simple_printer.h như sau:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Định nghĩa external/rust/simple_printer/Android.bp như bạn thấy ở đây:

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: ["."],
}

Ví dụ về tệp nhị phân C

Xác định external/rust/c_hello_rust/main.c như sau:

#include "simple_printer.h"

int main() {
  print_c_hello_rust();
  return 0;
}

Xác định external/rust/c_hello_rust/Android.bp như sau:

cc_binary {
    name: "c_hello_rust",
    srcs: ["main.c"],
    shared_libs: ["libsimple_c_printer"],
}

Cuối cùng, hãy tạo bằng cách gọi m c_hello_rust.

Khả năng tương tác Rust-Java

Thùng jni cung cấp khả năng tương tác của Rust với Java thông qua Giao diện gốc Java (JNI). Tệp này xác định các định nghĩa kiểu dữ liệu cần thiết cho Rust để tạo ra thư viện Rust cdylib cắm trực tiếp vào JNI của Java (JNIEnv, JClass, JString, v.v.). Không giống như các liên kết C++ thực hiện codegen thông qua cxx, khả năng tương tác của Java thông qua JNI không yêu cầu bước tạo mã trong quá trình xây dựng. Do đó, hệ thống xây dựng không cần hỗ trợ đặc biệt. Mã Java tải cdylib do Rust cung cấp giống như mọi thư viện gốc khác.

Cách sử dụng

Cách sử dụng trong cả mã Rust và Java được đề cập trong tài liệu về vùng chứa jni. Vui lòng làm theo ví dụ Bắt đầu được cung cấp tại đó. Sau khi bạn viết src/lib.rs, hãy quay lại trang này để tìm hiểu cách tạo thư viện bằng hệ thống xây dựng của Android.

Định nghĩa bản dựng

Java yêu cầu cung cấp thư viện Rust dưới dạng cdylib để có thể tải động. Định nghĩa thư viện Rust trong Soong như sau:

rust_ffi_shared {
    name: "libhello_jni",
    crate_name: "hello_jni",
    srcs: ["src/lib.rs"],

    // The jni crate is required
    rustlibs: ["libjni"],
}

Thư viện Java liệt kê thư viện Rust là phần phụ thuộc required; điều này đảm bảo thư viện Rust được cài đặt trên thiết bị cùng với thư viện Java mặc dù không phải là phần phụ thuộc tại thời điểm tạo bản dựng:

java_library {
        name: "libhelloworld",
        [...]
        required: ["libhellorust"]
        [...]
}

Ngoài ra, nếu bạn phải đưa thư viện Rust vào tệp AndroidManifest.xml, hãy thêm thư viện vào uses_libs như sau:

java_library {
        name: "libhelloworld",
        [...]
        uses_libs: ["libhellorust"]
        [...]
}

Khả năng tương tác của Rust–C++ bằng CXX

Hộp CXX cung cấp FFI an toàn giữa Rust và một tập hợp con của C++. Tài liệu về CXX đưa ra các ví dụ điển hình về cách hoạt động nói chung và bạn nên đọc trước tài liệu này để làm quen với thư viện và cách thư viện này kết nối C++ và Rust. Ví dụ sau đây cho thấy cách sử dụng tính năng này trong Android.

Để CXX tạo mã C++ mà Rust gọi vào, hãy xác định genrule để gọi CXX và cc_library_static để gói mã đó vào một thư viện. Nếu bạn dự định yêu cầu C++ gọi mã Rust hoặc sử dụng các kiểu được chia sẻ giữa C++ và Rust, hãy xác định quy tắc gen thứ hai (để tạo tiêu đề C++ chứa các liên kết Rust).

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"],
}

Công cụ cxxbridge được dùng ở trên để tạo phía C++ của cầu nối. Tiếp theo, thư viện tĩnh libcxx_test_cpp sẽ được dùng làm phần phụ thuộc cho tệp thực thi Rust:

rust_binary {
    name: "cxx_test",
    srcs: ["lib.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["libcxx_test_cpp"],
}

Trong tệp .cpp.hpp, hãy xác định các hàm C++ theo ý bạn, sử dụng các loại trình bao bọc CXX nếu muốn. Ví dụ: định nghĩa cxx_test.hpp chứa nội dung sau:

#pragma once

#include "rust/cxx.h"
#include "lib.rs.h"

int greet(rust::Str greetee);

Trong khi cxx_test.cpp chứa

#include "cxx_test.hpp"
#include "lib.rs.h"

#include <iostream>

int greet(rust::Str greetee) {
  std::cout << "Hello, " << greetee << std::endl;
  return get_num();
}

Để sử dụng tính năng này từ Rust, hãy xác định một cầu CXX như bên dưới trong lib.rs:

#[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;
}