Mẫu Android Rust

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 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à thông điệp nhật ký bằng 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 xem mã đang chạy trên thiết bị hay trên máy chủ lưu trữ (chẳng hạn như một phần của kiểm thử phía máy chủ), tin nhắn được ghi lại bằng android_logger hoặc env_logger.

Ví dụ về Rust AIDL

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

Sử dụng Tổng quan về AIDL trong Hướng dẫn cho nhà phát triển Android làm điểm bắt đầu, hãy tạo external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl với 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à trình tạo nguồn Rust, vì vậy, phần phụ trợ này hoạt động giống như nguồn Rust khác tạo và tạo 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ề việc sử dụng thư viện là phần phụ thuộc, thì rust_library có thể được định nghĩa 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 một ví dụ theo kiểu Hello World về việc 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 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ộ phương thức triển khai 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();
    });
}

Lưu ý rằng block_in_place là cần thiết để rời khỏi ngữ cảnh không đồng bộ, cho phép join_thread_pool sử dụng block_on nội bộ. Điều này là do #[tokio::main] gói mã trong một cuộc 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 do phần phụ trợ rust tạo ra còn có một giao diện cho phép triển khai ứng dụng không đồng bộ IRemoteServiceAsync cho RemoteService. Ứng dụng này có thể sẽ được 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.

Ví dụ về thư viện Rust

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 tiêu đề mà các mô-đun C phụ thuộc có thể kéo 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 SDK gốc Java Giao diện (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 đó, hệ thống xây dựng không cần hỗ trợ đặc biệt. Java mã sẽ tải cdylib do Rust cung cấp 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ề giỏ hàng jni. Năn nỉ làm theo hướng dẫn Bắt đầu ví dụ đượ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 xây dựng 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ể được 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 dưới dạng phần phụ thuộc required; điều này đảm bảo mã đượ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 gian xây 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"]
        [...]
}

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

Thùng 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 ví dụ điển hình về cách hoạt động của trang web và chúng tôi khuyên bạn nên đọc trước để làm quen với thư viện này cũng như cách thư viện này kết nối C++ và Rust. Chiến lược phát hành đĩa đơn ví dụ sau cho biết cách sử dụng thuộc tính này trong Android.

Để yêu cầu CXX tạo mã C++ mà Rust gọi vào, hãy xác định genrule thành gọi CXX và cc_library_static để gói nội dung đó 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 sử dụng ở trên để tạo phía C++ của cầu nối. libcxx_test_cpp Thư viện tĩnh được dùng tiếp theo 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;
}