تحتوي هذه الصفحة على معلومات حول تسجيل البيانات في Android، وتقدّم مثالاً على Rust AIDL، وتوضّح كيفية استدعاء Rust من C ، وتقدّم تعليمات حول التوافق بين Rust وC++ باستخدام CXX.
تسجيل البيانات في Android
يوضّح المثال التالي كيفية تسجيل الرسائل في 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
مرة واحدة (يمكنك استدعاؤها أكثر من مرة إذا لزم الأمر)،
وسجِّل الرسائل باستخدام وحدات الماكرو المتوفّرة. يمكنك الاطّلاع على
logger crate
للحصول على قائمة بخيارات الإعداد المحتملة.
توفّر حزمة تسجيل الأحداث واجهة برمجة تطبيقات لتحديد ما تريد تسجيله. استنادًا إلى ما إذا كان الرمز البرمجي يتم تنفيذه على الجهاز أو على المضيف (مثل جزء من اختبار على المضيف)، يتم تسجيل الرسائل باستخدام android_logger أو env_logger.
مثال على Rust AIDL
يقدّم هذا القسم مثالاً على استخدام AIDL مع Rust بأسلوب Hello World.
باستخدام قسم نظرة عامة حول 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 التي تم إنتاجها كعنصر تابع. كمثال على استخدام المكتبة التي تم إنتاجها كعنصر تابع، يمكن تعريف 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
يقدّم هذا القسم مثالاً على استخدام AIDL مع لغة Rust غير المتزامنة بأسلوب Hello World.
بالاستمرار في مثال RemoteService
، تتضمّن مكتبة الخلفية التي تم إنشاؤها بلغة تعريف واجهة Android (AIDL) واجهات غير متزامنة يمكن استخدامها لتنفيذ خادم غير متزامن لواجهة RemoteService
بلغة AIDL.
يمكن تنفيذ واجهة الخادم غير المتزامن 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: ["."],
}
مثال على رمز ثنائي
عرِّف 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 الأصلية (JNI). يحدّد هذا الملف تعريفات الأنواع اللازمة لكي ينتج Rust مكتبة cdylib
بلغة Rust يمكن ربطها مباشرةً بواجهة 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"]
[...]
}
التوافق بين Rust وC++ باستخدام CXX
توفر حزمة CXX واجهة FFI آمنة بين Rust ومجموعة فرعية من C++. تقدّم مستندات CXX أمثلة جيدة حول طريقة عملها بشكل عام، وننصحك بقراءتها أولاً للتعرّف على المكتبة وطريقة ربطها بين C++ وRust. يوضّح المثال التالي كيفية استخدامها في Android.
لكي ينشئ CXX رمز C++ الذي يستدعيه Rust، عليك تحديد genrule
لاستدعاء CXX وcc_library_static
لتضمين ذلك في مكتبة. إذا كنت تخطّط لأن يستدعي C++ رمز Rust البرمجي، أو استخدام أنواع مشتركة بين C++ وRust، حدِّد genrule ثانيًا (لإنشاء عنوان 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;
}