واجهة AIDL الخلفية هي هدف لإنشاء رمز رمزي. عند استخدام ملفات AIDL، يجب استخدامها دائمًا بلغة معيّنة مع وقت تشغيل محدّد. استنادًا إلى السياق، يجب استخدام الخلفيات المختلفة لـ AIDL.
في الجدول التالي، يشير ثبات واجهة برمجة التطبيقات إلى إمكانية compiling code against this API surface (تجميع الرمز البرمجي وفقًا لواجهة برمجة التطبيقات هذه) بطريقة يمكن بها إرسال الرمز البرمجي بشكل مستقل عن ملف system.img
libbinder.so
الثنائي.
تتضمّن واجهة برمجة التطبيقات AIDL الخلفيات التالية:
الخلفية | اللغة | واجهة برمجة التطبيقات | إنشاء الأنظمة |
---|---|---|---|
Java | Java | حزمة تطوير البرامج (SDK)/SystemApi (الإصدار الثابت*) | الكل |
كرونة دنماركية | C++ | libbinder_ndk (الإصدار الثابت*) | aidl_interface |
تكلفة المكالمة الهاتفية | C++ | libbinder (غير ثابت) | الكل |
الصدأ (Rust) | الصدأ (Rust) | libbinder_rs (ثابت*) | aidl_interface |
- مساحات عرض واجهة برمجة التطبيقات هذه مستقرة، ولكن العديد من واجهات برمجة التطبيقات، مثل تلك الخاصة بالخدمة إلا أنها مخصّصة لاستخدام النظام الأساسي الداخلي التطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات، يُرجى الاطّلاع على مستندات المطوّرين.
- تم تقديم الخلفية Rust في الإصدار 12 من Android، وأصبحت JDK الخلفية متاحة اعتبارًا من الإصدار 10 من Android.
- تم إنشاء حزمة Rust على
libbinder_ndk
، ما يتيح لها أن تكون مستقرة وقابلة للنقل. تستخدم وحدات APEX حزمة الحِزم بالطريقة نفسها التي يستخدمها أي شخص آخر على جانب النظام. يتم تجميع جزء Rust في حزمة APEX وشحنه داخلها. يعتمد ذلك علىlibbinder_ndk.so
في قسم النظام.
أنظمة التصميم
اعتمادًا على الخلفية، هناك طريقتان لتجميع AIDL في شكل دليل الرمز. لمزيد من التفاصيل حول أنظمة التصميم، يمكنك مراجعة مرجع وحدة Soong.
نظام التصميم الأساسي
في أي وحدة Android.bp من النوع cc_
أو java_
(أو في نظائرها من النوع Android.mk
)، يمكن تحديد ملفات
.aidl
كملفات مصدر. في هذه الحالة، لن تكون لغة Java/CPP
استخدام الواجهة الخلفية لـ AIDL (وليس الواجهة الخلفية لـ NDK)، وسيتم استخدام الفئات
تتم إضافة ملفات AIDL المقابلة إلى الوحدة تلقائيًا. الخيارات
مثل local_include_dirs
التي تُعلم نظام الإنشاء بالمسار الجذر إلىملفّات AIDL في هذه الوحدة يمكن تحديدها في هذه الوحدات ضمن مجموعة aidl:
. لاحظ أن خلفية Rust الخلفية للاستخدام مع Rust فقط. rust_
وحدة هي
التعامل معها بشكل مختلف في حال عدم تحديد ملفات AIDL كملفات مصدر.
بدلاً من ذلك، تنتج الوحدة aidl_interface
العنصر rustlib
الذي يسمى
<aidl_interface name>-rust
التي يمكن الربط بها لمزيد من التفاصيل، يُرجى الاطّلاع على
مثال Rust AIDL.
aidl_interface
يجب أن تكون الأنواع المستخدمة مع نظام التصميم هذا منظَّمة. لكي تكون مثبّتة، يجب أن تحتوي العناصر القابلة للتقسيم على حقول مباشرةً وألا تكون عبارة عن تعريفات لأنواع يتم تحديدها مباشرةً باللغات المستهدَفة. لمعرفة كيفية توافق واجهة برمجة التطبيقات المنظَّمة مع واجهة برمجة التطبيقات الثابتة، يُرجى الاطّلاع على واجهة برمجة التطبيقات المنظَّمة مقارنةً بواجهة برمجة التطبيقات الثابتة.
الأنواع
يمكنك اعتبار المحول البرمجي aidl
بمثابة تنفيذ مرجعي للأنواع.
عند إنشاء واجهة، يمكنك استدعاء aidl --lang=<backend> ...
للاطّلاع على
ملف الواجهة الناتج. عند استخدام وحدة aidl_interface
، يمكنك عرض
المخرجات في out/soong/.intermediates/<path to module>/
.
نوع Java/AIDL | النوع C++ | نوع NDK | نوع الصدأ |
---|---|---|---|
منطقية | قيمة منطقية (Bool) | قيمة منطقية (Bool) | قيمة منطقية (Bool) |
بايت | int8_t | int8_t | i8 |
حرف | char16_t | char16_t | u16 |
تدخُّل دفاعي | int32_t | int32_t | i32 |
طويلة | int64_t | int64_t | i64 |
عائم | عائم | عائم | f32 |
مزدوج | مزدوج | مزدوج | f64 |
سلسلة | android::String16 | std::string | سلسلة |
android.os.Parcelable | android::Parcelable | لا ينطبق | لا ينطبق |
بيندر | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | الإدخال: &[T] الإخراج: Vec<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t>1 | في: &[u8] الخروج: Vec<u8> |
List<T> | std::الانتقال <T>2 | std::الانتقال <T>3 | في: &[T]4 الخروج: Vec<T> |
واصف الملفات | android::base::unique_fd | لا ينطبق | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
نوع الواجهة (T) | android::sp<T> | std::shared_ptr<T>7 | binder::Strong |
نوع parcelable (T) | T | T | T |
نوع الاتحاد (T)5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. في الإصدار 12 من نظام التشغيل Android أو الإصدارات الأحدث، تستخدم صفائف البايت uint8_t بدلاً من int8_t لأسباب التوافق.
2. تتوافق خلفية C++ مع List<T>
حيث يكون T
واحدًا من String
،
IBinder
أو ParcelFileDescriptor
أو قطعة جغرافية في Android
13 أو أعلى، يمكن أن يكون T
أي نوع غير أساسي.
(بما في ذلك أنواع الواجهات) باستثناء الصفائف. تقترح شركة AOSP
تستخدم أنواع الصفائف مثل T[]
، لأنها تعمل في جميع الخلفيات.
3. تتوافق خلفية NDK مع List<T>
حيث يكون T
واحدًا من String
،
ParcelFileDescriptor
أو قطعة أرض. في نظام التشغيل Android 13
أو أعلى، يمكن أن يكون T
أي نوع غير أساسي باستثناء المصفوفات.
4. يتم تمرير الأنواع بشكل مختلف في رمز Rust بناءً على ما إذا كانت إدخالًا (مَعلمة) أو ناتجًا (قيمة معروضة).
5- تتوفّر أنواع الاتحاد في نظام التشغيل Android 12 أعلى.
6- في الإصدار 13 من نظام التشغيل Android أو الإصدارات الأحدث، يتم
دعم الصفائف ذات الحجم الثابت. يمكن أن تحتوي الصفائف ذات الحجم الثابت على أبعاد متعدّدة (مثل int[3][4]
).
في الخلفية في Java، يتم تمثيل الصفائف ذات الحجم الثابت كأنواع صفائف.
7. لإنشاء نسخة مثيل من عنصر ربط SharedRefBase
، استخدِم
SharedRefBase::make\<My\>(... args ...)
تنشئ هذه الدالة std::shared_ptr\<T\>
std::shared_ptr\<T\>
والتي تتم إدارتها أيضًا داخليًا، في حال كانت عملية الربط مملوكة لعملية
أخرى. يؤدي إنشاء الكائن بطرق أخرى إلى الملكية المزدوجة.
الاتجاهية (in/out/inout)
عند تحديد أنواع الوسيطات للدوالّ، يمكنك تحديد
ها على أنّها in
أو out
أو inout
. يتحكم هذا في تحديد معلومات الاتجاه
تم تمريره لاستدعاء IPC. in
هو الاتجاه التلقائي، ويشير إلى أن البيانات
تمريره من المتصل إلى المستدعي. يشير الرمز out
إلى أنّه يتم تمرير البيانات من المكوّن
المُستدعى إلى المكوّن المُتصل. وinout
عبارة عن مزيج منهما. ومع ذلك، ينصح
فريق Android بتجنُّب استخدام محدِّد الوسيطة inout
.
في حال استخدام inout
مع واجهة ذات إصدارات ومستلم مكالمة قديم، تتم إعادة ضبط
الحقول الإضافية المتوفّرة في جهة الاتصال فقط على قيمها
التلقائية. بالنسبة إلى Rust، يتلقّى نوع inout
عادي &mut Vec<T>
،
تتلقى قائمة النوع inout
&mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF8/UTF16
باستخدام الخلفية في لغة C++، يمكنك اختيار ما إذا كانت السلاسل بترميز utf-8 أو utf-16. يمكنك تحديد
السلاسل على أنّها @utf8InCpp String
في AIDL لتحويلها تلقائيًا إلى utf-8.
تستخدم الخلفيات في NDK وRust دائمًا سلاسل utf-8. لمزيد من المعلومات عن
التعليق التوضيحي utf8InCpp
، راجِع التعليقات التوضيحية في AIDL.
القابلية للإلغاء
يمكنك إضافة تعليقات توضيحية إلى أنواع التعليقات التوضيحية التي يمكن أن تكون فارغة باستخدام السمة @nullable
.
لمزيد من المعلومات عن التعليق التوضيحي nullable
، يُرجى الاطّلاع على
التعليقات التوضيحية في AIDL.
قطع طرود مخصّصة
قطعة أرض مخصّصة هي قطعة يتم تنفيذها يدويًا في هدف الخلفية. لا تستخدم عناصر جغرافية مخصصة إلا عندما تحاول إضافة دعم للقطع الخاصة بها والتي لا يمكن تغييرها.
لتعريف عنصر قابل للنقل مخصّص كي يتعرّف عليه AIDL، يبدو تعريف AIDL للعنصر القابل للنقل على النحو التالي:
package my.pack.age;
parcelable Foo;
يُعلن هذا الإجراء تلقائيًا عن عنصر قابل للنقل في Java حيث يكون my.pack.age.Foo
فئة Java
تنفِّذ واجهة Parcelable
.
لتعريف واجهة برمجة تطبيقات مخصّصة لنظام التشغيل CPP في واجهة برمجة التطبيقات لنظام التشغيل Android (AIDL)، استخدِم cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
يبدو تنفيذ C++ في my/pack/age/Foo.h
على النحو التالي:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
لإرسال بيان بطرد NDK مخصص في AIDL، يُرجى استخدام ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
يبدو تنفيذ NDK في android/pack/age/Foo.h
على النحو التالي:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
في Android 15، لتعريف ملف Rust
parcelable مخصّص في AIDL، استخدِم rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
يظهر تنفيذ Rust في rust_crate/src/lib.rs
على النحو التالي:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
يمكنك بعد ذلك استخدام هذا العنصر كنوع في ملفات AIDL، ولكن لن يتم
إنشاؤه بواسطة AIDL. قدِّم عاملَي التشغيل <
و==
لخلفية CPP/NDK
وParcelable المخصّصة لاستخدامها في union
.
القيم التلقائية
يمكن لأجزاء البيانات المنظمة الإعلان عن القيم التلقائية لكل حقل للأوليات،
String
والصفائف من هذه الأنواع.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
في الواجهة الخلفية لـ Java عندما تكون القيم الافتراضية مفقودة، يتم تهيئة الحقول
صفر للأنواع الأولية وnull
للأنواع غير الأولية.
في الخلفيات الأخرى، يتمّ إعداد الحقول باستخدام قيم تلقائية تمّ إعدادها عندما
لا يتمّ تحديد القيم التلقائية. على سبيل المثال، في خلفية C++ ، حقول String
كسلسلة فارغة ويتم إعداد حقول List<T>
كسلسلة
حقل vector<T>
فارغ. يتمّ إعداد حقول @nullable
كحقول ذات قيمة فارغة.
خطأ أثناء المعالجة
يقدّم نظام التشغيل Android أنواع أخطاء مضمّنة للخدمات لاستخدامها عند الإبلاغ عن الأخطاء. يستخدمها الرابط ويمكن أن تستخدمها أي خدمات تنفِّذ واجهة رابط. تم توثيق استخدامها جيدًا في تعريف AIDL ولا تتطلب أي حالة يحددها المستخدم أو نوع الإرجاع.
مَعلمات الإخراج التي بها أخطاء
عندما تبلغ دالة AIDL عن خطأ، قد لا تتم تهيئة الدالة أو
لتعديل معلمات الإخراج. وعلى وجه التحديد، قد يتم تعديل مَعلمات الإخراج إذا حدث الخطأ أثناء إزالة الحزمة بدلاً من حدوثه أثناء معالجة المعاملة نفسها. بشكل عام، عند ظهور خطأ من واجهة AIDL
كل المعاملين inout
وout
بالإضافة إلى القيمة المعروضة (والتي
تعمل كمعلمة out
في بعض الخلفيات) ضمن
حالة غير محددة.
قيم الخطأ المراد استخدامها
يمكن استخدام العديد من قيم الخطأ المضمنة في أي من واجهات AIDL، ولكن قد يتم
تتم معاملتهم بطريقة خاصة. على سبيل المثال، يمكن استخدام EX_UNSUPPORTED_OPERATION
و
EX_ILLEGAL_ARGUMENT
عندما يصفَان حالة الخطأ، ولكن يجب عدم استخدام
EX_TRANSACTION_FAILED
لأنّ البنية الأساسية
تتعامل معه بشكل خاص. تحقق من التعريفات المحددة للخلفية لمزيد من
المعلومات حول هذه القيم المضمنة.
إذا كانت واجهة AIDL تتطلّب قيم خطأ إضافية لا تغطّيها
أنواع الأخطاء المضمّنة، يمكنها استخدام الخطأ المضمّن الخاص بالخدمة والذي يسمح بتضمين قيمة خطأ خاصة بالخدمة يحدّدها
المستخدم. يتم عادةً تعريف هذه الأخطاء المتعلّقة بالخدمة في واجهة
AIDL على أنّها enum
مستندة إلى const int
أو int
ولا يتم تحليلها بواسطة
binder.
في Java، يتم ربط الأخطاء بالاستثناءات، مثل android.os.RemoteException
. بالنسبة إلى
الاستثناءات المتعلّقة بالخدمة، تستخدم Java android.os.ServiceSpecificException
بالإضافة إلى الخطأ الذي يحدّده المستخدم.
لا يستخدم الرمز الأصلي في Android الاستثناءات. تستخدم الواجهة الخلفية لـ CPP
android::binder::Status
تستخدم الواجهة الخلفية لـ NDK ndk::ScopedAStatus
. كلّ
التي تم إنشاؤها بواسطة AIDL أحد هذه الخيارات، والتي تمثل حالة
. تستخدم الواجهة الخلفية Rust نفس قيم رمز الاستثناء مثل NDK، ولكن
يحوّلها إلى أخطاء Rust أصلية (StatusCode
، ExceptionCode
) قبل
وتقديمها إلى المستخدم. بالنسبة إلى الأخطاء المتعلّقة بالخدمة، يستخدم الرمزان العميقان
Status
أو ScopedAStatus
الرمزEX_SERVICE_SPECIFIC
مع
الخطأ الذي يحدّده المستخدم.
يمكن العثور على أنواع الأخطاء المضمّنة في الملفات التالية:
الخلفية | التعريف |
---|---|
Java | android/os/Parcel.java |
تكلفة المكالمة الهاتفية | binder/Status.h |
NDK | android/binder_status.h |
الصدأ (Rust) | android/binder_status.h |
استخدام خلفيات مختلفة
تختص هذه التعليمات برمز النظام الأساسي Android. تستخدم هذه الأمثلة
النوع المحدد، my.package.IFoo
. للحصول على تعليمات عن كيفية استخدام الخلفية في Rust،
اطّلِع على مثال Rust AIDL
في صفحة أنماط Rust في Android
.
أنواع الاستيراد
سواء كان النوع المحدّد واجهة أو قابلاً للتقسيم أو اتحادًا، يمكنك استيراده في Java:
import my.package.IFoo;
أو في الواجهة الخلفية لخدمة CPP:
#include <my/package/IFoo.h>
أو في الخلفية في NDK (لاحظ مساحة الاسم aidl
الإضافية):
#include <aidl/my/package/IFoo.h>
أو في الخلفية في Rust:
use my_package::aidl::my::package::IFoo;
على الرغم من أنه يمكنك استيراد نوع متداخل في جافا، إلا أنه في خلفيات CPP/NDK يجب أن
تضمين العنوان لنوع الجذر الخاص به. على سبيل المثال، عند استيراد نوع متداخل
Bar
محدد في my/package/IFoo.aidl
(IFoo
هو نوع الجذر
يجب تضمين <my/package/IFoo.h>
للواجهة الخلفية لخدمة CPP (أو
<aidl/my/package/IFoo.h>
للواجهة الخلفية NDK).
تنفيذ الخدمات
ولتنفيذ خدمة، يجب أن يتم اكتسابها من فئة الكعب الأصلي. هذا الصف يقرأ الأوامر من برنامج تشغيل المجلَّد وينفّذ الطرق التي وتنفيذها. تخيل أن لديك ملف AIDL مثل هذا:
package my.package;
interface IFoo {
int doFoo();
}
في Java، يجب إضافة فئة فرعية من هذه الفئة:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
في خلفية CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
في الخلفية في NDK (لاحظ مساحة الاسم aidl
الإضافية):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
في الخلفية في Rust:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
impl IFoo for MyFoo {
fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
أو باستخدام Rust غير متزامن:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
التسجيل والحصول على الخدمات
يتم عادةً تسجيل الخدمات في نظام التشغيل Android من خلال عملية servicemanager
. بالإضافة إلى واجهات برمجة التطبيقات أدناه، تتحقق بعض واجهات برمجة التطبيقات من
خدمة (أي يعودون فورًا في حال عدم توفّر الخدمة).
راجِع واجهة servicemanager
المقابلة لمعرفة التفاصيل الدقيقة. هذه
إلا عند التجميع على نظام Android الأساسي.
في Java:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// return if service is started now
myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
// waiting until service comes up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));
في خلفية CPP:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// return if service is started now
status_t err = checkService<IFoo>(String16("service-name"), &myService);
// waiting until service comes up (new in Android 11)
myService = waitForService<IFoo>(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = waitForDeclaredService<IFoo>(String16("service-name"));
في الخلفية في NDK (لاحظ مساحة الاسم aidl
الإضافية):
#include <android/binder_manager.h>
// registering
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
// is a service declared in the VINTF manifest
// VINTF services have the type in the interface instance name.
bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
// wait until a service is available (if isDeclared or you know it's available)
myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));
في خلفية Rust:
use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_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()
}
في الواجهة الخلفية Rust غير المتزامنة التي تتضمن بيئة تشغيل ذات سلسلة محادثات واحدة:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks will run on this thread.
std::future::pending().await
}
يتمثل أحد الاختلافات المهمة عن الخيارات الأخرى في أنّنا لا نطلق
join_thread_pool
عند استخدام Rust غير المتزامن ووقت التشغيل المتعدّد المواضيع. ويعود سبب ذلك
إلى أنّك تحتاج إلى منح Tokio سلسلة محادثات يمكنه من خلالها تنفيذ المهام التي تم إنشاؤها. ضِمن
في هذا المثال، ستخدم سلسلة التعليمات الرئيسية هذا الغرض. أي مهام تنشأ باستخدام
سيتم تنفيذ tokio::spawn
في سلسلة التعليمات الرئيسية.
في الواجهة الخلفية Rust غير المتزامنة التي تتضمن بيئة تشغيل متعددة السلاسل:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
مع وقت تشغيل Tokio متعدد السلاسل، لا يتم تنفيذ المهام الناشئة على
. لذلك، من المنطقي أكثر استدعاء join_thread_pool
على
بحيث لا تكون سلسلة التعليمات الرئيسية في وضع الخمول فقط. يجب إنهاء المكالمة في
block_in_place
لمغادرة السياق غير المتزامن.
رابط إلى الموت
يمكنك طلب تلقّي إشعار عند توقّف خدمة تستضيف أغلفة. ويمكن أن يساعد ذلك في تجنُّب تسرُّب الطلبات الوكيلة لطلبات إعادة الاتصال أو المساعدة في استرداد الأخطاء. يمكنك إجراء هذه المكالمات على عناصر الربط الوكيلة.
- في Java، استخدِم
android.os.IBinder::linkToDeath
. - في الخلفية في "الإعلانات الصورية على شبكة البحث"، استخدِم
android::IBinder::linkToDeath
. - في خلفية NDK، استخدِم
AIBinder_linkToDeath
. - في الخلفية في Rust، أنشئ عنصرًا من النوع
DeathRecipient
، ثم استخدِمmy_binder.link_to_death(&mut my_death_recipient)
. يُرجى العلم أنّه بما أنّDeathRecipient
يملك طلب إعادة الاتصال، عليك إبقاء هذا العنصر نشطًا طالما أردت تلقّي الإشعارات.
معلومات المتصل
عند تلقي اتصال بـ kernel binder، تتوفر معلومات المتصل في والعديد من واجهات برمجة التطبيقات. يشير معرّف العملية (PID) إلى معرّف عملية Linux للعملية التي تُرسِل معاملة. يشير UID (أو رقم تعريف المستخدم) إلى رقم تعريف مستخدم Linux. عند تلقّي مكالمة باتجاه واحد، تكون قيمة معرّف PID للاتصال 0. فعندما خارج سياق معاملة المثبت، تعرض هذه الدوال المعرّف الفريد المعرف (PID) والمعرف الفريد من العملية الحالية.
في الخلفية في Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
في خلفية CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
في خلفية NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
في الخلفية في Rust، عند تنفيذ الواجهة، حدِّد ما يلي (بدلاً من السماح بالإعداد التلقائي):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
تقارير الأخطاء وواجهة برمجة التطبيقات لتصحيح الأخطاء في الخدمات
عند تشغيل تقارير الأخطاء (على سبيل المثال، باستخدام adb bugreport
)، يتم جمع
المعلومات من جميع أنحاء النظام للمساعدة في تصحيح أخطاء المشاكل المختلفة.
بالنسبة إلى خدمات AIDL، تستخدم تقارير الأخطاء رمز dumpsys
الثنائي في جميع الخدمات
لدى مدير الخدمة لتفريغ معلوماته في
تقرير خطأ. يمكنك أيضًا استخدام dumpsys
في سطر الأوامر للحصول على معلومات
من خدمة تتضمّن dumpsys SERVICE [ARGS]
. في خلفيات C++ وJava، يمكنك
التحكم في ترتيب التخلص من الخدمات عن طريق استخدام وسيطات إضافية
إلى addService
. يمكنك أيضًا استخدام dumpsys --pid SERVICE
للحصول على رقم تعريف عملية تدبير
الخدمة أثناء تصحيح الأخطاء.
لإضافة إخراج مخصّص إلى خدمتك، يمكنك إلغاء طريقة dump
في عنصر الخادم كما لو كنت تنفّذ أي طريقة أخرى لبروتوكول IPC
محددة في ملف AIDL. عند إجراء ذلك، يجب حصر عملية تفريغ البيانات في إذن
android.permission.DUMP
الخاص بالتطبيق أو حصرها في أرقام تعريف مستخدمين معيّنة.
في الخلفية في Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
في خلفية CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
في خلفية NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
في الخلفية في Rust، عند تنفيذ الواجهة، حدِّد ما يلي (بدلاً من السماح بالإعداد التلقائي):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
استخدام المؤشرات الضعيفة
يمكنك الاحتفاظ بمرجع ضعيف إلى كائن مادة ربط.
على الرغم من أنّ Java تتيح استخدام WeakReference
، إلا أنّها لا تتيح استخدام مراجع الربط الضعيف
في الطبقة الأصلية.
في الخلفية في "المعالجة المحدودة للصور"، يكون النوع الضعيف هو wp<IFoo>
.
في خلفية NDK، استخدم ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
في الخلفية في Rust، يمكنك استخدام WpIBinder
أو Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
الحصول على واصف الواجهة ديناميكيًا
يحدّد واصف الواجهة نوع الواجهة. هذا مفيد عند تصحيح الأخطاء أو عند وجود ملف ربط غير معروف.
في Java، يمكنك الحصول على واصف الواجهة مع رمز مثل:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
لا تدعم الخلفيات الخلفية لكل من NDK وRust هذه الإمكانية.
الحصول على واصف الواجهة بشكل ثابت
في بعض الأحيان (مثلاً عند تسجيل خدمات @VintfStability
)، عليك معرفة
القيمة الثابتة لموصّف الواجهة. في Java، يمكنك الحصول على
واصفًا عن طريق إضافة تعليمة برمجية مثل:
import my.package.IFoo;
... IFoo.DESCRIPTOR
في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
في الخلفية في NDK (لاحظ مساحة الاسم aidl
الإضافية):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
في خلفية Rust:
aidl::my::package::BnFoo::get_descriptor()
نطاق التعداد
في الخلفيات الأصلية، يمكنك تكرار القيم المحتملة التي يمكن أن يتخذها التعداد مفعَّلة. لا تتوفّر هذه الميزة في Java بسبب اعتبارات تتعلّق بحجم الرمز البرمجي.
بالنسبة إلى مصنّف MyEnum
محدّد في AIDL، يتم توفير التكرار على النحو التالي.
في الخلفية في صفحة المنتج في خدمة مقارنة الأسعار:
::android::enum_range<MyEnum>()
في الخلفية في NDK:
::ndk::enum_range<MyEnum>()
في خلفية Rust:
MyEnum::enum_values()
إدارة سلاسل المحادثات
تحافظ كلّ نسخة من libbinder
في عملية على مجموعة واحدة من سلاسل المهام. في معظم حالات الاستخدام، يجب أن يكون هذا مجمع مؤشرات تسلسل واحدًا بالضبط، ويتمّت مشاركته في جميع الخلفيات.
الاستثناء الوحيد هو عندما قد يحمِّل رمز المورّد نسخة أخرى من libbinder
للتحدّث إلى /dev/vndbinder
. وبما أنّ هذا الإجراء يتم على عقدة رابط منفصلة، لا تتم مشاركة ملف مشاركة طلبات المعالجة.
بالنسبة إلى الخلفية في Java، يمكن فقط زيادة حجم تجمع المواضيع (لأنه قد بدأ من قبل):
BinderInternal.setMaxThreads(<new larger value>);
بالنسبة إلى الخلفية في "شركاء المحتوى في خرائط Google"، تتوفّر العمليات التالية:
// set max threadpool count (default is 15)
status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
// create threadpool
ProcessState::self()->startThreadPool();
// add current thread to threadpool (adds thread to max thread count)
IPCThreadState::self()->joinThreadPool();
وبالمثل، في الخلفية في NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
في الخلفية في Rust:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
مع الواجهة الخلفية Rust غير المتزامنة، تحتاج إلى حزمتي خيط: binder وTokio.
وهذا يعني أن التطبيقات التي تستخدم Rust غير المتزامن تحتاج إلى اعتبارات خاصة،
خاصةً عندما يتعلق الأمر باستخدام join_thread_pool
. راجع القسم الذي يتناول
تسجيل الخدمات
للحصول على مزيد من المعلومات حول هذا الموضوع.
الأسماء المحجوزة
تحتفظ لغات C++ وJava وRust ببعض الأسماء ككلمات رئيسية أو لاستخدام خاص باللغة. بينما لا تفرض AIDL القيود استنادًا إلى قواعد اللغة، فإن استخدام
أو أسماء الحقول التي تتطابق مع اسم محجوز، قد ينتج عنها تجميع
فشل في C++ أو Java. بالنسبة إلى Rust، تتم إعادة تسمية الحقل أو النوع باستخدام
"المعرّف الأولي" يمكن الوصول إليها باستخدام البادئة r#
.
ننصحك بتجنُّب استخدام الأسماء المحجوزة في تعريفات AIDL حيثما أمكن لتجنُّب عمليات الربط غير المريحة أو تعذُّر الترجمة تمامًا.
إذا كان لديك أسماء محجوزة في تعريفات AIDL، يمكنك إجراء ذلك بأمان إعادة تسمية الحقول مع الاستمرار في التوافق مع البروتوكولات قد تحتاج إلى تحديث التعليمات البرمجية لمواصلة البناء، ولكن سيستمر أي برامج تم إنشاؤها التشغيل التفاعلي.
الأسماء التي يجب تجنُّبها: