زبان تعریف میانای Android ‏ (AIDL)

«زبان تعریف میانای Android»‏ (AIDL) شبیه به دیگر IDLها است: به شما امکان می‌دهد میانای برنامه‌نویسی را تعریف کنید که هم کارخواه و هم سرویس برای برقراری ارتباط با یکدیگر بااستفاده از ارتباط میان پردازشی (IPC) با آن موافقت می‌کنند.

در Android، یک فرایند معمولاً نمی‌تواند به حافظه فرایند دیگری دسترسی داشته باشد. برای صحبت کردن، آن‌ها باید اشیای خود را به اشکال اولیه‌ای تجزیه کنند که سیستم‌عامل بتواند آن‌ها را درک کند و اشیا را در آن مرز برای شما مرتب کند. کد لازم برای انجام این مرتب‌سازی خسته‌کننده است، بنابراین Android آن را با AIDL برای شما انجام می‌دهد.

توجه: AIDL فقط درصورتی لازم است که به کارخواه‌های برنامه‌های مختلف اجازه دهید برای IPC به سرویس شما دسترسی داشته باشند و بخواهید چندرشته‌ای را در سرویس خود مدیریت کنید. اگر نیازی به اجرای IPC هم‌زمان در برنامه‌های مختلف ندارید، میانای خود را با پیاده‌سازی Binder ایجاد کنید. اگر می‌خواهید IPC را اجرا کنید اما نیازی به مدیریت چندرشته‌ای ندارید، میانای خود را بااستفاده از Messenger پیاده‌سازی کنید. صرف‌نظر از این، قبل‌از پیاده‌سازی AIDL، مطمئن شوید که خدمات محدودکننده را درک کرده‌اید.

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

  • تماس‌های برقرارشده از فرایند محلی در همان رشته‌ای اجرا می‌شود که تماس را برقرار می‌کند. اگر این رشته میانای کاربر اصلی شما باشد، آن رشته در میانای AIDL به اجرا ادامه می‌دهد. اگر رشته دیگری باشد، همان رشته‌ای است که کد شما را در سرویس اجرا می‌کند. بنابراین، اگر فقط رشته‌های محلی به سرویس دسترسی داشته باشند، می‌توانید کاملاً کنترل کنید که کدام رشته‌ها در آن اجرا شوند. اما اگر این‌طور است، اصلاً از AIDL استفاده نکنید؛ درعوض، میانای موردنظر را با پیاده‌سازی Binder ایجاد کنید.
  • تماس‌های دریافتی از فرایند راه دور از استخر رشته‌ای که پلاتفرم در داخل فرایند شما نگهداری می‌کند ارسال می‌شود. برای تماس‌های ورودی از رشته‌های ناشناس آماده باشید، با چندین تماس که به‌طور هم‌زمان اتفاق می‌افتند. به عبارت دیگر، پیاده‌سازی میانای AIDL باید کاملاً ایمن از رشته باشد. تماس‌های برقرارشده از یک رشته در همان شیء راه دور به‌ترتیب به انتهای گیرنده می‌رسند.
  • کلیدواژه oneway رفتار تماس‌های از راه دور را تغییر می‌دهد. وقتی از آن استفاده می‌شود، تماس ازراه‌دور مسدود نمی‌شود. این روش داده‌های تراکنش را ارسال می‌کند و بلافاصله برمی‌گردد. پیاده‌سازی میانای کاربری درنهایت این را به‌عنوان تماسی عادی از استخر رشته Binder به‌عنوان تماسی از راه دور و عادی دریافت می‌کند. اگر از oneway با تماس محلی استفاده شود، تأثیری ندارد و تماس همچنان همگام است.

تعریف میانای AIDL

میانای AIDL خود را در فایل .aidl بااستفاده از نحو زبان برنامه‌نویسی Java تعریف کنید، سپس آن را در کد منبع، در دایرکتوری src/، هم در برنامه میزبان سرویس و هم در هر برنامه دیگری که به سرویس متصل می‌شود، ذخیره کنید.

وقتی هر برنامه‌ای را که حاوی فایل .aidl است می‌سازید، ابزارهای Android SDK میانای IBinder را براساس فایل .aidl تولید می‌کنند و آن را در فهرست راهنمای gen/ پروژه ذخیره می‌کنند. سرویس باید میانای IBinder را به‌طور مناسب پیاده‌سازی کند. سپس برنامه‌های کارخواه می‌توانند به سرویس متصل شوند و روش‌ها را از IBinder برای اجرای IPC فراخوانی کنند.

برای ایجاد سرویس محدودشده بااستفاده از AIDL، این مراحل را که در بخش‌های زیر شرح داده شده است دنبال کنید:

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

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

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

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

  3. نمایش میانای مشتریان

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

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

ایجاد فایل aidl.

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

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

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

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

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

  • روش‌ها می‌توانند صفر یا چند پارامتر داشته باشند و می‌توانند مقدار یا پوچی را برگردانند.
  • همه پارامترهای غیرابتدایی به برچسب جهت‌دار نیاز دارند که نشان دهد داده‌ها به کدام سمت می‌روند: in، out، یا inout (نمونه زیر را ببینید).

    رابط‌های Primitives،‏ String،‏ IBinder، و رابط‌های تولیدشده توسط AIDL به‌طور پیش‌فرض in هستند و نمی‌توانند غیراز این باشند.

    احتیاط: جهت را به آنچه واقعاً موردنیاز است محدود کنید، زیرا پارامترهای مرتب‌سازی گران هستند.

  • همه نظرات کد موجود در فایل .aidl در میانای IBinder تولیدشده گنجانده شده‌اند، به‌جز نظرات قبل‌از عبارت‌های وارد کردن و بسته.
  • ثابت‌های رشته‌ای و عدد صحیح را می‌توان در میانای AIDL تعریف کرد، مثل const int VERSION = 1;.
  • فراخوانی‌های روش توسط transact() کد ارسال می‌شوند که معمولاً براساس نمایه روش در میانای مربوطه است. چون این کار نسخه‌بندی را دشوار می‌کند، می‌توانید کد تراکنش را به‌صورت دستی به روشی اختصاص دهید: void method() = 10;.
  • متغیرهای مستقل تهی‌پذیر و انواع پاسخ باید بااستفاده از @nullable حاشیه‌نویسی شوند.

در اینجا یک نمونه فایل .aidl آورده شده است:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements.

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

فایل .aidl را در دایرکتوری src/ پروژه‌تان ذخیره کنید. وقتی برنامه‌تان را می‌سازید، ابزارهای کیت توسعه نرم‌افزار فایل میانای IBinder را در دایرکتوری gen/ پروژه‌تان تولید می‌کنند. نام فایل تولیدشده با نام فایل .aidl مطابقت دارد، اما با پسوند .java. برای مثال، IRemoteService.aidl به IRemoteService.java تبدیل می‌شود.

اگر از «استودیو Android» استفاده می‌کنید، ساخت افزایشی کلاس binder را تقریباً بلافاصله تولید می‌کند. اگر از Android Studio استفاده نمی‌کنید، ابزار Gradle دفعه بعد که برنامه‌تان را می‌سازید، کلاس binder را تولید می‌کند. به‌محض اینکه نوشتن فایل .aidl را تمام کردید، پروژه‌تان را با gradle assembleDebug یا gradle assembleRelease بسازید تا کدتان بتواند به کلاس تولیدشده پیوند داده شود.

پیاده‌سازی میانای کاربری

وقتی برنامه‌تان را می‌سازید، ابزارهای کیت توسعه نرم‌افزار Android یک فایل میانای .java به‌نام فایل .aidl شما تولید می‌کنند. میانای تولیدشده شامل یک زیرکلاس به‌نام Stub است که پیاده‌سازی انتزاعی میانای والد آن است، مانند YourInterface.Stub، و همه روش‌ها را از فایل .aidl اعلام می‌کند.

توجه: Stub همچنین چند روش کمکی را تعریف می‌کند، به‌ویژه asInterface()، که یک IBinder را می‌گیرد، معمولاً همان موردی که به روش فراخوانی برگشتی onServiceConnected() کارخواه ارسال می‌شود، و نمونه‌ای از میانای تهی‌سازه را برمی‌گرداند. برای جزئیات بیشتر درباره نحوه انجام این تبدیل، بخش فراخوانی روش 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 (یک Binder) است، که رابط IPC را برای سرویس تعریف می‌کند. در مرحله بعد، این نمونه درمعرض دید مشتریان قرار می‌گیرد تا بتوانند با سرویس تعامل داشته باشند.

هنگام پیاده‌سازی میانای AIDL، از چند قانون مطلع باشید:

  • تضمینی وجود ندارد که تماس‌های ورودی در رشته اصلی اجرا شوند، بنابراین باید از همان ابتدا به چندرشته‌ای فکر کنید و سرویس خود را به‌درستی بسازید تا ازنظر رشته‌ای ایمن باشد.
  • به‌طور پیش‌فرض، تماس‌های IPC همگام هستند. اگر می‌دانید که تکمیل درخواست توسط سرویس بیش‌از چند میلی‌ثانیه طول می‌کشد، آن را از رشته اصلی فعالیت فراخوانی نکنید. ممکن است برنامه را متوقف کند و درنتیجه Android کادر گفتگوی «برنامه پاسخ نمی‌دهد» را نمایش دهد. آن را از رشته‌ای جداگانه در کارخواه فراخوانی کنید.
  • فقط انواع استثنای فهرست‌شده در اسناد مرجع برای 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 (سطح میانای برنامه‌سازی کاربردی ۲۹ یا بالاتر)، می‌توانید اشیاء Parcelable را مستقیماً در AIDL تعریف کنید. انواعی که به‌عنوان آرگومان‌های میانای AIDL پشتیبانی می‌شوند و سایر parcelableها نیز در اینجا پشتیبانی می‌شوند. این کار از کار اضافی برای نوشتن دستی کد مرتب‌سازی و کلاس سفارشی جلوگیری می‌کند. بااین‌حال، این کار یک ساختار خالی نیز ایجاد می‌کند. اگر دسترسی‌دهنده سفارشی یا عملکرد دیگری موردنظر است، به‌جای آن Parcelable را پیاده‌سازی کنید.

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect {
    int left;
    int top;
    int right;
    int bottom;
}

نمونه کد قبلی به‌طور خودکار یک کلاس Java با فیلدهای عدد صحیح left، top، right، و bottom تولید می‌کند. همه کدهای مرتب‌سازی مربوطه به‌طور خودکار پیاده‌سازی می‌شوند و می‌توان از شیء مستقیماً بدون نیاز به افزودن هیچ پیاده‌سازی‌ای استفاده کرد.

همچنین می‌توانید کلاس سفارشی را ازطریق میانای IPC از یک فرایند به فرایند دیگر ارسال کنید. بااین‌حال، مطمئن شوید کد کلاس شما برای طرف دیگر کانال IPC دردسترس باشد و کلاس شما باید از میانای Parcelable پشتیبانی کند. پشتیبانی از Parcelable مهم است زیرا به سیستم Android اجازه می‌دهد اشیا را به عناصر اولیه‌ای تجزیه کند که می‌توانند در فرایندها مرتب شوند.

برای ایجاد کلاس سفارشی که از Parcelable پشتیبانی می‌کند، کارهای زیر را انجام دهید:

  1. کلاس خود را طوری بسازید که میانای Parcelable را پیاده‌سازی کند.
  2. writeToParcel را پیاده‌سازی کنید که وضعیت فعلی شیء را می‌گیرد و آن را در Parcel می‌نویسد.
  3. فیلد ثابتی به‌نام CREATOR به کلاس خود اضافه کنید که شیئی باشد که میانای Parcelable.Creator را پیاده‌سازی می‌کند.
  4. درنهایت، فایل .aidl را ایجاد کنید که کلاس قابل‌بسته‌بندی شما را اعلام می‌کند، همان‌طور که برای فایل Rect.aidl زیر نشان داده شده است.

    اگر از فرایند ساخت سفارشی استفاده می‌کنید، فایل .aidl را به ساختتان اضافه نکنید. این فایل .aidl، مشابه فایل سرایند در زبان C، کامپایل نمی‌شود.

‫AIDL از این روش‌ها و فیلدها در کدی که برای مرتب‌سازی و نامرتب‌سازی اشیاء شما تولید می‌کند استفاده می‌کند.

برای مثال، در اینجا یک فایل Rect.aidl برای ایجاد کلاس Rect وجود دارد که قابل‌بسته‌بندی است:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

و در اینجا مثالی از نحوه پیاده‌سازی پروتکل 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 که حاوی Parcelable هستند

اگر روشی شیء Bundle را می‌پذیرد که انتظار می‌رود حاوی parcelable باشد، مطمئن شوید که classloader مربوط به Bundle را با فراخوانی Bundle.setClassLoader(ClassLoader) قبل‌از تلاش برای خواندن از Bundle تنظیم کنید. درغیراین‌صورت، با ClassNotFoundException مواجه می‌شوید، حتی اگر parcelable به‌درستی در برنامه‌تان تعریف شده باشد.

برای مثال، فایل نمونه .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 دریافت می‌کنید. برای تبدیل پارامتر برگردانده‌شده به نوع YourInterface، YourInterfaceName.Stub.asInterface((IBinder)service) را فراخوانی کنید.
  6. متدهایی را که در میانای خود تعریف کرده‌اید فراخوانی کنید. همیشه استثناهای DeadObjectException را که هنگام قطع اتصال ایجاد می‌شوند به‌دام بیندازید. همچنین، استثناهای SecurityException را به‌دام می‌اندازد که وقتی دو فرایند دخیل در فراخوانی روش IPC دارای تعاریف AIDL متناقض باشند، ایجاد می‌شوند.
  7. برای قطع اتصال، Context.unbindService() را با نمونه میانای خود فراخوانی کنید.

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

  • اشیا در سراسر فرایندها شمارش مرجع می‌شوند.
  • می‌توانید اشیای ناشناس را به‌عنوان آرگومان‌های متد ارسال کنید.

برای اطلاعات بیشتر درباره مقید کردن به سرویس، نمای کلی سرویس‌های مقید را بخوانید.

در اینجا کد نمونه‌ای وجود دارد که فراخوانی سرویس ایجادشده توسط AIDL را نشان می‌دهد، که از نمونه سرویس ازراه‌دور در پروژه 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);
            }
        }
    }
}