Pola Rust Android

Halaman ini berisi informasi tentang Android Logging, menyediakan contoh Rust AIDL, memberi tahu Anda cara memanggil Rust dari C , dan memberikan petunjuk untuk Interop Rust/C++ Menggunakan CXX.

Logging Android

Contoh berikut menunjukkan cara mencatat pesan ke logcat (di perangkat) atau stdout (di host).

Dalam modul Android.bp, tambahkan liblogger dan liblog_rust sebagai dependensi:

rust_binary {
    name: "logging_test",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblogger",
        "liblog_rust",
    ],
}

Selanjutnya, di sumber Rust Anda, tambahkan kode ini:

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

Artinya, tambahkan dua dependensi yang ditunjukkan di atas (liblogger dan liblog_rust), panggil metode init sekali (Anda bisa memanggilnya lebih dari sekali jika perlu), dan mencatat pesan log menggunakan makro yang disediakan. Lihat kotak logger untuk daftar kemungkinan opsi konfigurasi.

Peti logger menyediakan API untuk menentukan hal yang ingin Anda catat. Bergantung pada apakah kode berjalan di perangkat atau di host (seperti bagian dari pengujian sisi host), pesan dicatat menggunakan android_logger atau env_logger.

Contoh Rust AIDL

Bagian ini menyediakan contoh gaya Halo Dunia dalam menggunakan AIDL dengan Rust.

Menggunakan Panduan Developer Android Ringkasan AIDL sebagai titik awal, buat external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl dengan konten berikut dalam file 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);
}

Kemudian, dalam file external/rust/binder_example/aidl/Android.bp, tentukan Modul aidl_interface. Anda harus mengaktifkan backend Rust secara eksplisit karena tidak diaktifkan secara default.

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 adalah generator sumber Rust, sehingga beroperasi seperti sumber Rust lainnya generator dan menghasilkan perpustakaan Rust. Modul library Rust yang dihasilkan dapat digunakan oleh modul Rust lainnya sebagai dependensi. Sebagai contoh penggunaan library sebagai dependensi, rust_library dapat ditentukan sebagai berikut di 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",
    ],
}

Perhatikan bahwa format nama modul untuk library yang dihasilkan AIDL yang digunakan di rustlibs adalah nama modul aidl_interface diikuti dengan -rust; dalam hal ini, com.example.android.remoteservice-rust.

Selanjutnya, antarmuka AIDL dapat direferensikan dalam src/lib.rs sebagai berikut:

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

Terakhir, mulai layanan dalam biner Rust seperti yang ditunjukkan di bawah ini:

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

Contoh Asynchronous Rust AIDL

Bagian ini memberikan contoh gaya Hello World dalam penggunaan AIDL dengan Rust asinkron.

Melanjutkan contoh RemoteService, library backend AIDL yang dihasilkan menyertakan antarmuka asinkron yang dapat digunakan untuk mengimplementasikan server asinkron untuk antarmuka AIDL RemoteService.

Antarmuka server asinkron IRemoteServiceAsyncServer yang dihasilkan dapat diterapkan sebagai berikut:

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

Penerapan server asinkron dapat dimulai sebagai berikut:

#[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();
    });
}

Perhatikan bahwa blokir_di_tempat diperlukan untuk meninggalkan konteks asinkron yang memungkinkan join_thread_pool untuk menggunakan block_on secara internal. Hal ini karena #[tokio::main] menggabungkan kode dalam panggilan ke block_on, dan join_thread_pool mungkin memanggil block_on saat menangani transaksi masuk. Memanggil block_on dari dalam block_on menyebabkan kepanikan. Hal ini juga dapat dihindari dengan membangun runtime tokio secara manual bukan menggunakan #[tokio::main], lalu memanggil join_thread_pool di luar metode block_on.

Selanjutnya, pustaka yang dihasilkan {i> rust backend<i} menyertakan antarmuka yang memungkinkan menerapkan klien asinkron IRemoteServiceAsync untuk RemoteService yang dapat diterapkan sebagai berikut:

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

Panggil Rust dari C

Contoh ini menunjukkan cara memanggil Rust dari C.

Contoh library Rust

Tentukan file libsimple_printer di external/rust/simple_printer/libsimple_printer.rs sebagai berikut:

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

{i>Library Rust<i} harus menentukan {i>header<i} yang dapat ditarik oleh modul C dependen, jadi tentukan header external/rust/simple_printer/simple_printer.h sebagai berikut:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Tentukan external/rust/simple_printer/Android.bp seperti yang Anda lihat di sini:

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: ["."],
}

Contoh biner C

Tentukan external/rust/c_hello_rust/main.c seperti berikut:

#include "simple_printer.h"

int main() {
  print_c_hello_rust();
  return 0;
}

Tentukan external/rust/c_hello_rust/Android.bp seperti berikut:

cc_binary {
    name: "c_hello_rust",
    srcs: ["main.c"],
    shared_libs: ["libsimple_c_printer"],
}

Terakhir, bangun dengan memanggil m c_hello_rust.

Interop Rust-Java

Peti jni menyediakan interoperabilitas Rust dengan Java melalui Java Native Antarmuka (JNI). Contoh ini mendefinisikan definisi jenis yang diperlukan Rust untuk menghasilkan library Rust cdylib yang dihubungkan langsung ke JNI Java (JNIEnv, JClass, JString, dan seterusnya). Tidak seperti binding C++ yang menjalankan codegen melalui cxx, Interoperabilitas Java melalui JNI tidak memerlukan langkah pembuatan kode selama proses build. Oleh karena itu, Anda tidak memerlukan dukungan sistem build khusus. Java memuat cdylib yang disediakan Rust seperti library native lainnya.

Penggunaan

Penggunaan dalam kode Rust dan Java tercakup dalam Dokumentasi kotak jni. Memohon ikuti panduan Memulai contoh yang disediakan di sana. Setelah menulis src/lib.rs, kembali ke halaman ini untuk mempelajari cara membangun library dengan sistem build Android.

Definisi build

Java mengharuskan library Rust disediakan sebagai cdylib agar dapat dimuat secara dinamis. Definisi library Rust di Soong adalah sebagai berikut:

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

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

Library Java mencantumkan library Rust sebagai dependensi required; ini memastikannya diinstal ke perangkat bersamaan pustaka Java meskipun hal ini bukan dependensi waktu build:

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

Atau, jika Anda harus menyertakan library Rust dalam AndroidManifest.xml tambahkan library ke uses_libs sebagai berikut:

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

Interop Rust–C++ menggunakan CXX

Peti CXX menyediakan FFI yang aman antara Rust dan subset dari C++. Dokumentasi CXX memberikan contoh yang baik tentang cara kerjanya secara umum dan kami sarankan untuk membacanya terlebih dahulu untuk membiasakan diri dengan perpustakaan dan cara menjembatani C++ dan Rust. Tujuan contoh berikut menunjukkan cara menggunakannya di Android.

Agar CXX menghasilkan kode C++ yang dipanggil Rust, tentukan genrule untuk panggil CXX dan cc_library_static untuk memaketkannya ke dalam library. Jika Anda berencana agar C++ memanggil kode Rust, atau menggunakan tipe yang dibagikan antara C++ dan Rust, genrule kedua (untuk menghasilkan header C++ yang berisi binding 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"],
}

Alat cxxbridge digunakan di atas untuk menghasilkan sisi C++ dari jembatan. libcxx_test_cpp berikutnya digunakan sebagai dependensi untuk file Rust yang dapat dieksekusi:

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

Di dalam file .cpp dan .hpp, tentukan fungsi C++ sesuai keinginan Anda, menggunakan jenis wrapper CXX sesuai keinginan. Misalnya, definisi cxx_test.hpp berisi hal berikut:

#pragma once

#include "rust/cxx.h"
#include "lib.rs.h"

int greet(rust::Str greetee);

Meskipun cxx_test.cpp berisi

#include "cxx_test.hpp"
#include "lib.rs.h"

#include <iostream>

int greet(rust::Str greetee) {
  std::cout << "Hello, " << greetee << std::endl;
  return get_num();
}

Untuk menggunakannya dari Rust, tentukan bridge CXX seperti di bawah ini di 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;
}