زبان تعریف رابط اندروید (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 از انواع داده های زیر پشتیبانی می کند:
- همه انواع اولیه، به استثنای
short، در زبان برنامه نویسی جاوا (مانند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); } } } }