تشبه لغة تعريف واجهة Android (AIDL) غيرها من لغات تعريف الواجهات (IDL): فهي تتيح لك تحديد واجهة البرمجة التي يتفق عليها العميل والخدمة من أجل التواصل مع بعضهما البعض باستخدام التواصل بين العمليات (IPC).
على نظام التشغيل Android، لا يمكن لأحد العمليات عادةً الوصول إلى ذاكرة عملية أخرى. للتواصل، يجب تقسيم الكائنات إلى عناصر أساسية يمكن لنظام التشغيل فهمها وتنظيم الكائنات على مستوى هذه الحدود نيابةً عنك. إنّ كتابة الرمز البرمجي للقيام بترتيب الحِزم هذه عملية شاقة، لذا يعالجها Android نيابةً عنك باستخدام AIDL.
ملاحظة: لا تكون واجهة برمجة التطبيقات AIDL ضرورية إلا إذا كنت تسمح للعملاء من
تطبيقات مختلفة بالوصول إلى خدمتك لإجراء الاتصالات بين العمليات (IPC) وتريد معالجة تعدد المواضيع في
خدمتك. إذا لم تكن بحاجة إلى تنفيذ تنسيق IPC متزامن على مستوى
تطبيقات مختلفة، يمكنك إنشاء واجهتك من خلال تنفيذ
Binder.
إذا كنت تريد تنفيذ تنسيق IPC ولكن لا تحتاج إلى معالجة تعدد المواضيع،
نفِّذ واجهتك باستخدام Messenger.
بغض النظر عن ذلك، احرص على فهم الخدمات المرتبطة قبل
تنفيذ واجهة برمجة تطبيقات AIDL.
قبل بدء تصميم واجهة AIDL، يُرجى العِلم أنّ طلبات البيانات إلى واجهة AIDL هي طلبات وظائف مباشرة. لا تفترض أيّ شيء بشأن سلسلة المحادثات التي يتم فيها إجراء المكالمة. يختلف ما يحدث بناءً على ما إذا كان الطلب واردًا من سلسلة محادثات في العملية المحلية أو العملية البعيدة:
- يتم تنفيذ المكالمات التي يتم إجراؤها من العملية المحلية في سلسلة المحادثات نفسها التي تجري المكالمة. إذا كان
هذا هو سلسلة التعليمات الرئيسية لواجهة المستخدم، سيستمر تنفيذ سلسلة التعليمات هذه في واجهة AIDL. إذا كان هناك سلسلاً رسائل أخرى، ستكون هي التي تنفِّذ الرمز البرمجي في الخدمة. وبالتالي، إذا كانت سلاسل الرسائل
المحلّية فقط هي التي تصل إلى الخدمة، يمكنك التحكّم بشكل كامل في سلاسل الرسائل التي يتم تنفيذها فيها. ولكن
إذا كان الأمر كذلك، لا تستخدِم AIDL على الإطلاق، بل أنشئ
الواجهة من خلال تنفيذ
Binder. - يتم إرسال المكالمات الواردة من عملية عن بُعد من تجمع مؤشرات تسلسل تحكم تديره المنصة داخل عمليتك. يجب الاستعداد للمكالمات الواردة من سلاسل محادثات غير معروفة، مع مكالمات متعددة تتم في الوقت نفسه. بعبارة أخرى، يجب أن يكون تنفيذ واجهة AIDL متوافقًا تمامًا مع مؤشرات الترابط. إنّ المكالمات التي يتم إجراؤها من سلسلة محادثات واحدة على العنصر البعيد نفسه تصل بالترتيب على جانب المُستلِم.
- تعمل الكلمة الرئيسية
onewayعلى تعديل سلوك المكالمات البعيدة. عند استخدام هذا الخيار، لن يتم حظْر المكالمة عن بُعد. ويُرسِل بيانات المعاملة ويعود على الفور. يتلقّى تنفيذ الواجهة هذا الطلب في النهاية كمكالمة عادية من تجمع سلاسل رسائلBinderكمكالمة عادية عن بُعد. في حال استخدامonewayمع مكالمة محلية، لن يكون هناك أي تأثير، وستظل المكالمة متزامنة.
تحديد واجهة AIDL
حدِّد واجهة AIDL في ملف .aidl باستخدام بنية لغة برمجة Java
، ثم احفظها في رمز المصدر، في الدليل src/، لكلٍّ من
التطبيق الذي يستضيف الخدمة وأي تطبيق آخر يرتبط بالخدمة.
عند إنشاء كل تطبيق يحتوي على ملف .aidl، تنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android
واجهة IBinder استنادًا إلى ملف .aidl وتحفظها في
دليل gen/ الخاص بالمشروع. يجب أن تنفّذ الخدمة واجهة IBinder
حسب الاقتضاء. يمكن بعد ذلك لتطبيقات العميل الربط بالخدمة واستدعاء الطرق من
IBinder لتنفيذ تقنية IPC.
لإنشاء خدمة محدودة باستخدام AIDL، اتّبِع الخطوات التالية الموضّحة في الأقسام التالية:
- إنشاء ملف
.aidlيحدِّد هذا الملف واجهة برمجة التطبيقات باستخدام توقيعات الطرق.
- تنفيذ الواجهة
تُنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android واجهة بلغة البرمجة Java استنادًا إلى ملف
.aidl. تحتوي هذه الواجهة على فئة مجردة داخلية باسمStubتمتدBinderوتنفِّذ طرقًا من واجهة AIDL. يجب توسيع نطاق فئةStubوتنفيذ الطرق. - إتاحة الواجهة للعملاء
تحذير: يجب أن تظل أي تغييرات تجريها على واجهة AIDL بعد
إصدارك الأول متوافقة مع الإصدارات القديمة لتجنُّب تعطيل التطبيقات الأخرى
التي تستخدم خدمتك. وهذا يعني أنّه يجب إبقاء الواجهة الأصلية متوافقة مع التطبيقات الأخرى
كي تتمكّن من الوصول إلى واجهة خدمتك، وذلك لأنّه يجب نسخ ملف .aidl إلى التطبيقات الأخرى.
إنشاء ملف .aidl
يستخدم AIDL بنية بسيطة تتيح لك تعريف واجهة باستخدام طريقة واحدة أو أكثر يمكنها تلقّي المَعلمات وعرض القيم. يمكن أن تكون المَعلمات والقيم المعروضة من أي نوع، حتى واجهات أخرى تم إنشاؤها باستخدام واجهة برمجة التطبيقات لنظام التشغيل Android (AIDL).
يجب إنشاء ملف .aidl باستخدام لغة البرمجة Java. يجب أن يحدِّد كل ملف .aidl
واجهة واحدة ولا يتطلّب سوى تعريف الواجهة وتوقيعات methods.
تتيح واجهة برمجة التطبيقات AIDL تلقائيًا أنواع البيانات التالية:
- جميع الأنواع الأساسية، باستثناء
short، في لغة برمجة Java (مثلintlongوcharوbooleanوما إلى ذلك) - صفائف من أي أنواع، مثل
int[]أوMyParcelable[] StringCharSequenceListيجب أن تكون جميع العناصر في
Listأحد أنواع البيانات المتوافقة في هذه القائمة أو أحد الواجهات أو العناصر القابلة للنقل التي تم إنشاؤها باستخدام AIDL والتي تذكرها. يمكن استخدامListاختياريًا كفئة نوع مُعلَمة، مثلList<String>. إنّ الفئة المحددة الفعلية التي يتلقّاها الجانب الآخر هي دائمًاArrayList، على الرغم من أنّه يتم إنشاء المحاولة لاستخدام واجهةList.Mapيجب أن تكون جميع العناصر في
Mapأحد أنواع البيانات المتوافقة في هذه القائمة أو أحد الواجهات أو العناصر التي تم إنشاؤها باستخدام واجهة برمجة التطبيقات لتحديد البيانات (AIDL) والتي تذكرها. لا تتوفّر خرائط الأنواع المُحدَّدة، مثل تلك الخاصة بالنموذجMap<String,Integer>. إنّ الفئة المحددة التي يتلقّاها الجانب الآخر تكون دائمًاHashMap، على الرغم من أنّ الطريقة يتم إنشاؤها لاستخدام واجهةMap. ننصحك باستخدامBundleكبديل لـMap.
يجب تضمين بيان import لكل نوع إضافي غير مُدرَج سابقًا،
حتى إذا تم تحديده في الحزمة نفسها التي تتضمّن واجهتك.
عند تحديد واجهة الخدمة، يُرجى مراعاة ما يلي:
- يمكن أن تأخذ الطرق مَعلمة واحدة أو أكثر أو لا تأخذ أي مَعلمات، ويمكن أن تُرجع قيمة أو قيمة فارغة.
- تتطلّب جميع المَعلمات غير الأساسية علامة توجيهية تشير إلى اتجاه البيانات:
inأوoutأوinout(اطّلِع على المثال أدناه).تكون الواجهات الأساسية و
StringوIBinderوتلك التي يتم إنشاؤها باستخدام لغة AIDL هيinتلقائيًا ولا يمكن أن تكون غير ذلك.ملاحظة: يجب حصر الاتجاه بما هو مطلوب حقًا، لأنّ ترتيب المَعلمات عملية باهظة التكلفة.
- يتم تضمين كل تعليقات الرموز البرمجية المضمّنة في ملف
.aidlفي واجهةIBinderالمُنشأة باستثناء التعليقات قبل عبارات الاستيراد والتعبئة. - يمكن تعريف ثوابت السلاسل والأرقام الصحيحة في واجهة AIDL، مثل
const int VERSION = 1;. - يتم إرسال طلبات الطريقة من خلال
transact()رمز يستند عادةً إلى فهرس طريقة في الواجهة. ولأنّ ذلك يجعل استخدام الإصدارات أمرًا صعبًا، يمكنك إسناد رمز المعاملة يدويًا إلى طريقة:void method() = 10;. - يجب إضافة تعليقات توضيحية إلى الوسائط التي يمكن أن تكون فارغة وأنواع النتائج باستخدام
@nullable.
في ما يلي مثال على ملف .aidl:
// IRemoteService.aidl
package com.example.android;
// Declare any non-default types here with import statements.
/** Example service interface */
interface IRemoteService {
/** Request the process ID of this service. */
int getPid();
/** Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
احفظ ملف .aidl في دليل src/ الخاص بمشروعك. عند
إنشاء تطبيقك، تنشئ أدوات حزمة تطوير البرامج (SDK) ملف واجهة IBinder في gen/ دليل
مشروعك. يتطابق اسم الملف الذي تم إنشاؤه مع اسم ملف .aidl، ولكن
مع إضافة .java. على سبيل المثال، يؤدي IRemoteService.aidl إلى IRemoteService.java.
إذا كنت تستخدم "استوديو Android"، ينشئ الإنشاء المتزايد فئة الربط على الفور تقريبًا.
إذا كنت لا تستخدم "استوديو Android"، تنشئ أداة Gradle فئة الربط في المرة التالية التي تُنشئ فيها
تطبيقك. يمكنك إنشاء مشروعك باستخدام gradle assembleDebug
أو gradle assembleRelease بعد الانتهاء من كتابة ملف .aidl،
لكي يتمكّن الرمز من الربط بالصف الذي تم إنشاؤه.
تنفيذ الواجهة
عند إنشاء تطبيقك، تنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android ملف واجهة .java
يحمل اسم ملف .aidl. تتضمّن الواجهة التي تم إنشاؤها فئة فرعية باسم Stub
وهي عبارة عن تنفيذ مجرد لواجهة الأصل، مثل YourInterface.Stub، وتعلن عن جميع الطرق من ملف .aidl.
ملاحظة: Stub
يحدِّد أيضًا بعض طرق المساعدة، وأهمها asInterface()، التي تأخذ IBinder، وعادةً ما يتم تمريرها إلى طريقة الاستدعاء onServiceConnected() للعميل، ويؤدي
ذلك إلى عرض مثيل لواجهة النموذج. لمزيد من التفاصيل حول كيفية إجراء عملية التحويل هذه، اطّلِع على القسم استدعاء أسلوب ملف تعريف الارتباط بين التطبيقات (IPC).
لتنفيذ الواجهة التي تم إنشاؤها من .aidl، يمكنك توسيع واجهة Binder
المُنشأة، مثل YourInterface.Stub، وتنفيذ الطرق
المكتسَبة من ملف .aidl.
في ما يلي مثال على تنفيذ واجهة باسم IRemoteService، تم تحديدها من خلال المثال السابق
IRemoteService.aidl، باستخدام مثيل مجهول:
Kotlin
private val binder = object : IRemoteService.Stub() { override fun getPid(): Int = Process.myPid() override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } }
Java
private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } };
أصبح الآن binder مثيلًا لفئة Stub (Binder)،
الذي يحدّد واجهة IPC للخدمة. في الخطوة التالية، يتم عرض هذه النسخة أمام
العملاء حتى يتمكّنوا من التفاعل مع الخدمة.
يجب الانتباه إلى بعض القواعد عند تنفيذ واجهة AIDL:
- لا يمكن ضمان تنفيذ المكالمات الواردة في سلسلة التعليمات الرئيسية، لذا عليك التفكير في استخدام ميزة "توفُّر عدّة سلاسل تعليمات" من البداية وإنشاء خدمتك بشكل صحيح لتكون آمنة من حيث سلاسل التعليمات.
- تكون طلبات IPC متزامنة تلقائيًا. إذا كنت تعلم أنّ الخدمة تستغرق أكثر من بضع ملي ثوانٍ لإكمال طلب، لا تطلبها من سلسلة المهام الرئيسية للنشاط. وقد يؤدي ذلك إلى تعليق التطبيق، ما يؤدي إلى ظهور مربّع حوار "التطبيق لا يستجيب" على Android. يمكنك الاتصال به من سلسلة محادثات منفصلة في العميل.
- لا يتم إرسال سوى أنواع الاستثناءات المدرَجة ضمن المستندات المرجعية لمحاولة
Parcel.writeException()إلى المتصل.
إتاحة الواجهة للعملاء
بعد تنفيذ واجهة خدمتك، عليك إتاحتها
للعملاء حتى يتمكّنوا من الربط بها. لعرض الواجهة
لخدمتك، يمكنك توسيع Service وتنفيذ onBind() لعرض مثيل من صفك الذي ينفِّذ
Stub الذي تم إنشاؤه، كما هو موضّح في القسم السابق. في ما يلي مثال على
خدمة تعرض واجهة IRemoteService للعملاء.
Kotlin
class RemoteService : Service() { override fun onCreate() { super.onCreate() } override fun onBind(intent: Intent): IBinder { // Return the interface. return binder } private val binder = object : IRemoteService.Stub() { override fun getPid(): Int { return Process.myPid() } override fun basicTypes( anInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float, aDouble: Double, aString: String ) { // Does nothing. } } }
Java
public class RemoteService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Return the interface. return binder; } private final IRemoteService.Stub binder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing. } }; }
الآن، عندما يتصل عميل، مثل نشاط، بخدمة bindService() للربط بهذه الخدمة، يتلقّى الإجراء المُعاد الاتصال به onServiceConnected() للعميل مثيل binder الذي تم إرجاعه من خلال طريقة onBind()
للخدمة.
يجب أن يتمكن العميل أيضًا من الوصول إلى فئة الواجهة. إذا كان العميل والخدمة في
تطبيقات منفصلة، يجب أن يحتوي تطبيق العميل على نسخة من ملف .aidl
في دليل src/، الذي ينشئ واجهة android.os.Binder
، ما يتيح للعميل الوصول إلى طرق AIDL.
عندما يتلقّى العميل IBinder
في دالة ردّ الاتصال onServiceConnected()، يجب أن يُطلِب
YourServiceInterface.Stub.asInterface(service) لتحويل المَعلمة
المعروضة إلى نوع YourServiceInterface:
Kotlin
var iRemoteService: IRemoteService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service) } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "Service has unexpectedly disconnected") iRemoteService = null } }
Java
IRemoteService iRemoteService; private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established. public void onServiceConnected(ComponentName className, IBinder service) { // Following the preceding example for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service. iRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly. public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); iRemoteService = null; } };
لمزيد من نماذج الرموز البرمجية، يمكنك الاطّلاع على فئة
RemoteService.java في
ApiDemos.
تمرير العناصر عبر واجهة برمجة التطبيقات لنظام التشغيل
في الإصدار 10 من نظام التشغيل Android (المستوى 29 أو مستوى أعلى لواجهة برمجة التطبيقات)، يمكنك تحديد
عناصرParcelable مباشرةً في
AIDL. يمكن أيضًا استخدام الأنواع المتوافقة كوسيطات لواجهة AIDL وأنواع Parcelable الأخرى. ويؤدي ذلك إلى تجنُّب العمل الإضافي المتعلق بكتابة رمز تجميع يدويًا وأحد فئات
المخصّصة. ومع ذلك، يؤدي ذلك أيضًا إلى إنشاء بنية فارغة. إذا أردت استخدام وظائف أخرى أو عناصر وصول مخصّصة، يمكنك تنفيذ Parcelable بدلاً من ذلك.
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
int left;
int top;
int right;
int bottom;
}
ينشئ مقتطف الرمز السابق تلقائيًا فئة Java تحتوي على الحقول الصحيحة left
top وright وbottom. يتم تنفيذ كل رمز ربط ذي صلة تلقائيًا، ويمكن استخدام العنصر مباشرةً بدون الحاجة إلى إضافة أي تنفيذ.
يمكنك أيضًا إرسال فئة مخصّصة من عملية إلى أخرى من خلال واجهة IPC. ومع ذلك،
تأكّد من توفّر رمز صفك للجانب الآخر من قناة IPC ويجب أن
يتوافق صفك مع واجهة Parcelable. من المهم إتاحة استخدام Parcelable
لأنّه يتيح لنظام Android تقسيم العناصر إلى عناصر أساسية يمكن تجميعها
في جميع العمليات.
لإنشاء فئة مخصّصة تتيح استخدام Parcelable، اتّبِع الخطوات التالية:
- اجعل فئة النموذج تنفّذ واجهة
Parcelable. - نفِّذ
writeToParcelالتي تأخذ الحالة الحالية للكائن وتكتبها فيParcel. - أضِف حقلًا ثابتًا باسم
CREATORإلى صفتك، وهو عنصر ينفذ واجهةParcelable.Creator. - أخيرًا، أنشئ ملف
.aidlيعرِض فئة parcelable، كما هو موضّح في ملفRect.aidlالتالي.إذا كنت تستخدم عملية إنشاء مخصّصة، لا تُضِف ملف
.aidlإلى عملية الإنشاء. لا يتم تجميع ملف.aidlهذا، تمامًا مثل ملف الرأس في لغة C.
يستخدم AIDL هذه الطرق والحقول في الرمز البرمجي الذي ينشئه لترتيب عناصرك وتحويلها إلى تنسيق قابل للقراءة.
على سبيل المثال، في ما يلي ملف Rect.aidl لإنشاء فئة Rect
قابلة للتقسيم:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
في ما يلي مثال على كيفية تنفيذ فئة Rect لبروتوكول
Parcelable.
Kotlin
import android.os.Parcel import android.os.Parcelable class Rect() : Parcelable { var left: Int = 0 var top: Int = 0 var right: Int = 0 var bottom: Int = 0 companion object CREATOR : Parcelable.Creator<Rect> { override fun createFromParcel(parcel: Parcel): Rect { return Rect(parcel) } override fun newArray(size: Int): Array<Rect?> { return Array(size) { null } } } private constructor(inParcel: Parcel) : this() { readFromParcel(inParcel) } override fun writeToParcel(outParcel: Parcel, flags: Int) { outParcel.writeInt(left) outParcel.writeInt(top) outParcel.writeInt(right) outParcel.writeInt(bottom) } private fun readFromParcel(inParcel: Parcel) { left = inParcel.readInt() top = inParcel.readInt() right = inParcel.readInt() bottom = inParcel.readInt() } override fun describeContents(): Int { return 0 } }
Java
import android.os.Parcel; import android.os.Parcelable; public final class Rect implements Parcelable { public int left; public int top; public int right; public int bottom; public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() { public Rect createFromParcel(Parcel in) { return new Rect(in); } public Rect[] newArray(int size) { return new Rect[size]; } }; public Rect() { } private Rect(Parcel in) { readFromParcel(in); } public void writeToParcel(Parcel out, int flags) { out.writeInt(left); out.writeInt(top); out.writeInt(right); out.writeInt(bottom); } public void readFromParcel(Parcel in) { left = in.readInt(); top = in.readInt(); right = in.readInt(); bottom = in.readInt(); } public int describeContents() { return 0; } }
إنّ عملية الترتيب في فئة Rect بسيطة. اطّلِع على Parcel methods الأخرى في Parcel للاطّلاع على الأنواع الأخرى من القيم التي يمكنك كتابتها في Parcel.
تحذير: تذكَّر الآثار الأمنية لتلقّي
البيانات من عمليات أخرى. في هذه الحالة، يقرأ Rect أربعة أرقام من Parcel، ولكن عليك التأكّد من أنّ هذه الأرقام ضمن النطاق المقبول
للقيم بغض النظر عمّا يحاول المتصل فعله. لمزيد من المعلومات حول كيفية الحفاظ على أمان تطبيقك من البرامج الضارة، اطّلِع على نصائح حول الأمان.
الطرق التي تحتوي على وسيطات حِزم تحتوي على عناصر Parcelable
إذا كانت إحدى الطرق تقبل عنصرBundle من المفترض أن يحتوي على
عناصر قابلة للتقسيم، تأكَّد من ضبط أداة تحميل الفئات لعنصر Bundle من خلال
استدعاء Bundle.setClassLoader(ClassLoader) قبل محاولة القراءة
من Bundle. بخلاف ذلك، ستواجه الخطأ ClassNotFoundException على الرغم من أنّ العنصر parcelable تم تحديده بشكل صحيح في تطبيقك.
على سبيل المثال، إليك نموذج ملف .aidl التالي:
// IRectInsideBundle.aidl package com.example.android; /** Example service interface */ interface IRectInsideBundle { /** Rect parcelable is stored in the bundle with key "rect". */ void saveRect(in Bundle bundle); }
ClassLoader
بوضوح في Bundle قبل قراءة Rect:
Kotlin
private val binder = object : IRectInsideBundle.Stub() { override fun saveRect(bundle: Bundle) { bundle.classLoader = classLoader val rect = bundle.getParcelable<Rect>("rect") process(rect) // Do more with the parcelable. } }
Java
private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() { public void saveRect(Bundle bundle){ bundle.setClassLoader(getClass().getClassLoader()); Rect rect = bundle.getParcelable("rect"); process(rect); // Do more with the parcelable. } };
استدعاء طريقة IPC
لاستدعاء واجهة عن بُعد محدّدة باستخدام AIDL، اتّبِع الخطوات التالية في صف الاتصال:
- أدرِج الملف
.aidlفي دليل المشروعsrc/. - عرِّف مثيلًا لواجهة
IBinderالتي يتم إنشاؤها استنادًا إلى ملف IDE. - نفِّذ
ServiceConnection. - اتصل بـ
Context.bindService()، وضَع عملية التنفيذServiceConnection. - في عملية تنفيذ
onServiceConnected()، تتلقّى مثيلIBinderيُسمىservice. استخدِم دالةYourInterfaceName.Stub.asInterface((IBinder)service)لتحويل المَعلمة المعروضة إلى نوعYourInterface. - استدعاء الطرق التي حدّدتها في واجهتك عليك دائمًا اعتراض
استثناءات
DeadObjectExceptionالتي يتم طرحها عند انقطاع الاتصال. عليك أيضًا اعتراض استثناءاتSecurityExceptionالتي يتم طرحها عندما تتضمّن العمليتان المشارِكتَان في طلب طريقة IPC تعريفات AIDL متضاربة. - لإيقاف الاتصال، يمكنك الاتصال بـ
Context.unbindService()باستخدام مثيل واجهتك.
يُرجى مراعاة النقاط التالية عند الاتصال بخدمة IPC:
- يتم احتساب عدد الإشارات إلى العناصر في جميع العمليات.
- يمكنك إرسال عناصر مجهولة كوسيطات للطريقة.
لمزيد من المعلومات عن الربط بخدمة، يُرجى الاطّلاع على نظرة عامة على الخدمات المرتبطة.
في ما يلي بعض نماذج الرموز البرمجية التي توضّح استدعاء خدمة تم إنشاؤها باستخدام واجهة برمجة التطبيقات AIDL، وهي مأخوذة من نموذج الخدمة البعيدة في مشروع ApiDemos.
Kotlin
private const val BUMP_MSG = 1 class Binding : Activity() { /** The primary interface you call on the service. */ private var mService: IRemoteService? = null /** Another interface you use on the service. */ internal var secondaryService: ISecondary? = null private lateinit var killButton: Button private lateinit var callbackText: TextView private lateinit var handler: InternalHandler private var isBound: Boolean = false /** * Class for interacting with the main interface of the service. */ private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service) killButton.isEnabled = true callbackText.text = "Attached." // We want to monitor the service for as long as we are // connected to it. try { mService?.registerCallback(mCallback) } catch (e: RemoteException) { // In this case, the service crashes before we can // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_connected, Toast.LENGTH_SHORT ).show() } override fun onServiceDisconnected(className: ComponentName) { // This is called when the connection with the service is // unexpectedly disconnected—that is, its process crashed. mService = null killButton.isEnabled = false callbackText.text = "Disconnected." // As part of the sample, tell the user what happened. Toast.makeText( this@Binding, R.string.remote_service_disconnected, Toast.LENGTH_SHORT ).show() } } /** * Class for interacting with the secondary interface of the service. */ private val secondaryConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // Connecting to a secondary interface is the same as any // other interface. secondaryService = ISecondary.Stub.asInterface(service) killButton.isEnabled = true } override fun onServiceDisconnected(className: ComponentName) { secondaryService = null killButton.isEnabled = false } } private val mBindListener = View.OnClickListener { // Establish a couple connections with the service, binding // by interface names. This lets other applications be // installed that replace the remote service by implementing // the same interface. val intent = Intent(this@Binding, RemoteService::class.java) intent.action = IRemoteService::class.java.name bindService(intent, mConnection, Context.BIND_AUTO_CREATE) intent.action = ISecondary::class.java.name bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE) isBound = true callbackText.text = "Binding." } private val unbindListener = View.OnClickListener { if (isBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. try { mService?.unregisterCallback(mCallback) } catch (e: RemoteException) { // There is nothing special we need to do if the service // crashes. } // Detach our existing connection. unbindService(mConnection) unbindService(secondaryConnection) killButton.isEnabled = false isBound = false callbackText.text = "Unbinding." } } private val killListener = View.OnClickListener { // To kill the process hosting the service, we need to know its // PID. Conveniently, the service has a call that returns // that information. try { secondaryService?.pid?.also { pid -> // Note that, though this API lets us request to // kill any process based on its PID, the kernel // still imposes standard restrictions on which PIDs you // can actually kill. Typically this means only // the process running your application and any additional // processes created by that app, as shown here. Packages // sharing a common UID are also able to kill each // other's processes. Process.killProcess(pid) callbackText.text = "Killed service process." } } catch (ex: RemoteException) { // Recover gracefully from the process hosting the // server dying. // For purposes of this sample, put up a notification. Toast.makeText(this@Binding, R.string.remote_call_failed, Toast.LENGTH_SHORT).show() } } // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private val mCallback = object : IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here is * NOT running in our main thread like most other things. So, * to update the UI, we need to use a Handler to hop over there. */ override fun valueChanged(value: Int) { handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0)) } } /** * Standard initialization of this activity. Set up the UI, then wait * for the user to interact with it before doing anything. */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.remote_service_binding) // Watch for button taps. var button: Button = findViewById(R.id.bind) button.setOnClickListener(mBindListener) button = findViewById(R.id.unbind) button.setOnClickListener(unbindListener) killButton = findViewById(R.id.kill) killButton.setOnClickListener(killListener) killButton.isEnabled = false callbackText = findViewById(R.id.callback) callbackText.text = "Not attached." handler = InternalHandler(callbackText) } private class InternalHandler( textView: TextView, private val weakTextView: WeakReference<TextView> = WeakReference(textView) ) : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { BUMP_MSG -> weakTextView.get()?.text = "Received from service: ${msg.arg1}" else -> super.handleMessage(msg) } } } }
Java
public static class Binding extends Activity { /** The primary interface we are calling on the service. */ IRemoteService mService = null; /** Another interface we use on the service. */ ISecondary secondaryService = null; Button killButton; TextView callbackText; private InternalHandler handler; private boolean isBound; /** * Standard initialization of this activity. Set up the UI, then wait * for the user to interact with it before doing anything. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.remote_service_binding); // Watch for button taps. Button button = (Button)findViewById(R.id.bind); button.setOnClickListener(mBindListener); button = (Button)findViewById(R.id.unbind); button.setOnClickListener(unbindListener); killButton = (Button)findViewById(R.id.kill); killButton.setOnClickListener(killListener); killButton.setEnabled(false); callbackText = (TextView)findViewById(R.id.callback); callbackText.setText("Not attached."); handler = new InternalHandler(callbackText); } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service is // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service); killButton.setEnabled(true); callbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { mService.registerCallback(mCallback); } catch (RemoteException e) { // In this case the service crashes before we can even // do anything with it. We can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service is // unexpectedly disconnected—that is, its process crashed. mService = null; killButton.setEnabled(false); callbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); } }; /** * Class for interacting with the secondary interface of the service. */ private ServiceConnection secondaryConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // Connecting to a secondary interface is the same as any // other interface. secondaryService = ISecondary.Stub.asInterface(service); killButton.setEnabled(true); } public void onServiceDisconnected(ComponentName className) { secondaryService = null; killButton.setEnabled(false); } }; private OnClickListener mBindListener = new OnClickListener() { public void onClick(View v) { // Establish a couple connections with the service, binding // by interface names. This lets other applications be // installed that replace the remote service by implementing // the same interface. Intent intent = new Intent(Binding.this, RemoteService.class); intent.setAction(IRemoteService.class.getName()); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); intent.setAction(ISecondary.class.getName()); bindService(intent, secondaryConnection, Context.BIND_AUTO_CREATE); isBound = true; callbackText.setText("Binding."); } }; private OnClickListener unbindListener = new OnClickListener() { public void onClick(View v) { if (isBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { mService.unregisterCallback(mCallback); } catch (RemoteException e) { // There is nothing special we need to do if the service // crashes. } } // Detach our existing connection. unbindService(mConnection); unbindService(secondaryConnection); killButton.setEnabled(false); isBound = false; callbackText.setText("Unbinding."); } } }; private OnClickListener killListener = new OnClickListener() { public void onClick(View v) { // To kill the process hosting our service, we need to know its // PID. Conveniently, our service has a call that returns // that information. if (secondaryService != null) { try { int pid = secondaryService.getPid(); // Note that, though this API lets us request to // kill any process based on its PID, the kernel // still imposes standard restrictions on which PIDs you // can actually kill. Typically this means only // the process running your application and any additional // processes created by that app as shown here. Packages // sharing a common UID are also able to kill each // other's processes. Process.killProcess(pid); callbackText.setText("Killed service process."); } catch (RemoteException ex) { // Recover gracefully from the process hosting the // server dying. // For purposes of this sample, put up a notification. Toast.makeText(Binding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } } }; // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here is * NOT running in our main thread like most other things. So, * to update the UI, we need to use a Handler to hop over there. */ public void valueChanged(int value) { handler.sendMessage(handler.obtainMessage(BUMP_MSG, value, 0)); } }; private static final int BUMP_MSG = 1; private static class InternalHandler extends Handler { private final WeakReference<TextView> weakTextView; InternalHandler(TextView textView) { weakTextView = new WeakReference<>(textView); } @Override public void handleMessage(Message msg) { switch (msg.what) { case BUMP_MSG: TextView textView = weakTextView.get(); if (textView != null) { textView.setText("Received from service: " + msg.arg1); } break; default: super.handleMessage(msg); } } } }