تشبه لغة تعريف واجهة Android (AIDL) لغات تعريف واجهة Android (AIDL) الأخرى: فهي تتيح لك تحديد واجهة البرمجة التي يتفق عليها كل من العميل والخدمة من أجل التواصل مع بعضهما باستخدام الاتصال البيني للعمليات (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.
لإنشاء خدمة محدودة باستخدام لغة تعريف واجهة نظام Android (AIDL)، اتّبِع الخطوات التالية الموضَّحة في الأقسام التالية:
- إنشاء ملف
.aidl
يعرّف هذا الملف واجهة البرمجة باستخدام توقيعات الطرق.
- تنفيذ الواجهة
تنشئ أدوات حزمة تطوير البرامج (SDK) لنظام التشغيل Android واجهة بلغة برمجة Java استنادًا إلى ملف
.aidl
. تحتوي هذه الواجهة على فئة مجردة داخلية باسمStub
توسّع نطاقBinder
وتنفِّذ طرقًا من واجهة AIDL. ويجب توسيع الفئةStub
وتنفيذ الطرق. - عرض الواجهة للعملاء
تنبيه: يجب أن تظل أي تغييرات تجريها على واجهة AIDL بعد الإصدار الأول متوافقة مع الأنظمة القديمة لتجنُّب تعطُّل التطبيقات الأخرى التي تستخدم خدمتك. وهذا يعني أنّه يجب نسخ ملف .aidl
إلى تطبيقات أخرى
لكي تتمكّن هذه التطبيقات من الوصول إلى واجهة خدمتك، عليك مواصلة استخدام الواجهة
الأصلية.
إنشاء ملف .aidl
يستخدم الذكاء الاصطناعي (AIDL) بنية بسيطة تتيح لك الإعلان عن واجهة باستخدام طريقة واحدة أو أكثر يمكنها استخدام المعلَمات وعرض القيم. يمكن أن تكون المعلمات والقيم المعروضة من أي نوع، حتى الواجهات الأخرى التي تم إنشاؤها بواسطة AIDL.
يجب إنشاء الملف .aidl
باستخدام لغة البرمجة Java. يجب أن يحدّد كل ملف .aidl
واجهة واحدة، ولا يتطلب سوى بيان الواجهة وتوقيعات الطريقة.
تتوافق تقنية AIDL تلقائيًا مع أنواع البيانات التالية:
- جميع الأنواع الأساسية في لغة البرمجة Java (مثل
int
وlong
وchar
وboolean
وما إلى ذلك) - صفائف الأنواع الأساسية، مثل
int[]
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
في دليل gen/
للمشروع. يتطابق اسم الملف الذي تم إنشاؤه مع اسم الملف .aidl
، ولكن
بامتداد .java
. على سبيل المثال، نتائج IRemoteService.aidl
باللغة IRemoteService.java
.
في حال استخدام Android Studio، سيتم إنشاء فئة الصنف Binder على الفور تقريبًا، وذلك من خلال الإصدار التزايدي.
إذا كنت لا تستخدم "استوديو Android"، ستنشئ أداة Gradle فئة الصنف Binder عند إنشاء تطبيقك في المرة التالية. يمكنك إنشاء مشروعك باستخدام 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
(a 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
في نظام التشغيل Android 10 (المستوى 29 لواجهة برمجة التطبيقات أو مستوى أعلى)، يمكنك تحديد
عناصر Parcelable
مباشرةً في
AIDL. يمكن هنا أيضًا استخدام الأنواع المتاحة كوسيطات واجهة AIDL وغيرها من الأجزاء. فهذا يتجنب العمل الإضافي لكتابة كود تنظيمي وفئة مخصصة يدويًا. ومع ذلك، فإن هذا يؤدي أيضًا إلى إنشاء هيكل مجرّد. إذا كنت تريد الموصّلات المخصّصة أو الوظائف الأخرى، يمكنك استخدام 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
يعرّف عن فئة الشحن، كما هو موضّح في ملف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
لمعرفة الأنواع الأخرى من القيم التي يمكنك كتابتها في Parcel
.
تحذير: يجب تذكّر الآثار الأمنية الناتجة عن تلقّي
البيانات من عمليات أخرى. في هذه الحالة، يقرأ Rect
أربعة أرقام من Parcel
، ولكن الأمر متروك لك للتأكد من أنّ هذه الأرقام تقع ضمن النطاق المقبول لكل ما يحاول المتصل فعله. لمزيد من المعلومات حول كيفية حماية تطبيقك من البرامج الضارة، راجع نصائح الأمان.
الطرق التي تحتوي على وسيطات حزمة تحتوي على Parcelables
إذا قبلت إحدى الطرق كائنBundle
يُتوقع أن يحتوي على عناصر قابلة للفصل، احرص على ضبط أداة تحميل الفئة في Bundle
من خلال
استدعاء Bundle.setClassLoader(ClassLoader)
قبل محاولة القراءة
من Bundle
. بخلاف ذلك، ستواجه ClassNotFoundException
على الرغم من تحديد الحزمة بشكل صحيح في تطبيقك.
على سبيل المثال، يمكنك الاطّلاع على النموذج التالي لملف .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
الذي يتم إنشاؤه استنادًا إلى AIDL. - نفِّذ
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); } } } }