تمثّل الواجهة الخلفية AIDL هدفًا لإنشاء رمز تجريبي. استخدِم دائمًا ملفات AIDL بلغة معيّنة مع وقت تشغيل محدّد. اعتمادًا على السياق، يجب استخدام برامج خلفية مختلفة للغة AIDL.
في الجدول التالي، يشير ثبات مساحة واجهة برمجة التطبيقات إلى إمكانية تجميع الرمز البرمجي مع مساحة واجهة برمجة التطبيقات هذه بطريقة يمكن من خلالها تسليم الرمز البرمجي بشكل مستقل عن ملف system.img
libbinder.so
الثنائي.
تتضمّن AIDL برامج الخلفية التالية:
الخلفية | اللغة | مساحة واجهة برمجة التطبيقات | أنظمة التصميم |
---|---|---|---|
Java | Java | حزمة تطوير البرامج أو SystemApi (إصدار ثابت*) |
الكل |
NDK | C++ | libbinder_ndk (ثابت*) |
aidl_interface |
تكلفة المكالمة الهاتفية | C++ | libbinder (غير مستقر) |
الكل |
Rust | Rust | libbinder_rs (ثابت*) |
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 |
byte 8 |
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::vector 1 |
في: &[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
أو قابل للتسلسل. في الإصدار 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
أو 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);
};
لتعريف حزمة مخصّصة متوافقة مع 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 باستخدام 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::make<Foo::stringField>("abc")); // make<tag>(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::StringField(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
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;
أو في الخلفية الخاصة ببرنامج "شركاء المحتوى المفضّلون":
#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
، ويُطلق عليها اسم دالة رد الاتصال عند تسجيلها من قِبل عميل إحدى الخدمات. ومع ذلك، يتم استخدام مجموعة متنوعة من الأسماء لوصف عمليات تنفيذ الواجهة، وذلك حسب الاستخدام الدقيق. تقرأ فئة العنصر النائب الأوامر من برنامج تشغيل الرابط وتنفّذ الطرق التي تنفّذها. لنفترض أنّ لديك ملف 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;
}
في الخلفية الأصلية (لاحظ مساحة الاسم الإضافية 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"));
في الخلفية الأصلية (لاحظ مساحة الاسم الإضافية 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
. - في الخلفية المستندة إلى 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
في الخلفية الأصلية (لاحظ مساحة الاسم الإضافية 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، يمكن فقط زيادة حجم مجموعة سلاسل التنفيذ (لأنّها بدأت بالفعل):
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، يمكنك بأمان إعادة تسمية الحقول مع الحفاظ على توافق البروتوكول. قد تحتاج إلى تعديل الرمز البرمجي لمواصلة الإنشاء، ولكن ستستمر أي برامج تم إنشاؤها في العمل معًا.
الأسماء التي يجب تجنّبها: