تشبه لغة تعريف واجهة 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
يحدّد هذا الملف واجهة البرمجة من خلال توقيعات الطرق.
- تنفيذ الواجهة
تُنشئ أدوات Android SDK واجهة بلغة برمجة Java استنادًا إلى ملف
.aidl
. تحتوي هذه الواجهة على فئة مجردة داخلية باسمStub
تمتدBinder
وينفّذ الطرق من واجهة AIDL. يجب عليك تمديدStub
ونفِّذ الطرق. - عرض الواجهة للعملاء
تنبيه: أي تغييرات تجريها على واجهة AIDL بعد
أن يظل إصدارك الأول متوافقًا مع الأنظمة القديمة لتجنُّب تعطُّل التطبيقات الأخرى
التي تستخدم خدمتك أي أنّه يجب نسخ الملف .aidl
إلى تطبيقات أخرى.
ولكي يتمكنوا من الدخول إلى واجهة الخدمة، يجب الحفاظ على دعم واجهة مستخدم الخدمة الأصلية،
من واجهة pyplot.
إنشاء ملف .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"، سينشئ الإصدار التزايدي فئة الرابط على الفور تقريبًا.
إذا كنت لا تستخدم "استوديو 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.
تمرير الكائنات على 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
إلى على غرار ملف العنوان بلغة C، لا يتم تجميع ملف.aidl
هذا.
تستخدم 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
، ولكن عليك التأكد من أنّ هذه الأرقام تقع ضمن النطاق المقبول
القيم لكل ما يحاول المتصل فعله. للحصول على مزيد من المعلومات حول كيفية حماية تطبيقك من البرامج الضارة، يُرجى الاطّلاع على نصائح الأمان.
الطرق التي تتضمّن وسيطات الحزمة التي تحتوي على قطع قطع
إذا قبلت الطريقة كائن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); } } } }