รูปแบบสนิมของ Android

หน้านี้มีข้อมูลเกี่ยวกับ Android Logging ที่มีตัวอย่าง Rust AIDL, บอกวิธีเรียกใช้ Rust จาก C พร้อม วิธีการสำหรับการทำงานร่วมกันของ Rust/C++ โดยใช้ CXX

การบันทึกของ Android

ตัวอย่างต่อไปนี้แสดงวิธีบันทึกข้อความไปยัง logcat (ในอุปกรณ์) หรือ stdout (ในโฮสต์)

ในโมดูล Android.bp ให้เพิ่ม liblogger และ liblog_rust เป็นทรัพยากร Dependency:

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

กล่าวคือ ให้เพิ่ม 2 รายการที่ขึ้นต่อกันที่แสดงด้านบน (liblogger และ liblog_rust) เรียกใช้เมธอด init 1 ครั้ง (เรียกใช้มากกว่า 1 ครั้งได้หากจำเป็น) และบันทึกข้อความโดยใช้มาโครที่ระบุ โปรดดูรายการตัวเลือกการกำหนดค่าที่เป็นไปได้ในลังล็อกเกอร์

แพ็กเกจบันทึกมี API สําหรับกําหนดสิ่งที่คุณต้องการบันทึก ข้อความจะได้รับการบันทึกโดยใช้ android_logger หรือ env_logger ทั้งนี้ขึ้นอยู่กับว่าโค้ดกำลังทำงานในอุปกรณ์หรือบนโฮสต์ (เช่น ส่วนหนึ่งของการทดสอบฝั่งโฮสต์)

ตัวอย่างของ Rust AIDL

ส่วนนี้จะแสดงตัวอย่างสไตล์ Hello World ของการใช้ AIDL กับ Rust

ใช้ส่วนภาพรวม AIDL ในคู่มือนักพัฒนาแอป Android เป็นจุดเริ่มต้น สร้าง 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);
}

จากนั้นกำหนดโมดูล aidl_interface ภายในไฟล์ external/rust/binder_example/aidl/Android.bp คุณต้องเปิดใช้แบ็กเอนด์ของ 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 ที่สร้างขึ้นเป็นทรัพยากร Dependency ได้ เพื่อเป็นตัวอย่างของการใช้ไลบรารีที่สร้างขึ้นเป็นทรัพยากร Dependency สามารถกําหนด 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",
    ],
}

โปรดทราบว่ารูปแบบชื่อโมดูลสำหรับไลบรารีที่ AIDL สร้างขึ้นซึ่งใช้ใน rustlibs คือชื่อโมดูล aidl_interface ตามด้วย -rust ในกรณีนี้คือ com.example.android.remoteservice-rust

จากนั้นจะอ้างอิงอินเทอร์เฟซ AIDL ใน src/lib.rs ได้โดยทำดังนี้

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

ตัวอย่าง Async 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_on และ join_thread_pool อาจโทรหา block_on เมื่อจัดการธุรกรรมที่เข้ามา การโทรหา block_on จากภายใน block_on จะทำให้คุณตื่นตระหนก นอกจากนี้ คุณยังหลีกเลี่ยงปัญหานี้ได้ด้วยการสร้างรันไทม์ของ Tokio ด้วยตนเองแทนการใช้ #[tokio::main] แล้วเรียกใช้ join_thread_pool นอกเมธอด block_on

นอกจากนี้ ไลบรารีที่สร้างขึ้นแบ็กเอนด์ของ Rust ยังมีอินเทอร์เฟซที่อนุญาตให้ติดตั้งใช้งานไคลเอ็นต์ IRemoteServiceAsync แบบไม่พร้อมกันสำหรับ RemoteService ซึ่งนำไปใช้ได้ดังนี้

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

การเรียกใช้ Rust จาก C

ตัวอย่างนี้แสดงวิธีเรียกใช้ Rust จาก C

ตัวอย่างไลบรารี Rust

กำหนดไฟล์ libsimple_printer ในexternal/rust/simple_printer/libsimple_printer.rs ดังนี้

//! 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 ช่วยให้ Rust ทำงานร่วมกับ Java ได้ผ่าน Java Native Interface (JNI) โดยกำหนดคำจำกัดความของประเภทที่จำเป็นสำหรับ Rust เพื่อสร้างไลบรารีของ Rust cdylib ที่เสียบเข้ากับ JNI ของ Java โดยตรง (JNIEnv, JClass,JString และอื่นๆ) การทำงานร่วมกันของ Java ผ่าน JNI ไม่จำเป็นต้องมีขั้นตอนการสร้างโค้ดระหว่างการบิลด์ ซึ่งแตกต่างจากการเชื่อมโยง C++ ที่ใช้ codegen ผ่าน cxx ดังนั้น จึงไม่จำเป็นต้องมีการสนับสนุนระบบบิลด์เป็นพิเศษ โค้ด Java จะโหลด cdylib ที่ Rust มีให้เหมือนกับไลบรารีแบบเนทีฟอื่นๆ

การใช้งาน

การใช้งานทั้งในโค้ด Rust และ Java อยู่ในjniเอกสารประกอบของ Crate โปรดทำตามตัวอย่างการเริ่มต้นใช้งาน ที่ระบุไว้ที่นั่น หลังจากเขียน src/lib.rs แล้ว ให้กลับมาที่หน้านี้เพื่อดูวิธีสร้างไลบรารีด้วยระบบบิลด์ของ Android

คําจํากัดความของบิลด์

Java กำหนดให้ระบุไลบรารี Rust เป็น cdylib เพื่อให้โหลดแบบไดนามิกได้ คําจํากัดความของคลัง Rust ใน Soong มีดังนี้

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

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

ไลบรารี Java แสดงรายการไลบรารี Rust เป็นทรัพยากร Dependency required ซึ่งจะช่วยให้มั่นใจว่ามีการติดตั้งไว้ในอุปกรณ์พร้อมกับไลบรารี Java แม้ว่าจะไม่ใช่ทรัพยากร Dependency ของบิลด์ก็ตาม

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

หรือหากต้องการรวมไลบรารี Rust ไว้ในไฟล์ AndroidManifest.xml ให้เพิ่มไลบรารีลงใน uses_libs ดังนี้

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

การทำงานร่วมกันของ Rust–C++ โดยใช้ CXX

ลัง CXX ให้ FFI ที่ปลอดภัยระหว่าง Rust และเซ็ตย่อยของ C++ เอกสารประกอบของ CXX ให้ตัวอย่างที่ดีเกี่ยวกับวิธีการทำงานโดยทั่วไป และเราขอแนะนำให้อ่านก่อนเพื่อทำความคุ้นเคยกับไลบรารีและวิธีการเชื่อมโยง C++ กับ Rust ตัวอย่างต่อไปนี้แสดงวิธีใช้ใน Android

หากต้องการให้ CXX สร้างโค้ด C++ ที่ Rust เรียกใช้ ให้กำหนด genrule เพื่อเรียก CXX และ cc_library_static เพื่อรวมกลุ่มนั้นลงในไลบรารี หากคุณวางแผนที่จะมีการเรียกใช้โค้ด Rust ของ C++ หรือใช้ประเภทที่แชร์ระหว่าง C++ และ Rust ให้กำหนด Genrule ที่ 2 (เพื่อสร้างส่วนหัว C++ ที่มีการเชื่อมโยง 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"],
}

ด้านบนจะใช้เครื่องมือ cxxbridge เพื่อสร้างด้าน C++ ของบริดจ์ ระบบจะใช้ไลบรารีแบบคงที่ของ libcxx_test_cpp เป็นทรัพยากร Dependency สำหรับไฟล์ปฏิบัติการ Rust ของเรา

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

ในไฟล์ .cpp และ .hpp ให้กำหนดฟังก์ชัน C++ ที่ต้องการโดยใช้ประเภท Wrapper ของ 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 ให้กำหนดบริดจ์ CXX ใน 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;
}