تشكّل الواجهة الخلفية AIDL هدفًا لإنشاء رموز التداخلات. عند استخدام ملفات AIDL، يمكنك أن يتم استخدامها دائمًا بلغة معيّنة مع وقت تشغيل محدَّد. استنادًا إلى السياق، يجب عليك استخدام خلفيات AIDL مختلفة.
في الجدول التالي، يشير ثبات سطح واجهة برمجة التطبيقات إلى إمكانية
لتجميع الرمز على سطح واجهة برمجة التطبيقات هذا بطريقة يمكن
يتم إرساله بشكل مستقل عن البرنامج الثنائي system.img
libbinder.so
.
تحتوي لغة AIDL على الخلفيات التالية:
الخلفية | اللغة | مساحة عرض واجهة برمجة التطبيقات | إنشاء الأنظمة |
---|---|---|---|
Java | Java | SDK/SystemApi (ثابت*) | الكل |
كرونة دنماركية | C++ | libbinder_ndk (ثابت*) | واجهة المساعد |
تكلفة المكالمة الهاتفية | C++ | libbinder (غير ثابت) | الكل |
الصدأ (Rust) | الصدأ (Rust) | libbinder_rs (ثابت*) | واجهة المساعد |
- مساحات عرض واجهة برمجة التطبيقات هذه مستقرة، ولكن العديد من واجهات برمجة التطبيقات، مثل تلك الخاصة بالخدمة إلا أنها مخصّصة لاستخدام النظام الأساسي الداخلي التطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات، يُرجى الاطّلاع على مستندات المطوّرين.
- تم تقديم واجهة Rust الخلفية في الإصدار 12 من نظام التشغيل Android. الـ تتوفّر الواجهة الخلفية NDK اعتبارًا من الإصدار 10 من نظام التشغيل Android.
- تم بناء القفص الصدأ فوق "
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 مع لغة AIDL ثابتة، اطّلِع على واجهة AIDL المنظَّمة مقابل AIDL
الأنواع
يمكنك اعتبار المحول البرمجي 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::ect<T> | std::ect<T> | في: &[T] الخروج: Vec<T> |
بايت[] | std::ect<uint8_t> | std::vector<int8_t>1 | في: &[u8] الخروج: Vec<u8> |
قائمة<T> | std::الانتقال <T>2 | std::الانتقال <T>3 | في: &[T]4 الخروج: Vec<T> |
واصف الملفات | android::base::unique_fd | لا ينطبق | binder::parcel::ParcelFileDescriptor |
واصف ملف ParcelFile | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
نوع الواجهة (T) | android::sp<T> | std::shared_ptr<T>7 | binder::قوي |
نوع قطعة الأرض (T) | T | T | T |
نوع الاتحاد (T)5 | T | T | T |
ث[N] 6 | std::array<T, N> | std::array<T, N> | [صواب؛ ن] |
1. في نظام التشغيل Android 12 أو الإصدارات الأحدث، تستخدم صفائف البايت 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- في نظام التشغيل Android 13 أو الإصدارات الأحدث، تكون الصفائف ذات الحجم الثابت
يمكن أن يكون للصفائف ذات الحجم الثابت أبعاد متعددة (مثل int[3][4]
).
في خلفية Java، يتم تمثيل الصفائف ذات الحجم الثابت كأنواع صفائف.
7- لإنشاء مثيل عن عنصر ربط SharedRefBase
، استخدِم
SharedRefBase::make\<My\>(... args ...)
تنشئ هذه الدالة دالة
عنصر واحد (std::shared_ptr\<T\>
)
تتم إدارته داخليًا أيضًا في حال كان الحافظ مملوكًا لشخص آخر
الدفع. يؤدي إنشاء الكائن بطرق أخرى إلى الملكية المزدوجة.
الاتجاه (للداخل/الخارج/الداخل)
عند تحديد أنواع وسيطات الدوال، يمكنك تحديد
اسم المستخدم على شكل 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
باستخدام الواجهة الخلفية CPP، يمكنك اختيار ما إذا كانت السلاسل هي utf-8 أو utf-16. التصريح
سلاسل مثل @utf8InCpp String
في AIDL لتحويلها تلقائيًا إلى utf-8.
تستخدم الخلفتان NDK وRust دائمًا سلاسل utf-8. لمزيد من المعلومات عن
التعليق التوضيحي utf8InCpp
، راجِع التعليقات التوضيحية في AIDL.
القابلية للإلغاء
يمكنك إضافة تعليقات توضيحية إلى الأنواع التي يمكن أن تكون فارغة في واجهة Java باستخدام @nullable
.
لعرض القيم الفارغة للواجهات الخلفية لكل من CPP وNDK. في خلفية Rust، تظهر
يتم عرض أنواع @nullable
باعتبارها Option<T>
. ترفض الخوادم الأصلية القيم الفارغة
تلقائيًا. الاستثناءات الوحيدة لذلك هي النوعان interface
وIBinder
،
والتي يمكن أن تكون دائمًا فارغة لقراءات NDK وعمليات كتابة CPP/NDK. لمزيد من المعلومات
حول التعليق التوضيحي nullable
، راجِع
التعليقات التوضيحية في AIDL.
قطع طرود مخصّصة
قطعة أرض مخصّصة هي قطعة يتم تنفيذها يدويًا في هدف الخلفية. لا تستخدم عناصر جغرافية مخصصة إلا عندما تحاول إضافة دعم للقطع الخاصة بها والتي لا يمكن تغييرها.
للإفصاح عن عنصر طرد مخصص حتى تعرف AIDL بشأنه، يجب أن تحدد AIDL نموذج البيان القابل للنقل كما يلي:
package my.pack.age;
parcelable Foo;
بشكل تلقائي، يفصح هذا عن مكوّن Java، حيث يكون my.pack.age.Foo
عبارة عن Java
طبقة تنفّذ واجهة Parcelable
.
للحصول على بيان بشأن حزمة خلفية مخصّصة في واجهة CPP في 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 (الإصدار التجريبي من AOSP)، للإعلان عن إصدار Rust مخصَّص
قابل للقطع في 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
قطع طرود مخصّصة لاستخدامها في 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
ولا يتم تحليلها بواسطة
مجلد.
في 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 |
كرونة دنماركية | android/binder_status.h |
الصدأ (Rust) | android/binder_status.h |
استخدام خلفيات مختلفة
تختص هذه التعليمات برمز النظام الأساسي Android. تستخدم هذه الأمثلة
النوع المحدد، my.package.IFoo
. للحصول على تعليمات حول كيفية استخدام الواجهة الخلفية Rust
اطّلِع على مثال Rust AIDL.
على صفحة Android Rust Patterns
.
استيراد الأنواع
سواء كان النوع المحدد واجهة أو قطعة أو اتحاد، يمكنك استيراد في 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
. - في خلفية CPP، استخدِم
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
للحصول على معرّف PID.
خدمة أثناء تصحيح الأخطاء.
لإضافة ناتج مخصّص إلى خدمتك، يمكنك إلغاء dump
.
في كائن الخادم كما لو كنت تنفّذ أي طريقة IPC أخرى
محددة في ملف AIDL. عند إجراء ذلك، يجب ألّا يقتصر الأمر على إلقاء الضوء على التطبيق.
إذن android.permission.DUMP
أو حصر التفريغ بمعرّفات فريدة (UID) معيّنة.
في خلفية 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، يمكنك الحصول على واصف الواجهة مع رمز مثل:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
في خلفية CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
لا تدعم الخلفيات الخلفية لكل من NDK وRust هذه الإمكانية.
الحصول على واصف الواجهة بشكل ثابت
عليك أحيانًا (مثلاً عند تسجيل خدمات "@VintfStability
")
تعرف ما هو واصف الواجهة بشكل ثابت. في Java، يمكنك الحصول على
واصفًا عن طريق إضافة تعليمة برمجية مثل:
import my.package.IFoo;
... IFoo.DESCRIPTOR
في خلفية CPP:
#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، يتم توفير التكرار على النحو التالي.
في خلفية CPP:
::android::enum_range<MyEnum>()
في خلفية NDK:
::ndk::enum_range<MyEnum>()
في خلفية Rust:
MyEnum::enum_values()
إدارة سلاسل المحادثات
في كل مرة يتم فيها إجراء libbinder
في العملية، يتم الاحتفاظ بمجموعة سلاسل محادثات واحدة. بالنسبة إلى معظم
في حالات الاستخدام، يجب أن تكون هذه البيانات تجمع سلسلة محادثات واحدة فقط وأن تتم مشاركتها في جميع الخلفيات.
الاستثناء الوحيد هو عندما يقوم رمز المورد بتحميل نسخة أخرى من libbinder
للتحدث مع /dev/vndbinder
. ونظرًا لأن هذا موجود على عقدة رابط منفصلة، فإن
لم تتم مشاركة سلسلة محادثات.
بالنسبة للواجهة الخلفية لـ Java، يمكن أن يزداد حجم Threadpool فقط (نظرًا لأنه بدأت بالفعل):
BinderInternal.setMaxThreads(<new larger value>);
بالنسبة إلى الواجهة الخلفية لخدمة CPP، تتوفّر العمليات التالية:
// 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، يمكنك إجراء ذلك بأمان إعادة تسمية الحقول مع الاستمرار في التوافق مع البروتوكولات قد تحتاج إلى تحديث التعليمات البرمجية لمواصلة البناء، ولكن سيستمر أي برامج تم إنشاؤها التشغيل التفاعلي.
الأسماء التي يجب تجنّبها: