تعد الواجهة الخلفية لـ AIDL هدفًا لإنشاء كود كعب الروتين. عند استخدام ملفات AIDL ، فأنت تستخدمها دائمًا بلغة معينة مع وقت تشغيل محدد. اعتمادًا على السياق ، يجب عليك استخدام خلفيات AIDL مختلفة.
AIDL لديه الخلفيات التالية:
الخلفية | لغة | سطح API | بناء الأنظمة |
---|---|---|---|
جافا | جافا | SDK / SystemApi (مستقر *) | الكل |
NDK | C ++ | libbinder_ndk (مستقر *) | واجهة المساعد |
CPP | C ++ | libbinder (غير مستقر) | الكل |
الصدأ | الصدأ | libbinder_rs (غير مستقر) | واجهة المساعد |
- أسطح API هذه مستقرة ، ولكن العديد من واجهات برمجة التطبيقات ، مثل تلك الخاصة بإدارة الخدمة ، محجوزة لاستخدام النظام الأساسي الداخلي وليست متاحة للتطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات ، راجع وثائق المطور .
- تم تقديم Rust backend في Android 12 ؛ كانت الواجهة الخلفية NDK متاحة اعتبارًا من Android 10.
- تم بناء صندوق الصدأ فوق
libbinder_ndk
. يستخدم APEXes صندوق الموثق بنفس الطريقة التي يستخدمها أي شخص آخر على جانب النظام. يتم تجميع جزء الصدأ في APEX ويتم شحنه بداخله. يعتمد ذلك علىlibbinder_ndk.so
على قسم النظام.
بناء الأنظمة
اعتمادًا على الخلفية ، هناك طريقتان لتجميع AIDL في كود كعب الروتين. لمزيد من التفاصيل حول أنظمة الإنشاء ، راجع مرجع وحدة Soong .
نظام البناء الأساسي
في أي cc_
أو java_
Android.bp (أو في معادلاتها Android.mk
) ، يمكن تحديد ملفات .aidl
كملفات مصدر. في هذه الحالة ، يتم استخدام الواجهات الخلفية لـ Java / CPP لـ AIDL (وليس الخلفية NDK) ، ويتم إضافة الفئات التي تستخدم ملفات AIDL المقابلة إلى الوحدة تلقائيًا. يمكن تحديد خيارات مثل local_include_dirs
، التي تخبر نظام الإنشاء مسار الجذر لملفات AIDL في تلك الوحدة النمطية في هذه الوحدات ضمن aidl:
group. لاحظ أن الواجهة الخلفية Rust مخصصة للاستخدام مع Rust فقط. يتم التعامل مع وحدات rust_
بشكل مختلف حيث لم يتم تحديد ملفات AIDL كملفات مصدر. بدلاً من ذلك ، تنتج الوحدة النمطية aidl_interface
عنصرًا rustlib
يسمى <aidl_interface name>-rust
والذي يمكن ربطه. لمزيد من التفاصيل ، راجع مثال Rust AIDL .
واجهة المساعد
انظر AIDL المستقر . يجب هيكلة الأنواع المستخدمة مع نظام البناء هذا ؛ وهذا معبر عنه في AIDL مباشرة. هذا يعني أنه لا يمكن استخدام الطرود المخصصة.
أنواع
يمكنك اعتبار مترجم aidl
كتطبيق مرجعي للأنواع. عند إنشاء واجهة ، قم باستدعاء aidl --lang=<backend> ...
لمشاهدة ملف الواجهة الناتج. عند استخدام وحدة aidl_interface
، يمكنك عرض الإخراج في out/soong/.intermediates/<path to module>/
.
جافا / نوع AIDL | نوع C ++ | نوع NDK | نوع الصدأ |
---|---|---|---|
قيمة منطقية | منطقي | منطقي | منطقي |
بايت | int8_t | int8_t | i8 |
شار | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
طويل | int64_t | int64_t | i64 |
تطفو | تطفو | تطفو | f32 |
مزدوج | مزدوج | مزدوج | f64 |
سلسلة | android :: String16 | الأمراض المنقولة جنسيا :: سلسلة | سلسلة |
android.os.Parcelable | android :: Parcelable | غير متاح | غير متاح |
آي بيندر | android :: IBinder | ndk :: SpAIBinder | الموثق :: SpIBinder |
تي [] | الأمراض المنقولة جنسيا :: متجه <T> | الأمراض المنقولة جنسيا :: متجه <T> | في: & [T] خارج: Vec <T> |
بايت [] | الأمراض المنقولة جنسيا :: متجه <uint8_t> | الأمراض المنقولة جنسياً :: المتجه <int8_t> 1 | في: & [u8] خارج: Vec <u8> |
قائمة <T> | الأمراض المنقولة جنسياً :: متجه <T> 2 | الأمراض المنقولة جنسيا :: متجه <T> 3 | في: & [T] 4 خارج: Vec <T> |
واصف الملف | android :: base :: unique_fd | غير متاح | الموثق :: parcel :: ParcelFileDescriptor |
ParcelFileDescriptor | android :: os :: ParcelFileDescriptor | ndk :: ScopedFileDescriptor | الموثق :: parcel :: ParcelFileDescriptor |
نوع الواجهة (T) | android :: sp <T> | الأمراض المنقولة جنسيا :: shared_ptr <T> | الموثق :: قوي |
نوع لا يتجزأ (T) | تي | تي | تي |
نوع الاتحاد (T) 5 | تي | تي | تي |
T [N] 6 | الأمراض المنقولة جنسيا :: مجموعة <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
أو لا يتجزأ. في Android 13 أو أعلى ، يمكن أن يكون T
أي نوع غير بدائي باستثناء المصفوفات.
4. يتم تمرير الأنواع بشكل مختلف لرمز Rust اعتمادًا على ما إذا كانت مدخلات (وسيطة) ، أو مخرجات (قيمة تم إرجاعها).
5. أنواع الاتحاد مدعومة في Android 12 والإصدارات الأحدث.
6. في Android 13 أو أعلى ، يتم دعم المصفوفات ذات الحجم الثابت. يمكن أن تحتوي المصفوفات ذات الحجم الثابت على أبعاد متعددة (على سبيل المثال int[3][4]
). في الواجهة الخلفية لـ Java ، يتم تمثيل المصفوفات ذات الحجم الثابت كأنواع مصفوفة.
الاتجاهية (الداخل / الخارج / الداخل)
عند تحديد أنواع الوسيطات الخاصة بالدوال ، يمكنك تحديدها كما in
الداخل أو out
أو inout
. يتحكم هذا في تحديد معلومات الاتجاه التي يتم تمريرها لمكالمة IPC. in
هو الاتجاه الافتراضي ، ويشير إلى أن البيانات يتم تمريرها من المتصل إلى المستدعي. out
يعني أن البيانات يتم تمريرها من المستدعى إلى المتصل. inout
هو مزيج من كلاهما. ومع ذلك ، يوصي فريق Android بتجنب استخدام محدد الوسيطة inout
. إذا كنت تستخدم inout
مع واجهة ذات إصدار ومستدعي أقدم ، فسيتم إعادة تعيين الحقول الإضافية الموجودة فقط في المتصل إلى قيمها الافتراضية. فيما يتعلق بالصدأ ، يتلقى نوع inout
العادي &mut Vec<T>
، ويستقبل نوع القائمة inout
&mut Vec<T>
.
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 .
الطرود المخصصة
في خلفيات C ++ و Java الخلفية في نظام البناء الأساسي ، يمكنك إعلان عنصر لا يتجزأ يتم تنفيذه يدويًا في الخلفية المستهدفة (في C ++ أو في Java).
package my.package;
parcelable Foo;
أو مع إعلان رأس C ++:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
بعد ذلك ، يمكنك استخدام هذا الجزء القابل للتجزئة كنوع في ملفات AIDL ، لكن لن يتم إنشاؤه بواسطة AIDL.
الصدأ لا يدعم الطرود المخصصة.
قيم افتراضية
يمكن أن تعلن العناصر المركبة الهيكلية عن القيم الافتراضية لكل حقل للأولويات والسلسلة String
من هذه الأنواع.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
في الواجهة الخلفية لـ Java عندما تكون القيم الافتراضية مفقودة ، تتم تهيئة الحقول كقيم صفرية للأنواع الأولية null
للأنواع غير الأولية.
في الخلفيات الأخرى ، تتم تهيئة الحقول بقيم افتراضية تم تهيئتها عندما لا يتم تحديد القيم الافتراضية. على سبيل المثال ، في الواجهة الخلفية لـ C ++ ، تتم تهيئة حقول String
كسلسلة فارغة ويتم تهيئة حقول List<T>
vector<T>
. يتم تهيئة الحقول @nullable
كحقول ذات قيمة خالية.
معالجة الأخطاء
يوفر نظام التشغيل Android أنواع أخطاء مضمنة للخدمات لاستخدامها عند الإبلاغ عن الأخطاء. يتم استخدامها بواسطة Binder ويمكن استخدامها من قبل أي خدمات تقوم بتنفيذ واجهة Binder. تم توثيق استخدامها جيدًا في تعريف 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
جنبًا إلى جنب مع الخطأ المحدد من قبل المستخدم.
يمكن العثور على أنواع الأخطاء المضمنة في الملفات التالية:
الخلفية | تعريف |
---|---|
جافا | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
الصدأ | 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;
على الرغم من أنه يمكنك استيراد نوع متداخل في Java ، إلا أنه في الواجهات الخلفية 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(())
}
}
التسجيل والحصول على الخدمات
عادة ما يتم تسجيل الخدمات في النظام الأساسي Android في عملية مدير servicemanager
. بالإضافة إلى واجهات برمجة التطبيقات أدناه ، تتحقق بعض واجهات برمجة التطبيقات من الخدمة (مما يعني أنها تعود على الفور إذا لم تكن الخدمة متاحة). تحقق من واجهة مدير servicemanager
المقابلة للحصول على التفاصيل الدقيقة. لا يمكن إجراء هذه العمليات إلا عند التجميع باستخدام نظام Android الأساسي.
في جافا:
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
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(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(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()
}
ربط الموت
يمكنك طلب الحصول على إشعار عند وفاة خدمة استضافة الموثق. يمكن أن يساعد ذلك في تجنب تسريب وكلاء رد الاتصال أو المساعدة في استعادة الأخطاء. قم بإجراء هذه المكالمات على كائنات وكيل الموثق.
- في 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. عندما تكون خارج سياق معاملة Binder ، تقوم هذه الوظائف بإرجاع 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();
تقارير الأخطاء وتصحيح أخطاء API للخدمات
عند تشغيل تقارير الأخطاء (على سبيل المثال ، مع adb bugreport
) ، فإنها تجمع المعلومات من جميع أنحاء النظام للمساعدة في تصحيح الأخطاء المختلفة. بالنسبة لخدمات dumpsys
، تستخدم تقارير الأخطاء عمليات التفريغ الثنائية في جميع الخدمات المسجلة لدى مدير الخدمة لتفريغ معلوماتها في تقرير الأخطاء. يمكنك أيضًا استخدام dumpsys
في سطر الأوامر للحصول على معلومات من خدمة مع خدمة dumpsys SERVICE [ARGS]
. في الخلفيات C ++ و Java ، يمكنك التحكم في الترتيب الذي يتم به إغراق الخدمات باستخدام وسيطات إضافية addService
. يمكنك أيضًا استخدام dumpsys --pid SERVICE
للحصول على معرف المنتج للخدمة أثناء التصحيح.
لإضافة مخرجات مخصصة إلى خدمتك ، يمكنك تجاوز طريقة 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_range()
إدارة الخيط
كل مثيل من libbinder
في عملية تحافظ على ترابط واحد. بالنسبة لمعظم حالات الاستخدام ، يجب أن تكون مجموعة سلاسل محادثات واحدة بالضبط ، يتم مشاركتها عبر جميع الواجهات الخلفية. الاستثناء الوحيد لذلك هو عندما يقوم رمز البائع بتحميل نسخة أخرى من libbinder
للتحدث إلى /dev/vndbinder
. نظرًا لأن هذا موجود على عقدة رابط منفصلة ، لا تتم مشاركة مجموعة مؤشرات الترابط.
بالنسبة للواجهة الخلفية لجافا ، يمكن أن يزيد حجم مجموعة سلاسل العمليات (حيث أنها بدأت بالفعل):
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();
الأسماء المحجوزة
تحتفظ C ++ و Java و Rust ببعض الأسماء ككلمات رئيسية أو لاستخدام لغة معينة. بينما لا يفرض AIDL قيودًا استنادًا إلى قواعد اللغة ، فإن استخدام أسماء الحقول أو الأنواع التي تتطابق مع اسم محجوز قد يؤدي إلى فشل تجميع لـ C ++ أو Java. بالنسبة إلى Rust ، تتم إعادة تسمية الحقل أو النوع باستخدام بناء جملة "المعرف الخام" ، ويمكن الوصول إليه باستخدام البادئة r#
.
نوصي بتجنب استخدام الأسماء المحجوزة في تعريفات AIDL الخاصة بك حيثما أمكن لتجنب الارتباطات غير المريحة أو الفشل التام في الترجمة.
إذا كان لديك بالفعل أسماء محجوزة في تعريفات AIDL الخاصة بك ، فيمكنك إعادة تسمية الحقول بأمان بينما تظل متوافقًا مع البروتوكول ؛ قد تحتاج إلى تحديث التعليمات البرمجية الخاصة بك لمواصلة البناء ، ولكن أي برامج تم إنشاؤها بالفعل ستستمر في العمل البيني.
الأسماء التي يجب تجنبها: * كلمات رئيسية C ++ * كلمات رئيسية Java * كلمات مفتاحية الصدأ
وتعد الواجهة الخلفية لـ AIDL هدفًا لإنشاء كود كعب الروتين. عند استخدام ملفات AIDL ، فأنت تستخدمها دائمًا بلغة معينة مع وقت تشغيل محدد. اعتمادًا على السياق ، يجب عليك استخدام خلفيات AIDL مختلفة.
AIDL لديه الخلفيات التالية:
الخلفية | لغة | سطح API | بناء الأنظمة |
---|---|---|---|
جافا | جافا | SDK / SystemApi (مستقر *) | الكل |
NDK | C ++ | libbinder_ndk (مستقر *) | واجهة المساعد |
CPP | C ++ | libbinder (غير مستقر) | الكل |
الصدأ | الصدأ | libbinder_rs (غير مستقر) | واجهة المساعد |
- أسطح API هذه مستقرة ، ولكن العديد من واجهات برمجة التطبيقات ، مثل تلك الخاصة بإدارة الخدمة ، محجوزة لاستخدام النظام الأساسي الداخلي وليست متاحة للتطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات ، راجع وثائق المطور .
- تم تقديم Rust backend في Android 12 ؛ كانت الواجهة الخلفية NDK متاحة اعتبارًا من Android 10.
- تم بناء صندوق الصدأ فوق
libbinder_ndk
. يستخدم APEXes صندوق الموثق بنفس الطريقة التي يستخدمها أي شخص آخر على جانب النظام. يتم تجميع جزء الصدأ في APEX ويتم شحنه بداخله. يعتمد ذلك علىlibbinder_ndk.so
على قسم النظام.
بناء الأنظمة
اعتمادًا على الخلفية ، هناك طريقتان لتجميع AIDL في كود كعب الروتين. لمزيد من التفاصيل حول أنظمة الإنشاء ، راجع مرجع وحدة Soong .
نظام البناء الأساسي
في أي cc_
أو java_
Android.bp (أو في معادلاتها Android.mk
) ، يمكن تحديد ملفات .aidl
كملفات مصدر. في هذه الحالة ، يتم استخدام الواجهات الخلفية لـ Java / CPP لـ AIDL (وليس الخلفية NDK) ، ويتم إضافة الفئات التي تستخدم ملفات AIDL المقابلة إلى الوحدة تلقائيًا. يمكن تحديد خيارات مثل local_include_dirs
، التي تخبر نظام الإنشاء مسار الجذر لملفات AIDL في تلك الوحدة النمطية في هذه الوحدات ضمن aidl:
group. لاحظ أن الواجهة الخلفية Rust مخصصة للاستخدام مع Rust فقط. يتم التعامل مع وحدات rust_
بشكل مختلف حيث لم يتم تحديد ملفات AIDL كملفات مصدر. بدلاً من ذلك ، تنتج الوحدة النمطية aidl_interface
عنصرًا rustlib
يسمى <aidl_interface name>-rust
والذي يمكن ربطه. لمزيد من التفاصيل ، راجع مثال Rust AIDL .
واجهة المساعد
انظر AIDL المستقر . يجب هيكلة الأنواع المستخدمة مع نظام البناء هذا ؛ وهذا معبر عنه في AIDL مباشرة. هذا يعني أنه لا يمكن استخدام الطرود المخصصة.
أنواع
يمكنك اعتبار مترجم aidl
كتطبيق مرجعي للأنواع. عند إنشاء واجهة ، قم باستدعاء aidl --lang=<backend> ...
لمشاهدة ملف الواجهة الناتج. عند استخدام وحدة aidl_interface
، يمكنك عرض الإخراج في out/soong/.intermediates/<path to module>/
.
جافا / نوع AIDL | نوع C ++ | نوع NDK | نوع الصدأ |
---|---|---|---|
قيمة منطقية | منطقي | منطقي | منطقي |
بايت | int8_t | int8_t | i8 |
شار | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
طويل | int64_t | int64_t | i64 |
تطفو | تطفو | تطفو | f32 |
مزدوج | مزدوج | مزدوج | f64 |
سلسلة | android :: String16 | الأمراض المنقولة جنسيا :: سلسلة | سلسلة |
android.os.Parcelable | android :: Parcelable | غير متاح | غير متاح |
آي بيندر | android :: IBinder | ndk :: SpAIBinder | الموثق :: SpIBinder |
تي [] | الأمراض المنقولة جنسيا :: متجه <T> | الأمراض المنقولة جنسيا :: متجه <T> | في: & [T] خارج: Vec <T> |
بايت [] | الأمراض المنقولة جنسيا :: متجه <uint8_t> | الأمراض المنقولة جنسياً :: المتجه <int8_t> 1 | في: & [u8] خارج: Vec <u8> |
قائمة <T> | الأمراض المنقولة جنسياً :: متجه <T> 2 | الأمراض المنقولة جنسيا :: متجه <T> 3 | في: & [T] 4 خارج: Vec <T> |
واصف الملف | android :: base :: unique_fd | غير متاح | الموثق :: parcel :: ParcelFileDescriptor |
ParcelFileDescriptor | android :: os :: ParcelFileDescriptor | ndk :: ScopedFileDescriptor | الموثق :: parcel :: ParcelFileDescriptor |
نوع الواجهة (T) | android :: sp <T> | الأمراض المنقولة جنسيا :: shared_ptr <T> | الموثق :: قوي |
نوع لا يتجزأ (T) | تي | تي | تي |
نوع الاتحاد (T) 5 | تي | تي | تي |
T [N] 6 | الأمراض المنقولة جنسيا :: مجموعة <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
أو لا يتجزأ. في Android 13 أو أعلى ، يمكن أن يكون T
أي نوع غير بدائي باستثناء المصفوفات.
4. يتم تمرير الأنواع بشكل مختلف لرمز Rust اعتمادًا على ما إذا كانت مدخلات (وسيطة) ، أو مخرجات (قيمة تم إرجاعها).
5. أنواع الاتحاد مدعومة في Android 12 والإصدارات الأحدث.
6. في Android 13 أو أعلى ، يتم دعم المصفوفات ذات الحجم الثابت. يمكن أن تحتوي المصفوفات ذات الحجم الثابت على أبعاد متعددة (على سبيل المثال int[3][4]
). في الواجهة الخلفية لـ Java ، يتم تمثيل المصفوفات ذات الحجم الثابت كأنواع مصفوفة.
الاتجاهية (الداخل / الخارج / الداخل)
عند تحديد أنواع الوسيطات الخاصة بالدوال ، يمكنك تحديدها كما in
الداخل أو out
أو inout
. يتحكم هذا في تحديد معلومات الاتجاه التي يتم تمريرها لمكالمة IPC. in
هو الاتجاه الافتراضي ، ويشير إلى أن البيانات يتم تمريرها من المتصل إلى المستدعي. out
يعني أن البيانات يتم تمريرها من المستدعى إلى المتصل. inout
هو مزيج من كلاهما. ومع ذلك ، يوصي فريق Android بتجنب استخدام محدد الوسيطة inout
. إذا كنت تستخدم inout
مع واجهة ذات إصدار ومستدعي أقدم ، فسيتم إعادة تعيين الحقول الإضافية الموجودة فقط في المتصل إلى قيمها الافتراضية. فيما يتعلق بالصدأ ، يتلقى نوع inout
العادي &mut Vec<T>
، ويستقبل نوع القائمة inout
&mut Vec<T>
.
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 .
الطرود المخصصة
في الخلفيات C ++ و Java في نظام البناء الأساسي ، يمكنك إعلان جزء لا يتجزأ يتم تنفيذه يدويًا في الخلفية المستهدفة (في C ++ أو في Java).
package my.package;
parcelable Foo;
أو مع إعلان رأس C ++:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
بعد ذلك ، يمكنك استخدام هذا الجزء القابل للتجزئة كنوع في ملفات AIDL ، لكن لن يتم إنشاؤه بواسطة AIDL.
الصدأ لا يدعم الطرود المخصصة.
قيم افتراضية
يمكن أن تعلن العناصر المركبة الهيكلية عن القيم الافتراضية لكل حقل للأولويات والسلسلة String
من هذه الأنواع.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
في الواجهة الخلفية لـ Java عندما تكون القيم الافتراضية مفقودة ، تتم تهيئة الحقول كقيم صفرية للأنواع الأولية null
للأنواع غير الأولية.
في الخلفيات الأخرى ، تتم تهيئة الحقول بقيم افتراضية تم تهيئتها عندما لا يتم تحديد القيم الافتراضية. على سبيل المثال ، في الواجهة الخلفية لـ C ++ ، تتم تهيئة حقول String
كسلسلة فارغة ويتم تهيئة حقول List<T>
vector<T>
. يتم تهيئة الحقول @nullable
كحقول ذات قيمة خالية.
معالجة الأخطاء
يوفر نظام التشغيل Android أنواع أخطاء مضمنة للخدمات لاستخدامها عند الإبلاغ عن الأخطاء. يتم استخدامها بواسطة Binder ويمكن استخدامها من قبل أي خدمات تقوم بتنفيذ واجهة Binder. تم توثيق استخدامها جيدًا في تعريف 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
جنبًا إلى جنب مع الخطأ المحدد من قبل المستخدم.
يمكن العثور على أنواع الأخطاء المضمنة في الملفات التالية:
الخلفية | تعريف |
---|---|
جافا | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
الصدأ | 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;
على الرغم من أنه يمكنك استيراد نوع متداخل في Java ، إلا أنه في الواجهات الخلفية 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(())
}
}
التسجيل والحصول على الخدمات
عادة ما يتم تسجيل الخدمات في النظام الأساسي Android في عملية مدير servicemanager
. بالإضافة إلى واجهات برمجة التطبيقات أدناه ، تتحقق بعض واجهات برمجة التطبيقات من الخدمة (مما يعني أنها تعود على الفور إذا لم تكن الخدمة متاحة). تحقق من واجهة مدير servicemanager
المقابلة للحصول على التفاصيل الدقيقة. لا يمكن إجراء هذه العمليات إلا عند التجميع باستخدام نظام Android الأساسي.
في جافا:
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
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(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(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()
}
ربط الموت
يمكنك طلب الحصول على إشعار عند وفاة خدمة استضافة الموثق. يمكن أن يساعد ذلك في تجنب تسريب وكلاء رد الاتصال أو المساعدة في استعادة الأخطاء. قم بإجراء هذه المكالمات على كائنات وكيل الموثق.
- في 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. عندما تكون خارج سياق معاملة Binder ، تقوم هذه الوظائف بإرجاع 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();
تقارير الأخطاء وتصحيح أخطاء API للخدمات
عند تشغيل تقارير الأخطاء (على سبيل المثال ، مع adb bugreport
) ، فإنها تجمع المعلومات من جميع أنحاء النظام للمساعدة في تصحيح المشكلات المختلفة. For AIDL services, bugreports use the binary dumpsys
on all services registered with the service manager to dump their information into the bugreport. You can also use dumpsys
on the commandline to get information from a service with dumpsys SERVICE [ARGS]
. In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService
. You can also use dumpsys --pid SERVICE
to get the PID of a service while debugging.
To add custom output to your service, you can override the dump
method in your server object like you are implementing any other IPC method defined in an AIDL file. When doing this, you should restrict dumping to the app permission android.permission.DUMP
or restrict dumping to specific UIDs.
In the Java backend:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
In the CPP backend:
status_t dump(int, const android::android::Vector<android::String16>&) override;
In the NDK backend:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Dynamically getting interface descriptor
The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.
In Java, you can get the interface descriptor with code such as:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
In the CPP backend:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
The NDK and Rust backends don't support this functionality.
Statically getting interface descriptor
Sometimes (such as when registering @VintfStability
services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:
import my.package.IFoo;
... IFoo.DESCRIPTOR
In the CPP backend:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
In the NDK backend (notice the extra aidl
namespace):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
In the Rust backend:
aidl::my::package::BnFoo::get_descriptor()
Enum Range
In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java currently.
For an enum MyEnum
defined in AIDL, iteration is provided as follows.
In the CPP backend:
::android::enum_range<MyEnum>()
In the NDK backend:
::ndk::enum_range<MyEnum>()
In the Rust backend:
MyEnum::enum_range()
Thread management
Every instance of libbinder
in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception to this is when vendor code might load another copy of libbinder
to talk to /dev/vndbinder
. Since this is on a separate binder node, the threadpool isn't shared.
For the Java backend, the threadpool can only increase in size (since it is already started):
BinderInternal.setMaxThreads(<new larger value>);
For the CPP backend, the following operations are available:
// 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();
Similarly, in the NDK backend:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
In the Rust backend:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();
Reserved Names
C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that matching a reserved name might result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the "raw identifier" syntax, accessible using the r#
prefix.
We recommend that you avoid using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.
If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible; you may need to update your code to continue building, but any already built programs will continue to interoperate.
Names to avoid: * C++ Keywords * Java Keywords * Rust Keywords