واجهة AIDL الخلفية هي هدف لإنشاء رمز رمزي. استخدِم دائمًا ملفات AIDL بلغة معيّنة مع وقت تشغيل محدّد. استنادًا إلى السياق، يجب استخدام الخلفيات المختلفة لـ AIDL.
في الجدول التالي، يشير ثبات واجهة برمجة التطبيقات إلى إمكانية compiling code against this API surface (تجميع الرمز البرمجي وفقًا لواجهة برمجة التطبيقات هذه) بطريقة يمكن بها إرسال الرمز البرمجي بشكل مستقل عن ملف system.img
libbinder.so
الثنائي.
تتضمّن واجهة برمجة التطبيقات AIDL الخلفيات التالية:
الخلفية | اللغة | واجهة برمجة التطبيقات | أنظمة التصميم |
---|---|---|---|
Java | Java | حزمة تطوير البرامج (SDK) أو SystemApi (الإصدار الثابت*) |
الكل |
NDK | C++ | libbinder_ndk (الإصدار الثابت*) |
aidl_interface |
تكلفة المكالمة الهاتفية | C++ | libbinder (غير متّصل بالإنترنت) |
الكل |
Rust | Rust | libbinder_rs (الإصدار الثابت*) |
aidl_interface |
- إنّ مساحات عرض واجهات برمجة التطبيقات هذه مستقرة، ولكنّ العديد من واجهات برمجة التطبيقات، مثل تلك المخصّصة لإدارة الخدمات، محجوزة لاستخدام المنصة الداخلية ولا تتوفّر للتطبيقات. لمزيد من المعلومات حول كيفية استخدام لغة تعريف واجهة Android (AIDL) في التطبيقات، يُرجى الاطّلاع على مقالة لغة تعريف واجهة Android (AIDL).
- تم تقديم الخلفية Rust في Android 12، وأصبح بإمكانك استخدام الخلفية NDK اعتبارًا من Android 10.
- تم إنشاء حزمة 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
منظَّمة. لكي تكون مثيلات Parcelable منظَّمة، يجب أن تحتوي على حقول مباشرةً وألا تكون تعريفات لأنواع محدّدة مباشرةً باللغات المستهدَفة. لمعرفة مدى توافق لغة 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. في الإصدار 12 من نظام التشغيل Android أو الإصدارات الأحدث، تستخدِم صفائف البايت uint8_t
بدلاً من int8_t
لأسباب تتعلّق بالتوافق.
2. تتوافق الخلفية في C++ مع List<T>
حيث يكون T
أحد موارد
String
أو IBinder
أو ParcelFileDescriptor
أو parcelable. في الإصدار T
من نظام التشغيل Android
13 أو الإصدارات الأحدث، يمكن أن يكون أي نوع غير أساسي (بما في ذلك أنواع الواجهات)
باستثناء الصفائف. ينصح فريق AOSP باستخدام أنواع الصفائف مثل T[]
، لأنّها
تعمل في جميع الخلفيات.
3. تتوافق الخلفية في NDK مع List<T>
حيث يكون T
أحد String
أو
ParcelFileDescriptor
أو parcelable. في الإصدار 13 من Android
أو الإصدارات الأحدث، يمكن أن يكون T
أي نوع غير أساسي باستثناء الصفائف.
4. يتم تمرير الأنواع بشكل مختلف لرمز Rust بناءً على ما إذا كانت مدخلات (مَعلمة) أو مخرجات (قيمة معروضة).
5- تتوفّر أنواع المجموعات في الإصدار 12 من نظام التشغيل Android والإصدارات الأحدث.
6- في الإصدار 13 من Android أو الإصدارات الأحدث، يتم استخدام صفائف بحجم ثابت. يمكن أن تحتوي الصفائف ذات الحجم الثابت على سمات متعددة (مثل
int[3][4]
). في الخلفية في Java، يتم تمثيل الصفائف ذات الحجم الثابت
كأنواع صفائف.
7. لإنشاء مثيل لكائن SharedRefBase
، استخدِم
SharedRefBase::make\<My\>(... args ...)
. تنشئ هذه الدالة عنصرًا
std::shared_ptr\<T\>
، والذي تتم إدارته أيضًا داخليًا، في حال كان
binder مملوكًا لعملية أخرى. يؤدّي إنشاء العنصر بطرق أخرى إلى
ملكية مضاعفة.
8. يمكنك أيضًا الاطّلاع على نوع Java أو AIDL byte[]
.
الاتجاه (in وout وinout)
عند تحديد أنواع الوسيطات للدوالّ، يمكنك تحديد
ها على أنّها in
أو out
أو inout
. يتحكّم هذا الإعداد في الاتجاه الذي يتم فيه
تمرير المعلومات لطلب IPC.
يشير محدّد الوسيطة
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
باستخدام الخلفية في لغة C++، يمكنك اختيار ما إذا كانت السلاسل بترميز UTF-8 أو UTF-16.
يمكنك تحديد السلاسل على أنّها @utf8InCpp String
في AIDL لتحويلها تلقائيًا
إلى UTF-8. تستخدم الخلفيات في NDK وRust دائمًا سلاسل UTF-8. لمزيد من
المعلومات عن التعليق التوضيحي utf8InCpp
، يُرجى الاطّلاع على
utf8InCpp.
إمكانية قبول القيم الفارغة
يمكنك إضافة تعليقات توضيحية إلى الأنواع التي يمكن أن تكون فارغة باستخدام @nullable
.
لمزيد من المعلومات عن التعليق التوضيحي nullable
، اطّلِع على
nullable.
السلع المخصّصة للتوصيل في طرد
العنصر المخصّص الذي يمكن تقسيمه هو عنصر يمكن تقسيمه يتم تنفيذه يدويًا في خلفي ة مستهدفة. لا تستخدِم عنصرَي 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. قدِّم عاملَي التشغيل <
و==
لخلفية "المعالجة المحدودة للصور" و"حزمة تطوير البرامج (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
كحقول ذات قيمة فارغة.
الاتحادات
يتم وضع علامة على مجموعات 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.setSringField("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، يتم تنفيذ الوحدات على أنّها قوائم أرقام مميزة ولا تحتوي على وظائف جلب و ضبط صريحة.
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 على أنّها 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 |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
استخدام الخلفيات المختلفة
هذه التعليمات خاصة برمز نظام التشغيل Android. تستخدِم هذه الأمثلة نوعًا محدّدًا هو my.package.IFoo
. للحصول على تعليمات حول كيفية استخدام واجهة برمجة التطبيقات
الخلفية في Rust، اطّلِع على مثال ملف Rust AIDL
في أنماط IDE لبرمجة التطبيقات بلغة Rust.
أنواع الاستيراد
سواء كان النوع المحدّد واجهة أو قابلاً للتقسيم أو اتحادًا، يمكنك استيراده في Java:
import my.package.IFoo;
أو في الخلفية في "شركاء المحتوى في خرائط Google":
#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
، ويُطلق عليه اسم
استدعاء عندما يسجّله عميل خدمة. ومع ذلك، يتم استخدام مجموعة متنوعة
من الأسماء لوصف عمليات تنفيذ الواجهات استنادًا إلى
الاستخدام الدقيق. تقرأ فئة الرمز المرجعي الأوامر من برنامج تشغيل الربط وتنفذ methods التي تنفذها. لنفترض أنّ لديك ملف AIDL على النحو التالي:
package my.package;
interface IFoo {
int doFoo();
}
في Java، يجب إضافة فئة Stub
تم إنشاؤها:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
في الخلفية في "الإعلانات الصورية على شبكة البحث":
#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"));
في الخلفية في "الإعلانات الصورية على شبكة البحث":
#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 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
يملك طلب إعادة الاتصال، يجب إبقاء هذا العنصر نشطًا طالما أردت تلقّي الإشعارات.
معلومات المتصل
عند تلقّي مكالمة من رابط النواة، تتوفّر معلومات المتصل في العديد من واجهات برمجة التطبيقات. يشير معرّف العملية (PID) إلى معرّف عملية Linux للعملية التي تُرسل معاملة. يشير رقم تعريف المستخدم (UI) إلى رقم تعريف مستخدم Linux. عند تلقّي مكالمة أحادية الاتجاه، يكون معرّف PID للمتصل هو 0. خارج سياق معاملة الربط، تعرض هذه الدوال رقم تعريف العملية (PID) ورقم التعريف الفريد (UID) للعملية الحالية.
في الخلفية في Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
في الخلفية في "الإعلانات الصورية على شبكة البحث":
... = 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) {...}
في الخلفية في "الإعلانات الصورية على شبكة البحث":
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();
الحصول على وصف الواجهة ديناميكيًا
يحدِّد وصف الواجهة نوع الواجهة. يكون ذلك مفيدًا عند debugging أو عند استخدام رابط غير معروف.
في 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 بسبب اعتبارات تتعلّق بحجم الرمز البرمجي.
بالنسبة إلى مصنّف enum 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.
وهذا يعني أنّ التطبيقات التي تستخدم async Rust تحتاج إلى اعتبارات خاصة،
خاصةً عندما يتعلق الأمر باستخدام join_thread_pool
. يُرجى الاطّلاع على القسم المخصص لتسجيل الخدمات للحصول على مزيد من المعلومات حول هذا الموضوع.
الأسماء المحجوزة
تحتفظ لغات C++ وJava وRust ببعض الأسماء ككلمات رئيسية أو لاستخدام خاص باللغة. على الرغم من أنّ واجهة برمجة التطبيقات AIDL لا تفرض قيودًا استنادًا إلى قواعد اللغة، إلا أنّ استخدام اسم حقل أو نوع يتطابق مع اسم محجوز قد يؤدي إلى تعذُّر compiling لـ C++ أو Java. بالنسبة إلى Rust، تتم إعادة تسمية الحقل أو النوع باستخدام نحو
المعرّف الأوّلي، والذي يمكن الوصول إليه باستخدام البادئة r#
.
ننصحك بتجنُّب استخدام الأسماء المحجوزة في تعريفات AIDL حيثما أمكن ذلك لتجنُّب عمليات الربط غير المريحة أو تعذُّر الترجمة تمامًا.
إذا كانت لديك أسماء محجوزة في تعريفات AIDL، يمكنك إعادة تسمية الحقول بأمان مع الحفاظ على التوافق مع البروتوكول. قد تحتاج إلى تعديل الرمز البرمجي لمواصلة عملية الإنشاء، ولكن ستظل أي برامج تم إنشاؤها في السابق قادرة على التفاعل مع بعضها.
الأسماء التي يجب تجنُّبها: