تمثّل الواجهة الخلفية AIDL هدفًا لإنشاء رمز تجريبي. استخدِم دائمًا ملفات AIDL بلغة معيّنة مع وقت تشغيل محدّد. اعتمادًا على السياق، يجب استخدام برامج خلفية مختلفة للغة AIDL.
في الجدول التالي، يشير ثبات مساحة واجهة برمجة التطبيقات إلى إمكانية تجميع الرمز البرمجي مقابل مساحة واجهة برمجة التطبيقات هذه بطريقة يمكن من خلالها تسليم الرمز البرمجي بشكل مستقل عن ملف system.img libbinder.so الثنائي.
تتضمّن AIDL الخلفيات التالية:
| الخلفية | اللغة | مساحة عرض واجهة برمجة التطبيقات | أنظمة الإنشاء |
|---|---|---|---|
| Java | Java | حزمة تطوير البرامج أو SystemApi (إصدار ثابت*) |
الكل |
| NDK | C++ | libbinder_ndk (stable*) |
aidl_interface |
| تكلفة المكالمة الهاتفية | C++ | libbinder (غير مستقر) |
الكل |
| Rust | Rust | libbinder_rs (stable*) |
aidl_interface |
- تتسم مساحات واجهات برمجة التطبيقات هذه بالثبات، ولكن العديد من واجهات برمجة التطبيقات، مثل تلك الخاصة بإدارة الخدمات، مخصّصة للاستخدام الداخلي للمنصة ولا تتوفّر للتطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات، يُرجى الاطّلاع على لغة تعريف واجهة نظام Android (AIDL).
- تم طرح الخلفية البرمجية المستندة إلى Rust في الإصدار 12 من نظام التشغيل Android، بينما تتوفّر الخلفية البرمجية المستندة إلى NDK منذ الإصدار 10 من نظام التشغيل Android.
- تم إنشاء حزمة Rust استنادًا إلى
libbinder_ndk، ما يتيح لها أن تكون ثابتة وقابلة للنقل. تستخدم حِزم APEX حزمة binder بالطريقة العادية على مستوى النظام. يتم تجميع جزء Rust في حزمة APEX وشحنه بداخلها. يعتمد هذا الجزء علىlibbinder_ndk.soفي قسم النظام.
أنظمة الإنشاء
استنادًا إلى الخلفية، هناك طريقتان لتجميع AIDL في رمز تجريبي. لمزيد من التفاصيل حول أنظمة التصميم، يُرجى الاطّلاع على مرجع وحدات Soong.
نظام التصميم الأساسي
في أي cc_ أو java_ Android.bp module (أو في Android.mk المكافئ لهما)، يمكنك تحديد ملفات AIDL (.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_interface منظَّمة. لكي تكون الحزم قابلة للتحليل، يجب أن تحتوي على حقول مباشرةً وليس على تعريفات لأنواع محدّدة مباشرةً في اللغات المستهدَفة. لمعرفة كيفية توافق AIDL المنظَّمة مع AIDL الثابتة، يمكنك الاطّلاع على الفرق بين AIDL المنظَّمة وAIDL الثابتة.
الأنواع
يمكنك اعتبار برنامج التحويل البرمجي aidl بمثابة تنفيذ مرجعي للأنواع.
عند إنشاء واجهة، استدعِ aidl --lang=<backend> ... للاطّلاع على ملف الواجهة الناتج. عند استخدام الوحدة aidl_interface، يمكنك عرض الناتج في out/soong/.intermediates/<path to module>/.
| نوع Java أو AIDL | نوع C++ | نوع NDK | نوع الصدأ |
|---|---|---|---|
boolean |
bool |
bool |
bool |
byte8 |
int8_t |
int8_t |
i8 |
char |
char16_t |
char16_t |
u16 |
int |
int32_t |
int32_t |
i32 |
long |
int64_t |
int64_t |
i64 |
float |
float |
float |
f32 |
double |
double |
double |
f64 |
String |
android::String16 |
std::string |
في: &strخارج: String |
android.os.Parcelable |
android::Parcelable |
لا ينطبق | لا ينطبق |
IBinder |
android::IBinder |
ndk::SpAIBinder |
binder::SpIBinder |
T[] |
std::vector<T> |
std::vector<T> |
في: &[T]خارج: Vec<T> |
byte[] |
std::vector |
std::vector1 |
في: &[u8]خارج: Vec<u8> |
List<T> |
std::vector<T>2 |
std::vector<T>3 |
في: In: &[T]4النتيجة: Vec<T> |
FileDescriptor |
android::base::unique_fd |
لا ينطبق | لا ينطبق |
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. في نظام التشغيل 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 أو parcelable. في الإصدار 13 من نظام التشغيل Android أو الإصدارات الأحدث، يمكن أن يكون T أي نوع غير أساسي باستثناء المصفوفات.
4. يتم تمرير الأنواع بشكل مختلف إلى رمز Rust استنادًا إلى ما إذا كانت مدخلات (وسيطة) أو مخرجات (قيمة معروضة).
5. تتوفّر أنواع الاتحاد في نظام التشغيل Android 12 والإصدارات الأحدث.
6. في الإصدار 13 من نظام التشغيل Android أو الإصدارات الأحدث، تتوفّر ميزة الصفائف ذات الحجم الثابت. يمكن أن تحتوي المصفوفات ذات الحجم الثابت على سمات متعددة (على سبيل المثال، int[3][4]). في الخلفية المستندة إلى Java، يتم تمثيل المصفوفات ذات الحجم الثابت كأنواع مصفوفات.
7. لإنشاء مثيل لكائن SharedRefBase من نوع binder، استخدِم
SharedRefBase::make\<My\>(... args ...). تنشئ هذه الدالة عنصر
std::shared_ptr\<T\>، تتم إدارته داخليًا أيضًا، في حال كان
binder مملوكًا من خلال عملية أخرى. يؤدي إنشاء الكائن بطرق أخرى إلى
ملكية مزدوجة.
8. راجِع أيضًا نوع Java أو AIDL byte[].
الاتجاه (داخل وخارج وداخل/خارج)
عند تحديد أنواع وسيطات الدوال، يمكنك تحديدها على أنّها in أو out أو inout. يتحكّم هذا الخيار في اتجاه تمرير المعلومات عند إجراء عملية استدعاء بين العمليات.
يشير محدّد وسيطة
inإلى أنّه يتم تمرير البيانات من المتصل إلى المتلقي. المحدّدinهو الاتجاه التلقائي، ولكن إذا كان بإمكان أنواع البيانات أن تكونoutأيضًا، يجب تحديد الاتجاه.يعني محدّد وسيطة
outأنّه يتم تمرير البيانات من الدالة المستدعاة إلى الدالة المستدعية.محدّد وسيطة
inoutهو مزيج من كليهما. ومع ذلك، ننصحك بتجنُّب استخدام محدّد الوسيطةinout. إذا كنت تستخدمinoutمع واجهة ذات إصدار ومستدعى قديم، ستتم إعادة ضبط الحقول الإضافية المتوفّرة في المستدعي فقط إلى قيمها التلقائية. في ما يتعلق بلغة Rust، يتلقّى النوع العاديinoutالقيمة&mut 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);
}
UTF-8 وUTF-16
باستخدام الخلفية البرمجية لـ CPP، يمكنك اختيار ما إذا كانت السلاسل UTF-8 أو UTF-16.
يمكنك تعريف السلاسل على أنّها @utf8InCpp String في AIDL لتحويلها تلقائيًا إلى UTF-8. تستخدم الخلفيات البرمجية لـ NDK وRust دائمًا سلاسل UTF-8. لمزيد من المعلومات حول التعليق التوضيحي utf8InCpp، يُرجى الاطّلاع على utf8InCpp.
إمكانية قبول القيم الفارغة
يمكنك إضافة تعليقات توضيحية إلى الأنواع التي يمكن أن تكون فارغة باستخدام @nullable.
لمزيد من المعلومات حول التعليق التوضيحي nullable، راجِع nullable.
Custom parcelables
العنصر القابل للتسلسل المخصّص هو عنصر قابل للتسلسل يتم تنفيذه يدويًا في الخلفية المستهدَفة. لا تستخدِم عناصر Parcelable المخصّصة إلا عندما تحاول إضافة دعم للغات أخرى لعنصر Parcelable مخصّص حالي لا يمكن تغييره.
في ما يلي مثال على تعريف 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);
};
لتعريف عنصر قابل للتسلسل مخصّص في 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 في 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 على أنّها حقول ذات قيمة فارغة.
الاتحادات
يتم وضع علامات على اتحادات AIDL وتتشابه ميزاتها في جميع الخلفيات. يتم إنشاء هذه الحقول باستخدام القيمة التلقائية للحقل الأول، وتتوفّر طريقة خاصة بكل لغة للتفاعل معها:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
مثال على Java
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setStringField("abc"); // setter
مثال على C++ وNDK
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u ==< Foo::makeFoo::s>tringField("<abc>")); // maketag(value)
مثال على Rust
في Rust، يتم تنفيذ الاتحادات كأنواع تعدادية ولا تتضمّن دوال جلب وتعيين صريحة.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::String>Field(x) = panic!("Default constructed to first field");
Foo::>ParcelableField(x) = panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
معالجة الأخطاء
يوفّر نظام التشغيل Android أنواع أخطاء مدمجة يمكن أن تستخدمها الخدمات عند الإبلاغ عن الأخطاء. تستخدم المجلّدات هذه المعرّفات ويمكن لأي خدمات تنفّذ واجهة مجلّد استخدامها. ويتم توثيق استخدامها بشكل جيد في تعريف AIDL، ولا تتطلّب أي حالة أو نوع إرجاع يحدّده المستخدم.
مَعلمات النتائج التي تتضمّن أخطاء
عندما تعرض إحدى دوال AIDL خطأً، قد لا يتم تهيئة الدالة أو تعديل مَعلمات الإخراج. على وجه التحديد، قد يتم تعديل المَعلمات الناتجة إذا حدث الخطأ أثناء إلغاء التجميع، وليس أثناء معالجة المعاملة نفسها. بشكل عام، عند تلقّي خطأ من دالة AIDL، يجب اعتبار جميع المَعلمات inout وout بالإضافة إلى القيمة المعروضة (التي تعمل كمَعلمة out في بعض الأنظمة الخلفية) في حالة غير محدّدة.
قيم الخطأ المطلوب استخدامها
يمكن استخدام العديد من قيم الخطأ المضمّنة في أي واجهات AIDL، ولكن يتم التعامل مع بعضها بطريقة خاصة. على سبيل المثال، يمكن استخدام EX_UNSUPPORTED_OPERATION وEX_ILLEGAL_ARGUMENT عند وصف حالة الخطأ، ولكن يجب عدم استخدام EX_TRANSACTION_FAILED لأنّ البنية الأساسية تتعامل معه بشكل خاص. يمكنك الاطّلاع على التعريفات الخاصة بالخادم الخلفي للحصول على مزيد من المعلومات حول هذه القيم المضمّنة.
إذا كانت واجهة AIDL تتطلّب قيم أخطاء إضافية لا تغطّيها أنواع الأخطاء المضمّنة، يمكنها استخدام الخطأ المضمّن الخاص بالخدمة والذي يتيح تضمين قيمة خطأ خاصة بالخدمة يحدّدها المستخدم. يتم عادةً تحديد هذه الأخطاء الخاصة بالخدمة
في واجهة AIDL كـ const int أو int-backed enum ولا يتم تحليلها
بواسطة 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، راجِع مثال AIDL المستند إلى Rust في أنماط Android Rust.
أنواع عمليات الاستيراد
سواء كان النوع المحدّد واجهة أو قابلاً للتسلسل أو اتحادًا، يمكنك استيراده في 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;
على الرغم من أنّه يمكنك استيراد نوع متداخل في Java، يجب تضمين العنوان لنوعه الأساسي في الخلفيتين البرمجيتين CPP وNDK. على سبيل المثال، عند استيراد نوع Bar متداخل محدّد في my/package/IFoo.aidl (IFoo هو النوع الأساسي للملف)، يجب تضمين <my/package/IFoo.h> لخادم CPP الخلفي (أو <aidl/my/package/IFoo.h> لخادم NDK الخلفي).
تنفيذ واجهة
لتنفيذ واجهة، يجب أن ترث من فئة العنصر النائب الأصلية. يُطلق على تنفيذ واجهة اسم خدمة غالبًا عند تسجيلها لدى مدير الخدمة أو android.app.ActivityManager، ويُطلق عليها اسم دالة رد الاتصال عند تسجيلها من قِبل عميل إحدى الخدمات. ومع ذلك، يتم استخدام مجموعة متنوعة من الأسماء لوصف عمليات تنفيذ الواجهة، وذلك حسب الاستخدام الدقيق. تقرأ فئة العنصر النائب الأوامر من برنامج تشغيل Binder وتنفّذ الطرق التي تنفّذها. لنفترض أنّ لديك ملف AIDL على النحو التالي:
package my.package;
interface IFoo {
int doFoo();
}
في Java، يجب أن توسّع من فئة Stub التي تم إنشاؤها:
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 = ch<eckS>erviceIFoo(String16("s&ervice-name"), myService);
// waiting until service comes up (new in Android 11)
myServ<ice >= waitForServiceIFoo(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
mySe<rvic>e = waitForDeclaredServiceIFoo(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 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. استخدِم دائمًاAIBinder_DeathRecipient_setOnUnlinkedللتحكّم في مدة صلاحية ملف تعريف ارتباط متلقّي إشعار الوفاة. - في الخلفية المستندة إلى Rust، أنشئ عنصر
DeathRecipient، ثم استدعِmy_binder.link_to_death(&mut my_death_recipient). يُرجى العِلم أنّه بما أنّDeathRecipientيملك وظيفة معاودة الاتصال، عليك إبقاء هذا العنصر نشطًا طالما أردت تلقّي الإشعارات.
معلومات المتصل
عند تلقّي طلب ربط في النواة، تتوفّر معلومات المتّصل في عدة واجهات برمجة تطبيقات. يشير معرّف العملية (PID) إلى معرّف عملية Linux التي ترسل معاملة. يشير معرّف المستخدم (UI) إلى معرّف مستخدم Linux. عند تلقّي مكالمة أحادية الاتجاه، يكون معرّف PID للمتصل هو 0. خارج سياق معاملة الرابط، تعرض هذه الدوال معرّف العملية (PID) ومعرّف المستخدم (UID) للعملية الحالية.
في الخلفية المستندة إلى 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 يتيح استخدام WeakReference، إلا أنّه لا يتيح استخدام مراجع الرابط الضعيف
في الطبقة الأصلية.
في الخلفية البرمجية لـ CPP، يكون النوع الضعيف 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();
في خلفية 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.
بالنسبة إلى النوع enum MyEnum المحدّد في AIDL، يتم توفير التكرار على النحو التالي.
في خلفية CPP:
::android::enum_range<MyEnum>()
في الخلفية الخاصة بـ NDK:
::ndk::enum_range<MyEnum>()
في الخلفية المستندة إلى Rust:
MyEnum::enum_values()
إدارة سلاسل المحادثات
يحتفظ كل مثيل من libbinder في إحدى العمليات بمجموعة سلاسل محادثات واحدة. في معظم حالات الاستخدام، يجب أن يكون هناك مجموعة سلاسل محادثات واحدة فقط، تتم مشاركتها بين جميع الأنظمة الخلفية.
الاستثناء الوحيد هو إذا كان رمز المورّد يحمّل نسخة أخرى من libbinder
للتواصل مع /dev/vndbinder. ويتم ذلك في عقدة رابط منفصلة، لذا لا تتم مشاركة مجموعة سلاسل التنفيذ.
بالنسبة إلى الخلفية المستندة إلى Java، يمكن فقط زيادة حجم مجموعة سلاسل التنفيذ (لأنّها بدأت بالفعل):
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، يمكنك بأمان إعادة تسمية الحقول مع الحفاظ على توافق البروتوكول. قد تحتاج إلى تعديل الرمز البرمجي لمواصلة الإنشاء، ولكن ستستمر أي برامج تم إنشاؤها في العمل معًا.
الأسماء التي يجب تجنُّبها: