דפוסי חלודה של אנדרואיד

דף זה מכיל מידע על רישום אנדרואיד , מספק דוגמה של 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, 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!");
}

כלומר, הוסף את שתי התלות המוצגות לעיל ( liblogger ו- liblog_rust ), קרא לשיטת init פעם אחת (ניתן לקרוא לה יותר מפעם אחת במידת הצורך), ויומן הודעות באמצעות פקודות המאקרו המסופקות. עיין בארגז לוגר עבור רשימה של אפשרויות תצורה אפשריות.

ארגז לוגר מספק API להגדרת מה אתה רוצה לרשום. תלוי אם הקוד פועל במכשיר או במארח (כגון חלק מבדיקה בצד המארח), הודעות נרשמות באמצעות android_logger או env_logger .

דוגמה לחלודה AIDL

סעיף זה מספק דוגמה בסגנון Hello World לשימוש ב-AIDL עם Rust.

השתמש ב-Android Developer Guide AIDL סקירה כללית כנקודת התחלה, צור 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,
        },
    },
}

הקצה האחורי של AIDL הוא מחולל מקור חלודה, כך שהוא פועל כמו מחוללים אחרים של מקור חלודה ומייצר ספריית חלודה. מודול ספריית 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 עם חלודה אסינכרית.

בהמשך לדוגמא של 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 .

יתרה מכך, הספרייה שנוצרה בחלק האחורי של חלודה כוללת ממשק המאפשר הטמעת לקוח אסינכרון IRemoteServiceAsync for 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::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),
    }
}

מתקשר ל-Rust מ-C

דוגמה זו מראה כיצד לקרוא ל-Rust מ-C.

ספריית חלודה לדוגמה

הגדר את הקובץ 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 include_dirs so cc_binary knows where the headers are.
    include_dirs: ["."],
}

דוגמה ג' בינארית

הגדר 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 .

אינטררופ חלודה-ג'אווה

ארגז jni מספק יכולת פעולה הדדית של Rust עם Java באמצעות ממשק המקור של Java (JNI). הוא מגדיר את הגדרות הסוג ההכרחיות עבור Rust כדי לייצר ספריית Rust cdylib שמתחברת ישירות ל-JNI של Java ( JNIEnv , JClass , JString וכן הלאה). בניגוד לקשרי C++ שמבצעים קודגן דרך cxx , יכולת פעולה הדדית של Java דרך ה-JNI אינה דורשת שלב של יצירת קוד במהלך בנייה. לכן זה לא צריך תמיכה מיוחדת של מערכת בנייה. קוד ה-Java טוען את cdylib המסופק ב-Rust כמו כל ספרייה מקומית אחרת.

נוֹהָג

השימוש בקוד Rust וגם ב-Java מכוסה בתיעוד הארגז jni . אנא עקוב אחר הדוגמה לתחילת העבודה המופיעה שם. לאחר כתיבת src/lib.rs , חזור לדף זה כדי ללמוד כיצד לבנות את הספרייה עם מערכת הבנייה של אנדרואיד.

הגדרת בנייה

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 כתלות required ; זה מבטיח שהוא מותקן במכשיר לצד ספריית Java למרות שזה לא תלוי בזמן בנייה:

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 כדי לאגד אותו לספרייה. אם אתה מתכנן להתקשר לקוד 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;
}