Các mẫu Android Rust

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

Ghi nhật ký Android

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

Trong mô-đun Android.bp của bạn, 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 của bạn, hãy thêm mã này:

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

Nghĩa là, thêm hai phần phụ thuộc đượ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 nó 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 thùng ghi nhật ký để biết danh sách các tùy chọn cấu hình có thể có.

Thùng ghi nhật ký cung cấp API để xác định nội dung bạn muốn ghi. Tùy 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 quá trình kiểm tra phía máy chủ), thông báo sẽ được ghi lại bằng cách sử dụng android_logger hoặc env_logger .

Ví dụ 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 bắt đầu, 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ô aidl_interface . Bạn phải bật phần phụ trợ Rust một cách rõ ràng vì nó 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 ra có thể được sử dụng bởi các mô-đun Rust khác như một phần phụ thuộc. Là một ví dụ về việc sử dụng thư viện được tạo làm phần phụ thuộc, một 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",
    ],
}

Lưu ý rằng định dạng tên mô-đun cho thư viện do AIDL tạo được sử 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 .

Giao diện AIDL sau đó 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ụ ở dạng nhị phân Rust như hiển thị bên dưới:

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ụ Async Rust AIDL

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

Tiếp tục với ví dụ RemoteService , thư viện phụ trợ AIDL được tạo bao gồm các giao diện không đồng bộ có thể được sử dụng để triển khai 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ộ được tạo IRemoteServiceAsyncServer có thể được 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(())
    }
}

Việc triển khai máy chủ async có thể được bắt đầu 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 cần có block_in_place để rời khỏi bối 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 tới block_onjoin_thread_pool có thể gọi block_on khi xử lý một giao dịch đến. Việc gọi block_on từ bên trong block_on sẽ dẫn đến hoảng loạn. Điều này cũng có thể tránh được bằng cách xây dự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 .

Hơn nữa, thư viện được tạo phụ trợ rỉ sét bao gồm một giao diện cho phép triển khai ứng dụng khách không đồng bộ IRemoteServiceAsync cho RemoteService Giao diện này có thể đượ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::get_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ể 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

Xác định 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 include_dirs so cc_binary knows where the headers are.
    include_dirs: ["."],
}

Ví dụ C nhị phân

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, xây dựng bằng cách gọi m c_hello_rust .

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). Nó định nghĩa các định nghĩa kiểu cần thiết để Rust tạo ra một 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. Vì vậy 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ư bất kỳ thư viện gốc nào khác.

Cách sử dụng

Việc sử dụng cả mã Rust và Java đều được đề cập trong tài liệu jni Crate . Hãy làm theo ví dụ Bắt đầu được cung cấp ở đó. 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.

Xây dựng định nghĩa

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 như một phần phụ thuộc required ; điều này đảm bảo nó được cài đặt vào thiết bị cùng với thư viện Java mặc dù nó không phụ thuộc vào thời gian xây 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"]
        [...]
}

Tương tác 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 CXX đưa ra các ví dụ hay về cách hoạt động nói chung và chúng tôi khuyên bạn nên đọc tài liệu này trước để làm quen với thư viện cũng như cách nó kết nối C++ và Rust. Ví dụ sau đây cho thấy cách sử dụng nó trong Android.

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

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

Trong các tệp .cpp.hpp , hãy xác định các hàm C++ theo ý muốn của bạn, sử dụng các kiểu trình bao bọc CXX như mong 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 điều này từ Rust, hãy xác định 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;
}