تعد الواجهة الخلفية لـ AIDL هدفًا لإنشاء كود كعب الروتين. عند استخدام ملفات AIDL، فإنك تستخدمها دائمًا بلغة معينة مع وقت تشغيل محدد. اعتمادًا على السياق، يجب عليك استخدام واجهات AIDL الخلفية المختلفة.
لدى AIDL الواجهات الخلفية التالية:
الخلفية | لغة | سطح واجهة برمجة التطبيقات | بناء الأنظمة |
---|---|---|---|
جافا | جافا | SDK/SystemApi (مستقر*) | الجميع |
NDK | سي ++ | libbinder_ndk (مستقر*) | aidl_interface |
تكلفة المكالمة الهاتفية | سي ++ | ليبندر (غير مستقر) | الجميع |
الصدأ | الصدأ | libbinder_rs (غير مستقر) | aidl_interface |
- تعتبر أسطح واجهات برمجة التطبيقات هذه مستقرة، ولكن العديد من واجهات برمجة التطبيقات، مثل تلك الخاصة بإدارة الخدمة، محجوزة لاستخدام النظام الأساسي الداخلي وغير متاحة للتطبيقات. لمزيد من المعلومات حول كيفية استخدام AIDL في التطبيقات، راجع وثائق المطور .
- تم تقديم الواجهة الخلفية Rust في 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:
لاحظ أن الواجهة الخلفية لـ Rust مخصصة للاستخدام مع Rust فقط. يتم التعامل مع وحدات rust_
بشكل مختلف حيث لم يتم تحديد ملفات AIDL كملفات مصدر. بدلاً من ذلك، تنتج وحدة aidl_interface
rustlib
يسمى <aidl_interface name>-rust
والذي يمكن ربطه به. لمزيد من التفاصيل، راجع مثال Rust AIDL .
aidl_interface
يجب أن تكون الأنواع المستخدمة مع نظام البناء هذا منظمة. لكي يتم تنظيمها، يجب أن تحتوي القطع القابلة للتجزئة على حقول مباشرة وألا تكون عبارة عن إعلانات لأنواع محددة مباشرة في اللغات المستهدفة. للتعرف على كيفية توافق AIDL المنظم مع AIDL المستقر، راجع AIDL المنظم مقابل المستقر .
أنواع
يمكنك اعتبار المترجم aidl
بمثابة تطبيق مرجعي للأنواع. عندما تقوم بإنشاء واجهة، قم باستدعاء aidl --lang=<backend> ...
لرؤية ملف الواجهة الناتج. عند استخدام وحدة aidl_interface
، يمكنك عرض المخرجات في out/soong/.intermediates/<path to module>/
.
نوع جافا/AIDL | النوع سي++ | نوع إن دي كيه | نوع الصدأ |
---|---|---|---|
منطقية | منطقي | منطقي | منطقي |
بايت | int8_t | int8_t | i8 |
شار | char16_t | char16_t | u16 |
كثافة العمليات | int32_t | int32_t | i32 |
طويل | int64_t | int64_t | i64 |
يطفو | يطفو | يطفو | f32 |
مزدوج | مزدوج | مزدوج | f64 |
خيط | الروبوت::سلسلة16 | الأمراض المنقولة جنسيا::سلسلة | خيط |
android.os.Parcelable | الروبوت::لا يتجزأ | لا يوجد | لا يوجد |
IBinder | الروبوت::IBinder | ndk::SpAIBinder | الموثق::SpIBinder |
ت[] | الأمراض المنقولة جنسيا::ناقل<T> | الأمراض المنقولة جنسيا::ناقل<T> | في: &[ر] خارج: فيك <T> |
بايت[] | الأمراض المنقولة جنسيا::ناقل<uint8_t> | الأمراض المنقولة جنسيا::vector<int8_t> 1 | في: &[u8] الخروج: فيك <u8> |
القائمة<T> | الأمراض المنقولة جنسيا::ناقل<T> 2 | الأمراض المنقولة جنسيا::ناقل<T> 3 | في: &[ت] 4 خارج: فيك <T> |
واصف الملف | android::base::unique_fd | لا يوجد | Binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | Binder::parcel::ParcelFileDescriptor |
نوع الواجهة (T) | الروبوت::SP<T> | الأمراض المنقولة جنسيا::shared_ptr<T> | الموثق::قوي |
نوع الطرود (T) | ت | ت | ت |
نوع الاتحاد (T) 5 | ت | ت | ت |
ت[ن] 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 .
الطرود المخصصة
الطرد المخصص هو جزء قابل للطرد يتم تنفيذه يدويًا في الواجهة الخلفية المستهدفة. استخدم الطرود المخصصة فقط عندما تحاول إضافة دعم للغات أخرى لقطعة مخصصة موجودة والتي لا يمكن تغييرها.
من أجل الإعلان عن تخصيص قابل للتوزيع حتى يعرف AIDL عنه، يبدو إعلان AIDL القابل للتوزيع كما يلي:
package my.pack.age;
parcelable Foo;
افتراضيًا، يُعلن هذا عن Java القابل للتجزئة حيث my.pack.age.Foo
عبارة عن فئة Java تنفذ واجهة Parcelable
.
للحصول على إعلان عن واجهة خلفية CPP مخصصة قابلة للتوزيع في AIDL، استخدم cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
يبدو تطبيق C++ في my/pack/age/Foo.h
كما يلي:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
للإعلان عن NDK مخصص قابل للتوزيع في AIDL، استخدم ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
يبدو تنفيذ NDK في android/package/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);
};
ثم يمكنك استخدام هذا الطرد كنوع في ملفات 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 أنواع أخطاء مضمنة للخدمات لاستخدامها عند الإبلاغ عن الأخطاء. يتم استخدامها بواسطة الموثق ويمكن استخدامها بواسطة أي خدمة تنفذ واجهة الموثق. إن استخدامها موثق جيدًا في تعريف AIDL ولا يتطلب أي حالة أو نوع إرجاع محدد من قبل المستخدم.
معلمات الإخراج مع الأخطاء
عندما تقوم دالة AIDL بالإبلاغ عن خطأ، فقد لا تقوم الوظيفة بتهيئة معلمات الإخراج أو تعديلها. على وجه التحديد، قد يتم تعديل معلمات الإخراج إذا حدث الخطأ أثناء إلغاء التوزيع بدلاً من حدوثه أثناء معالجة المعاملة نفسها. بشكل عام، عند الحصول على خطأ من دالة AIDL، يجب اعتبار جميع معلمات inout
out
بالإضافة إلى قيمة الإرجاع (التي تعمل كمعلمة out
في بعض الواجهات الخلفية) في حالة غير محددة.
ما هي قيم الخطأ التي يجب استخدامها
يمكن استخدام العديد من قيم الخطأ المضمنة في أي واجهات AIDL، ولكن يتم التعامل مع بعضها بطريقة خاصة. على سبيل المثال، يمكن استخدام EX_UNSUPPORTED_OPERATION
و EX_ILLEGAL_ARGUMENT
عندما يصفان حالة الخطأ، ولكن يجب عدم استخدام EX_TRANSACTION_FAILED
لأنه يتم معاملته بشكل خاص بواسطة البنية الأساسية الأساسية. تحقق من التعريفات المحددة للواجهة الخلفية للحصول على مزيد من المعلومات حول هذه القيم المضمنة.
إذا كانت واجهة AIDL تتطلب قيم أخطاء إضافية لا تغطيها أنواع الأخطاء المضمنة، فيمكنهم استخدام الخطأ المضمن الخاص بالخدمة والذي يسمح بتضمين قيمة خطأ خاصة بالخدمة يحددها المستخدم . عادةً ما يتم تعريف هذه الأخطاء الخاصة بالخدمة في واجهة AIDL على أنها enum
const int
أو int
مدعوم ولا يتم تحليلها بواسطة 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
مع الخطأ المحدد من قبل المستخدم.
يمكن العثور على أنواع الأخطاء المضمنة في الملفات التالية:
الخلفية | تعريف |
---|---|
جافا | android/os/Parcel.java |
تكلفة المكالمة الهاتفية | 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;
}
في الواجهة الخلفية الصدأ:
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")));
في الواجهة الخلفية الصدأ:
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()
}
الارتباط بالموت
يمكنك طلب الحصول على إشعار عند وفاة خدمة تستضيف رابطًا. يمكن أن يساعد هذا في تجنب تسرب وكلاء رد الاتصال أو المساعدة في استرداد الأخطاء. قم بإجراء هذه الاستدعاءات على كائنات وكيل Binder.
- في Java، استخدم
android.os.IBinder::linkToDeath
. - في الواجهة الخلفية لـ CPP، استخدم
android::IBinder::linkToDeath
. - في الواجهة الخلفية لـ NDK، استخدم
AIBinder_linkToDeath
. - في الواجهة الخلفية لـ Rust، أنشئ كائن
DeathRecipient
، ثم اتصل بـmy_binder.link_to_death(&mut my_death_recipient)
. لاحظ أنه نظرًا لأنDeathRecipient
يمتلك رد الاتصال، فيجب عليك إبقاء هذا الكائن نشطًا طالما أنك تريد تلقي الإشعارات.
معلومات المتصل
عند تلقي مكالمة kernel Binder، تتوفر معلومات المتصل في العديد من واجهات برمجة التطبيقات. يشير PID (أو معرف العملية) إلى معرف عملية Linux للعملية التي ترسل المعاملة. يشير UID (أو معرف المستخدم) إلى معرف مستخدم Linux. عند تلقي مكالمة أحادية الاتجاه، يكون معرف PID الخاص بالاستدعاء هو 0. وعندما تكون هذه الوظائف خارج سياق معاملة الموثق، فإنها ترجع معرف PID وUID للعملية الحالية.
في الواجهة الخلفية لجافا:
... = 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
)، فإنها تجمع المعلومات من جميع أنحاء النظام للمساعدة في تصحيح المشكلات المختلفة. بالنسبة لخدمات AIDL، تستخدم تقارير الأخطاء ملف dumpsys
الثنائي على كافة الخدمات المسجلة لدى مدير الخدمة لتفريغ معلوماتها في تقرير الأخطاء. يمكنك أيضًا استخدام dumpsys
في سطر الأوامر للحصول على معلومات من خدمة ذات dumpsys SERVICE [ARGS]
. في الواجهات الخلفية لـ C++ وJava، يمكنك التحكم في الترتيب الذي يتم به تفريغ الخدمات باستخدام وسيطات إضافية لـ addService
. يمكنك أيضًا استخدام dumpsys --pid SERVICE
للحصول على معرف المنتج (PID) الخاص بالخدمة أثناء تصحيح الأخطاء.
لإضافة مخرجات مخصصة إلى خدمتك، يمكنك تجاوز طريقة dump
في كائن الخادم الخاص بك كما لو كنت تقوم بتنفيذ أي طريقة IPC أخرى محددة في ملف AIDL. عند القيام بذلك، يجب عليك تقييد الإلقاء على إذن التطبيق android.permission.DUMP
أو تقييد الإلقاء على معرفات UID محددة.
في الواجهة الخلفية لجافا:
@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
في الواجهة الخلفية الصدأ:
aidl::my::package::BnFoo::get_descriptor()
نطاق التعداد
في الواجهات الخلفية الأصلية، يمكنك تكرار القيم المحتملة التي يمكن أن يأخذها التعداد. نظرًا لاعتبارات حجم الكود، فإن هذا غير مدعوم في Java حاليًا.
بالنسبة للتعداد MyEnum
المحدد في AIDL، يتم توفير التكرار على النحو التالي.
في الواجهة الخلفية لـ CPP:
::android::enum_range<MyEnum>()
في الواجهة الخلفية لـ NDK:
::ndk::enum_range<MyEnum>()
في الواجهة الخلفية الصدأ:
MyEnum::enum_values()
إدارة الموضوع
كل مثيل لـ libbinder
في العملية يحافظ على مجمع ترابط واحد. بالنسبة لمعظم حالات الاستخدام، يجب أن يكون هذا مجمع ترابط واحد فقط، مشتركًا عبر جميع الواجهات الخلفية. الاستثناء الوحيد لذلك هو عندما قد يقوم كود البائع بتحميل نسخة أخرى من libbinder
للتحدث إلى /dev/vndbinder
. وبما أن هذا موجود على عقدة رابط منفصلة، فلن تتم مشاركة مجموعة مؤشرات الترابط.
بالنسبة للواجهة الخلفية لـ Java، يمكن أن يزيد حجم Threadpool فقط (نظرًا لأنه بدأ بالفعل):
BinderInternal.setMaxThreads(<new larger value>);
بالنسبة للواجهة الخلفية لـ CPP، تتوفر العمليات التالية:
// set max threadpool count (default is 15)
status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
// create threadpool
ProcessState::self()->startThreadPool();
// add current thread to threadpool (adds thread to max thread count)
IPCThreadState::self()->joinThreadPool();
وبالمثل، في الواجهة الخلفية NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
في الواجهة الخلفية الصدأ:
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 * الكلمات الرئيسية الصدأ