وتشبه لغة تعريف واجهة 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
وتنفيذ الطرق. - إتاحة الواجهة للعملاء
عليك تنفيذ
Service
وإلغاء السمةonBind()
لإظهار عملية تنفيذ الفئةStub
.
تحذير: يجب أن تظل أي تغييرات تجريها على واجهة AIDL بعد
إصدارك الأول متوافقة مع الإصدارات القديمة لتجنُّب إيقاف التطبيقات الأخرى
التي تستخدم خدمتك. وهذا يعني أنّه يجب أن يكون لديك واجهة أصلية متوافقة مع التطبيقات الأخرى
كي تتمكّن من الوصول إلى واجهة خدمتك، ويجب أن تحافظ على هذه الواجهة..aidl
إنشاء ملف .aidl
يستخدم AIDL بنية بسيطة تتيح لك تعريف واجهة باستخدام طريقة واحدة أو أكثر يمكنها تلقّي المَعلمات وعرض القيم. يمكن أن تكون المَعلمات والقيم المعروضة من أي نوع، حتى واجهات أخرى تم إنشاؤها باستخدام واجهة برمجة التطبيقات AIDL.
يجب إنشاء ملف .aidl
باستخدام لغة البرمجة Java. يجب أن يحدِّد كل ملف .aidl
واجهة واحدة ولا يتطلّب سوى تعريف الواجهة وتوقيعات methods.
يتيح AIDL تلقائيًا أنواع البيانات التالية:
- جميع الأنواع الأساسية في لغة برمجة Java (مثل
int
وlong
char
وboolean
وما إلى ذلك) - صفائف من أي أنواع، مثل
int[]
أوMyParcelable[]
String
CharSequence
List
يجب أن تكون جميع العناصر في
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
في directorygen/
لمشروعك. يتطابق اسم الملف الذي تم إنشاؤه مع اسم ملف .aidl
، ولكن
مع إضافة .java
. على سبيل المثال، يؤدي استخدام IRemoteService.aidl
باللغة IRemoteService.java
.
إذا كنت تستخدم "استوديو Android"، سينشئ الإصدار التزايدي فئة الرابط على الفور تقريبًا.
إذا كنت لا تستخدم Android Studio، تنشئ أداة 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.
تمرير العناصر عبر واجهة IPC
في الإصدار 10 من نظام التشغيل Android (المستوى 29 من واجهة برمجة التطبيقات أو مستوى أعلى)، يمكنك تحديد عناصر
Parcelable
مباشرةً فيملف IDEAL. يمكن أيضًا استخدام الأنواع المتوافقة كوسيطات لواجهة 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); } } } }