الگوهای Android Rust

این صفحه حاوی اطلاعاتی درباره Android Logging است، یک مثال Rust AIDL را ارائه می دهد، به شما می گوید که چگونه Rust را از C فراخوانی کنید ، و دستورالعمل هایی را برای Rust/C++ Interop با استفاده از CXX ارائه می دهد.

لاگ اندروید

مثال زیر نشان می دهد که چگونه می توانید پیام ها را به logcat (روی دستگاه) یا stdout (روی میزبان) وارد کنید.

در ماژول Android.bp خود، liblogger و liblog_rust به عنوان وابستگی اضافه کنید:

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

یعنی دو وابستگی نشان داده شده در بالا ( liblogger و liblog_rust ) را اضافه کنید، روش init را یک بار فراخوانی کنید (در صورت لزوم می توانید بیش از یک بار آن را فراخوانی کنید) و پیام ها را با استفاده از ماکروهای ارائه شده ثبت کنید. برای لیستی از گزینه های پیکربندی احتمالی به جعبه لاگر مراجعه کنید.

جعبه لاگر یک API برای تعریف آنچه می خواهید ثبت کنید ارائه می دهد. بسته به اینکه کد روی دستگاه اجرا می‌شود یا روی میزبان (مانند بخشی از آزمایش سمت میزبان)، پیام‌ها با استفاده از android_logger یا env_logger ثبت می‌شوند.

Rust AIDL مثال

این بخش نمونه ای به سبک Hello World از استفاده از AIDL با Rust را ارائه می دهد.

با استفاده از بخش AIDL Developer Guide به عنوان نقطه شروع، 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);
}

سپس در فایل external/rust/binder_example/aidl/Android.bp ، ماژول aidl_interface را تعریف کنید. شما باید به صراحت باطن 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,
        },
    },
}

Backend AIDL یک مولد منبع Rust است، بنابراین مانند سایر مولدهای منبع Rust عمل می کند و یک کتابخانه Rust تولید می کند. ماژول کتابخانه Rust تولید شده می تواند توسط سایر ماژول های Rust به عنوان یک وابستگی استفاده شود. به عنوان نمونه ای از استفاده از کتابخانه تولید شده به عنوان یک وابستگی، 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 async ارائه می دهد.

در ادامه مثال RemoteService ، کتابخانه باطن AIDL ایجاد شده شامل واسط های همگام است که می تواند برای پیاده سازی سرور async برای رابط AIDL RemoteService استفاده شود.

رابط سرور async ایجاد شده 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(())
    }
}

اجرای سرور async را می توان به صورت زیر شروع کرد:

#[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 interop

جعبه jni قابلیت همکاری Rust را با جاوا از طریق Java Native Interface (JNI) فراهم می کند. این تعاریف نوع لازم را برای Rust تعریف می کند تا یک کتابخانه Rust cdylib تولید کند که مستقیماً به JNI جاوا ( JNIEnv ، JClass ، JString و غیره) وصل می شود. برخلاف پیوندهای C++ که کدژن را از طریق cxx انجام می‌دهند، قابلیت همکاری جاوا از طریق JNI نیازی به مرحله تولید کد در طول ساخت ندارد. بنابراین نیازی به پشتیبانی سیستم ساخت خاصی ندارد. کد جاوا cdylib ارائه شده توسط Rust را مانند هر کتابخانه بومی دیگری بارگیری می کند.

استفاده

استفاده در کدهای Rust و Java در مستندات جعبه jni پوشش داده شده است. لطفاً مثال شروع ارائه شده در آنجا را دنبال کنید. پس از نوشتن src/lib.rs ، به این صفحه بازگردید تا نحوه ساخت کتابخانه با سیستم ساخت اندروید را بیاموزید.

تعریف بسازید

جاوا نیاز دارد که کتابخانه 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"],
}

کتابخانه جاوا کتابخانه Rust را به عنوان یک وابستگی required فهرست می کند. این تضمین می‌کند که در کنار کتابخانه جاوا روی دستگاه نصب شده است، حتی اگر این وابستگی به زمان ساخت نباشد:

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

از طرف دیگر، اگر باید کتابخانه Rust را در یک فایل AndroidManifest.xml قرار دهید، کتابخانه را به صورت زیر به uses_libs اضافه کنید:

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

Rust-C++ interop با استفاده از CXX

جعبه CXX FFI ایمن را بین Rust و زیر مجموعه ای از C++ فراهم می کند. مستندات CXX مثال‌های خوبی از نحوه عملکرد آن به طور کلی ارائه می‌دهد و پیشنهاد می‌کنیم ابتدا آن را بخوانید تا با کتابخانه و نحوه اتصال C++ و Rust آشنا شوید. مثال زیر نحوه استفاده از آن را در اندروید نشان می دهد.

برای اینکه CXX کد C++ را که Rust فراخوانی می‌کند تولید کند، یک genrule برای فراخوانی CXX و یک cc_library_static برای بسته‌بندی آن در یک کتابخانه تعریف کنید. اگر قصد دارید کد Rust را فراخوانی C++ داشته باشید، یا از انواع مشترک بین C++ و Rust استفاده کنید، ژانر دوم را تعریف کنید (برای ایجاد هدر 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 در ادامه به عنوان یک وابستگی برای فایل اجرایی Rust ما استفاده می شود:

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

در فایل‌های .cpp و .hpp ، توابع C++ را به دلخواه خود با استفاده از انواع بسته‌بندی 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;
}