أنماط الصدأ لنظام Android

تحتوي هذه الصفحة على معلومات حول تسجيل Android ، وتوفر مثالاً لـ 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 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. يمكن استخدام وحدة مكتبة 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()
}

مثال غير متزامن الصدأ 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 .

علاوة على ذلك، تتضمن المكتبة التي تم إنشاؤها في الواجهة الخلفية الصدأ واجهة تسمح بتنفيذ عميل غير متزامن 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),
    }
}

استدعاء الصدأ من 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: ["."],
}

مثال 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 .

الصدأ-جافا التشغيل المتداخل

يوفر صندوق jni إمكانية التشغيل التفاعلي لـ Rust مع Java من خلال Java Native Interface (JNI). فهو يحدد تعريفات النوع الضرورية لـ Rust لإنتاج مكتبة Rust cdylib التي يتم توصيلها مباشرة بـ JNI الخاص بـ Java ( JNIEnv و JClass و JString وما إلى ذلك). على عكس روابط C++ التي تنفذ إنشاء التعليمات البرمجية من خلال cxx ، فإن قابلية التشغيل التفاعلي لـ Java من خلال JNI لا تتطلب خطوة إنشاء التعليمات البرمجية أثناء الإنشاء. ولذلك فهو لا يحتاج إلى دعم خاص لنظام البناء. يقوم كود Java بتحميل cdylib المقدم من Rust مثل أي مكتبة أصلية أخرى.

الاستخدام

تمت تغطية الاستخدام في كل من كود Rust وJava في وثائق صندوق jni . يرجى اتباع مثال البدء الموجود هناك. بعد أن تكتب 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 باعتبارها تبعية required ؛ وهذا يضمن تثبيته على الجهاز بجانب مكتبة Java على الرغم من أنه ليس تابعًا لوقت البناء:

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

وبدلاً من ذلك، إذا كان يجب عليك تضمين مكتبة Rust في ملف AndroidManifest.xml ، فقم بإضافة المكتبة إلى uses_libs كما يلي:

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

الصدأ-C++ Interop باستخدام CXX

يوفر صندوق CXX FFI آمنًا بين Rust ومجموعة فرعية من C++. تقدم وثائق CXX أمثلة جيدة لكيفية عملها بشكل عام ونقترح قراءتها أولاً للتعرف على المكتبة والطريقة التي تربط بها C++ وRust. يوضح المثال التالي كيفية استخدامه في Android.

لجعل CXX ينشئ رمز C++ الذي يستدعيه Rust، حدد genrule لاستدعاء CXX و cc_library_static لتجميع ذلك في مكتبة. إذا كنت تخطط لاستدعاء C++ لرمز Rust، أو استخدام أنواع مشتركة بين 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;
}