زبان تعریف رابط اندروید (AIDL)

زبان تعریف رابط اندروید (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، این مراحل را دنبال کنید که در بخش های زیر توضیح داده شده است:

  1. فایل .aidl ایجاد کنید

    این فایل رابط برنامه نویسی را با امضاهای متد تعریف می کند.

  2. رابط را پیاده سازی کنید

    ابزار Android SDK یک رابط به زبان برنامه نویسی جاوا بر اساس فایل .aidl شما ایجاد می کند. این رابط دارای یک کلاس انتزاعی درونی به نام Stub است که Binder را گسترش می‌دهد و متدهایی را از رابط AIDL شما پیاده‌سازی می‌کند. شما باید کلاس Stub را گسترش دهید و متدها را پیاده سازی کنید.

  3. رابط را در معرض دید مشتریان قرار دهید

    برای برگرداندن پیاده‌سازی کلاس Stub ، یک Service پیاده‌سازی کنید و onBind() را لغو کنید.

احتیاط: هر گونه تغییری که پس از اولین نسخه در رابط AIDL خود ایجاد می‌کنید، باید برای جلوگیری از شکستن سایر برنامه‌هایی که از سرویس شما استفاده می‌کنند، سازگار باقی بماند. یعنی چون فایل .aidl شما باید در برنامه های دیگر کپی شود تا بتوانند به رابط سرویس شما دسترسی داشته باشند، باید از رابط اصلی پشتیبانی کنید.

فایل .aidl را ایجاد کنید

AIDL از یک نحو ساده استفاده می‌کند که به شما امکان می‌دهد یک رابط را با یک یا چند روش اعلام کنید که می‌تواند پارامترها و مقادیر را برگرداند. پارامترها و مقادیر بازگشتی می توانند از هر نوع باشند، حتی سایر رابط های تولید شده توسط AIDL.

شما باید فایل .aidl را با استفاده از زبان برنامه نویسی جاوا بسازید. هر فایل .aidl باید یک رابط واحد تعریف کند و فقط به اعلان رابط و امضاهای متد نیاز دارد.

به طور پیش فرض، AIDL از انواع داده های زیر پشتیبانی می کند:

  • همه انواع اولیه در زبان برنامه نویسی جاوا (مانند 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 اضافه کنید، حتی اگر آنها در همان بسته رابط شما تعریف شده باشند.

هنگام تعریف رابط سرویس خود، توجه داشته باشید که:

  • متدها می توانند صفر یا چند پارامتر داشته باشند و می توانند مقدار یا 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

در اندروید 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 پشتیبانی می کند، موارد زیر را انجام دهید:

  1. کلاس خود را وادار کنید که رابط Parcelable را پیاده سازی کند.
  2. پیاده سازی writeToParcel ، که وضعیت فعلی شی را می گیرد و آن را در یک Parcel می نویسد.
  3. یک فیلد ثابت به نام CREATOR به کلاس خود اضافه کنید که یک شی است که رابط Parcelable.Creator را پیاده سازی می کند.
  4. در نهایت، همانطور که برای فایل 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، مراحل زیر را در کلاس تماس خود انجام دهید:

  1. فایل .aidl را در پوشه src/ پروژه قرار دهید.
  2. یک نمونه از رابط IBinder را که بر اساس AIDL تولید می شود، اعلام کنید.
  3. ServiceConnection پیاده سازی کنید.
  4. Context.bindService() را فراخوانی کنید که در اجرای ServiceConnection شما ارسال می شود.
  5. در پیاده سازی onServiceConnected() یک نمونه IBinder به نام service دریافت می کنید. YourInterfaceName .Stub.asInterface((IBinder) service ) را فراخوانی کنید تا پارامتر برگشتی را به نوع YourInterface ارسال کنید.
  6. روش هایی را که در رابط خود تعریف کرده اید فراخوانی کنید. همیشه استثناهای DeadObjectException را به دام بیاندازید، که وقتی اتصال قطع می شود، پرتاب می شوند. همچنین، استثناهای SecurityException را به دام بیندازید، که وقتی دو فرآیند درگیر در فراخوانی روش IPC دارای تعاریف متضاد AIDL هستند، پرتاب می‌شوند.
  7. برای قطع ارتباط، 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&mdash;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&mdash;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);
            }
        }
    }
}