Mẫu Rust cho Android

Trang này chứa thông tin về Ghi nhật ký Android, cung cấp một ví dụ về Raust AIDL để biết cách gọi cho Rust từ C và cung cấp hướng dẫn cho Tương tác Rust/C++ bằng CXX.

Ghi nhật ký Android

Ví dụ sau đây cho thấy cách bạn có thể ghi nhật ký tin nhắn 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 hiển thị ở trên (libloggerliblog_rust), gọi phương thức init một lần (bạn có thể gọi nhiều lần nếu cần) và ghi nhật ký thông điệp bằng các macro được cung cấp. Xem Thùng rác để 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 rõ ràng phần phụ trợ Rust 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 được tạo có thể là đượ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 của thư viện do AIDL tạo sẽ được dùng trong rustlibs là tên mô-đun aidl_interface theo sau là -rust; trong trường hợp này, com.example.android.remoteservice-rust.

Sau đó, giao diện AIDL có thể được tham chiếu 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ới ví dụ về RemoteService, thư viện phụ trợ AIDL được tạo 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.

Giao diện máy chủ không đồng bộ đã tạo IRemoteServiceAsyncServer có thể là triển khai 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 quá trình 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ộ. Lý do là #[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. Gọi một block_on từ trong block_on dẫn đến sự hoảng loạn. Điều này cũng có thể có thể tránh được bằng cách xây dựng môi trường thời gian chạy tokio theo cách thủ công thay vì sử dụng #[tokio::main] rồi gọi join_thread_pool bên ngoài phương thức block_on.

Ngoài ra, thư viện được tạo bằng phần phụ trợ rust bao gồm một giao diện cho phép triển khai ứng dụng IRemoteServiceAsync 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: ["."],
}

Tệp nhị phân C mẫu

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

Hộp 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). Định nghĩa này xác định các định nghĩa cần thiết về kiểu dữ liệu để 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 việc tạo mã thông qua cxx, Khả năng tương tác Java thông qua JNI không yêu cầu bước tạo mã trong quá trình tạo bản dựng. Do đó, bạn không cần hỗ trợ hệ thống xây dựng đặ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 thư viện Rust được cung cấp dưới dạng cdylib để có thể tải thư viện này một cách linh độ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 phải đưa thư viện Rust vào AndroidManifest.xml hãy thêm thư viện vào uses_libs như sau:

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

Tương tác 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 để 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++ 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. Tiếp theo, thư viện tĩnh libcxx_test_cpp đượ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 khai báo các hàm C++ theo ý bạn, bằng cách sử dụng loại trình bao bọc CXX tuỳ ý. 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 cầu nối 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;
}