زبان تعریف رابط اندروید (AIDL) مشابه سایر IDL ها است: به شما امکان می دهد رابط برنامه نویسی را تعریف کنید که مشتری و سرویس بر آن توافق دارند تا با استفاده از ارتباطات بین فرآیندی (IPC) با یکدیگر ارتباط برقرار کنند.
در اندروید، یک فرآیند به طور معمول نمی تواند به حافظه یک فرآیند دیگر دسترسی پیدا کند. برای صحبت کردن، آنها باید اشیاء خود را به موارد اولیه تجزیه کنند که سیستم عامل بتواند آنها را درک کند و اشیاء را در آن مرز برای شما قرار دهد. نوشتن کد برای انجام آن marshalling خسته کننده است، بنابراین Android آن را برای شما با AIDL مدیریت می کند.
توجه: AIDL تنها در صورتی ضروری است که به مشتریان برنامه های مختلف اجازه دهید به سرویس شما برای IPC دسترسی داشته باشند و بخواهید چند رشته ای را در سرویس خود مدیریت کنید. اگر نیازی به اجرای IPC همزمان در برنامه های مختلف ندارید، رابط خود را با پیاده سازی Binder
ایجاد کنید. اگر میخواهید IPC انجام دهید اما نیازی به مدیریت چند رشتهای ندارید ، رابط خود را با استفاده از Messenger
پیادهسازی کنید. بدون در نظر گرفتن این موضوع، قبل از اجرای AIDL مطمئن شوید که خدمات محدود را درک می کنید.
قبل از شروع طراحی رابط AIDL خود، توجه داشته باشید که فراخوانی به رابط AIDL فراخوانی مستقیم تابع است. در مورد رشته ای که فراخوانی در آن اتفاق می افتد فرضیات خود را انجام ندهید. بسته به اینکه تماس از یک رشته در فرآیند محلی باشد یا یک فرآیند راه دور، چه اتفاقی می افتد متفاوت است:
- تماسهای ایجاد شده از فرآیند محلی در همان رشتهای اجرا میشوند که تماس را برقرار میکند. اگر این رشته اصلی UI شما باشد، آن رشته در رابط AIDL به اجرا ادامه می دهد. اگر رشته دیگری باشد، آن رشته ای است که کد شما را در سرویس اجرا می کند. بنابراین، اگر فقط رشته های محلی به سرویس دسترسی داشته باشند، می توانید به طور کامل کنترل کنید که کدام رشته ها در آن اجرا می شوند. اما اگر اینطور است، اصلا از AIDL استفاده نکنید. در عوض، رابط را با پیاده سازی
Binder
ایجاد کنید. - تماسهای یک فرآیند راه دور از یک استخر نخی ارسال میشوند که پلتفرم در داخل فرآیند شما نگهداری میکند. برای تماسهای دریافتی از رشتههای ناشناخته، با چندین تماس همزمان آماده باشید. به عبارت دیگر، پیاده سازی یک رابط AIDL باید کاملاً ایمن باشد. تماس های برقرار شده از یک رشته در همان شی راه دور به ترتیب به انتهای گیرنده می رسند.
- کلمه کلیدی
oneway
رفتار تماس های راه دور را تغییر می دهد. هنگامی که از آن استفاده می شود، تماس از راه دور مسدود نمی شود. داده های تراکنش را ارسال می کند و بلافاصله برمی گردد. پیاده سازی رابط در نهایت این را به عنوان یک تماس معمولی از مخزن رشتهBinder
به عنوان یک تماس از راه دور معمولی دریافت می کند. اگرoneway
با تماس محلی استفاده شود، هیچ تاثیری ندارد و تماس همچنان همزمان است.
تعریف رابط AIDL
رابط AIDL خود را در یک فایل .aidl
با استفاده از نحو زبان برنامه نویسی جاوا تعریف کنید، سپس آن را در کد منبع، در دایرکتوری src/
، برنامه میزبان سرویس و هر برنامه دیگری که به سرویس متصل می شود، ذخیره کنید.
وقتی هر برنامهای را میسازید که حاوی فایل .aidl
است، ابزار Android SDK یک رابط IBinder
بر اساس فایل .aidl
ایجاد میکند و آن را در دایرکتوری gen/
پروژه ذخیره میکند. این سرویس باید رابط IBinder
را به صورت مناسب پیاده سازی کند. سپس برنامه های سرویس گیرنده می توانند برای اجرای IPC به سرویس و متدهای فراخوانی از IBinder
متصل شوند.
برای ایجاد یک سرویس محدود با استفاده از AIDL، این مراحل را دنبال کنید که در بخش های زیر توضیح داده شده است:
- فایل
.aidl
را ایجاد کنیداین فایل رابط برنامه نویسی را با امضاهای متد تعریف می کند.
- رابط را پیاده سازی کنید
ابزار Android SDK یک رابط به زبان برنامه نویسی جاوا بر اساس فایل
.aidl
شما ایجاد می کند. این رابط دارای یک کلاس انتزاعی درونی به نامStub
است کهBinder
گسترش میدهد و متدهایی را از رابط AIDL شما پیادهسازی میکند. شما باید کلاسStub
را گسترش دهید و متدها را پیاده سازی کنید. - رابط را در معرض دید مشتریان قرار دهید
برای برگرداندن پیادهسازی کلاس
Stub
، یکService
پیادهسازی کنید وonBind()
لغو کنید.
احتیاط: هر گونه تغییری که پس از اولین نسخه در رابط AIDL خود ایجاد میکنید، باید برای جلوگیری از شکستن سایر برنامههایی که از سرویس شما استفاده میکنند، سازگار باقی بماند. یعنی چون فایل .aidl
شما باید در برنامه های دیگر کپی شود تا بتوانند به رابط سرویس شما دسترسی داشته باشند، باید از رابط اصلی پشتیبانی کنید.
فایل .aidl را ایجاد کنید
AIDL از یک نحو ساده استفاده میکند که به شما امکان میدهد یک رابط را با یک یا چند روش اعلام کنید که میتواند پارامترها و مقادیر را برگرداند. پارامترها و مقادیر بازگشتی می توانند از هر نوع باشند، حتی سایر رابط های تولید شده توسط AIDL.
شما باید فایل .aidl
را با استفاده از زبان برنامه نویسی جاوا بسازید. هر فایل .aidl
باید یک واسط واحد تعریف کند و فقط به اعلان رابط و امضای روش نیاز دارد.
به طور پیش فرض، AIDL از انواع داده های زیر پشتیبانی می کند:
- همه انواع اولیه در زبان برنامه نویسی جاوا (مانند
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
اضافه کنید، حتی اگر آنها در همان بسته رابط شما تعریف شده باشند.
هنگام تعریف رابط سرویس خود، توجه داشته باشید که:
- متدها می توانند صفر یا چند پارامتر داشته باشند و می توانند مقدار یا void را برگردانند.
- همه پارامترهای غیر ابتدایی نیاز به یک برچسب جهت دار دارند که نشان می دهد داده ها به کدام سمت می روند:
in
,out
یاinout
(به مثال زیر مراجعه کنید).رابط های اولیه،
String
،IBinder
و AIDL تولید شده به طور پیش فرضin
هستند و غیر از این نمی توانند باشند.احتیاط: جهت را به آنچه واقعاً مورد نیاز است محدود کنید، زیرا پارامترهای مارشال گران هستند.
- همه نظرات کد موجود در فایل
.aidl
در رابطIBinder
ایجاد شده به جز نظرات قبل از import و بسته اظهارات گنجانده شده است. - ثابت های رشته و int را می توان در رابط 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 استفاده می کنید، ساخت افزایشی تقریباً بلافاصله کلاس بایندر را ایجاد می کند. اگر از اندروید استودیو استفاده نمی کنید، ابزار Gradle بار بعدی که برنامه خود را می سازید کلاس بایندر تولید می کند. پروژه خود را با gradle assembleDebug
یا gradle assembleRelease
به محض اتمام نوشتن فایل .aidl
بسازید تا کد شما بتواند با کلاس تولید شده پیوند برقرار کند.
رابط را پیاده سازی کنید
هنگامی که برنامه خود را میسازید، ابزار Android SDK یک فایل رابط .java
به نام فایل .aidl
شما ایجاد میکند. اینترفیس تولید شده شامل یک کلاس فرعی به نام Stub
است که یک پیادهسازی انتزاعی از رابط والد خود، مانند YourInterface.Stub
است و همه روشها را از فایل .aidl
اعلام میکند.
نکته: Stub
همچنین چند متد کمکی را تعریف میکند، به ویژه asInterface()
که یک IBinder
را میگیرد، معمولاً روشی که به متد callback onServiceConnected()
مشتری ارسال میشود، و نمونهای از واسط stub را برمیگرداند. برای جزئیات بیشتر در مورد نحوه ساخت این قالب، به بخش فراخوانی روش IPC مراجعه کنید.
برای پیاده سازی رابط تولید شده از .aidl
، رابط Binder
تولید شده مانند YourInterface.Stub
را گسترش دهید و روش های به ارث رسیده از فایل .aidl
را پیاده سازی کنید.
در اینجا نمونه ای از پیاده سازی یک رابط به نام IRemoteService
است که توسط مثال قبلی IRemoteService.aidl
با استفاده از یک نمونه ناشناس تعریف شده است:
کاتلین
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. } }
جاوا
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 همزمان هستند. اگر می دانید که سرویس بیش از چند میلی ثانیه طول می کشد تا یک درخواست را تکمیل کند، آن را از رشته اصلی فعالیت تماس نگیرید. ممکن است برنامه را آویزان کند و در نتیجه اندروید کادر گفتگوی «برنامه پاسخ نمیدهد» را نمایش دهد. آن را از یک موضوع جداگانه در مشتری فراخوانی کنید.
- فقط انواع استثناهای فهرست شده در اسناد مرجع برای
Parcel.writeException()
برای تماس گیرنده ارسال می شوند.
رابط را در معرض دید مشتریان قرار دهید
هنگامی که رابط کاربری را برای سرویس خود پیاده سازی کردید، باید آن را در معرض دید مشتریان قرار دهید تا بتوانند به آن متصل شوند. برای نمایش رابط سرویس خود، Service
گسترش دهید و onBind()
را پیاده سازی کنید تا نمونه ای از کلاس شما را که Stub
تولید شده را پیاده سازی می کند، همانطور که در بخش قبل توضیح داده شد، برگردانید. در اینجا یک سرویس نمونه است که رابط نمونه IRemoteService
را در معرض دید مشتریان قرار می دهد.
کاتلین
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. } } }
جاوا
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
ارسال کند:
کاتلین
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 } }
جاوا
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 (سطح API 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; }
نمونه کد قبلی به طور خودکار یک کلاس جاوا با فیلدهای عدد صحیح left
، top
، right
و bottom
تولید می کند. همه کدهای مارشال مربوطه به طور خودکار پیاده سازی می شوند و شی را می توان مستقیماً بدون نیاز به افزودن هیچ پیاده سازی استفاده کرد.
همچنین می توانید یک کلاس سفارشی را از طریق یک رابط IPC از یک فرآیند به فرآیند دیگر ارسال کنید. با این حال، مطمئن شوید که کد کلاس شما در طرف دیگر کانال IPC موجود است و کلاس شما باید از رابط Parcelable
پشتیبانی کند. پشتیبانی از Parcelable
مهم است زیرا به سیستم اندروید اجازه میدهد اشیاء را به موارد اولیه تجزیه کند که میتوانند در سراسر فرآیندها تقسیم شوند.
برای ایجاد یک کلاس سفارشی که از Parcelable
پشتیبانی می کند، موارد زیر را انجام دهید:
- کلاس خود را وادار کنید که رابط
Parcelable
را پیاده سازی کند. - پیاده سازی
writeToParcel
، که وضعیت فعلی شی را می گیرد و آن را در یکParcel
می نویسد. - یک فیلد ثابت به نام
CREATOR
را به کلاس خود اضافه کنید که یک شی است که رابطParcelable.Creator
را پیاده سازی می کند. - در نهایت، همانطور که برای فایل
Rect.aidl
زیر نشان داده شده است، یک فایل.aidl
ایجاد کنید که کلاس parcelable شما را اعلام کند.اگر از فرآیند ساخت سفارشی استفاده می کنید، فایل
.aidl
را به بیلد خود اضافه نکنید . مشابه فایل هدر در زبان C، این فایل.aidl
کامپایل نشده است.
AIDL از این روشها و فیلدها در کدی که تولید میکند برای مارشال کردن و از بین بردن اشیاء شما استفاده میکند.
به عنوان مثال، در اینجا یک فایل Rect.aidl
برای ایجاد یک کلاس Rect
است که قابل parcelable است:
package android.graphics; // Declare Rect so AIDL can find it and knows that it implements // the parcelable protocol. parcelable Rect;
و در اینجا مثالی از نحوه پیاده سازی پروتکل Parcelable
توسط کلاس Rect
آورده شده است.
کاتلین
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 } }
جاوا
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 حاوی Parcelables
اگر متدی شیءBundle
را میپذیرد که انتظار میرود حاوی parcelable باشد، مطمئن شوید که کلاسلودر Bundle
را با فراخوانی Bundle.setClassLoader(ClassLoader)
قبل از تلاش برای خواندن از Bundle
تنظیم کردهاید. در غیر این صورت، با وجود اینکه parcelable به درستی در برنامه شما تعریف شده است، با 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
تنظیم شده است: کاتلین
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. } }
جاوا
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 به این نکات توجه کنید:
- اشیاء مرجع شمارش شده در فرآیندها هستند.
- می توانید اشیاء ناشناس را به عنوان آرگومان های متد ارسال کنید.
برای اطلاعات بیشتر در مورد اتصال به یک سرویس، نمای کلی خدمات Bound را بخوانید.
در اینجا چند کد نمونه وجود دارد که فراخوانی یک سرویس ایجاد شده توسط AIDL را نشان می دهد که از نمونه Remote Service در پروژه ApiDemos گرفته شده است.
کاتلین
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) } } } }
جاوا
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); } } } }