Android इंटरफ़ेस डेफ़िनिशन लैंग्वेज (एआईडीएल)

Android इंटरफ़ेस डेफ़िनिशन लैंग्वेज (AIDL), अन्य आईडीएल से मिलती-जुलती है: इसकी मदद से, प्रोग्रामिंग इंटरफ़ेस तय किया जा सकता है. क्लाइंट और सेवा, दोनों इंटरप्रोसेस कम्यूनिकेशन (आईपीसी) का इस्तेमाल करके एक-दूसरे के साथ कम्यूनिकेट करने के लिए, इस इंटरफ़ेस पर सहमत होते हैं.

Android पर, आम तौर पर एक प्रोसेस, दूसरी प्रोसेस की मेमोरी को ऐक्सेस नहीं कर सकती. बातचीत करने के लिए, उन्हें अपने ऑब्जेक्ट को ऐसे प्राइमिटिव में बांटना होगा जिन्हें ऑपरेटिंग सिस्टम समझ सके. साथ ही, वे आपके लिए उस सीमा के ऑब्जेक्ट को मार्शल कर सकें. मार्शल करने के लिए कोड लिखना मुश्किल होता है. इसलिए, Android आपके लिए AIDL की मदद से इसे मैनेज करता है.

ध्यान दें: AIDL सिर्फ़ तब ज़रूरी होता है, जब अलग-अलग ऐप्लिकेशन के क्लाइंट को आईपीसी के लिए आपकी सेवा को ऐक्सेस करने की अनुमति दी जाती है. साथ ही, आपको अपनी सेवा में मल्टीथ्रेडिंग को मैनेज करना हो. अगर आपको अलग-अलग ऐप्लिकेशन में एक साथ आईपीसी करने की ज़रूरत नहीं है, तो Binder लागू करके अपना इंटरफ़ेस बनाएं. अगर आपको आईपीसी करना है, लेकिन मल्टीथ्रेडिंग को हैंडल करने की ज़रूरत नहीं है, तो Messenger का इस्तेमाल करके अपना इंटरफ़ेस लागू करें. इसके बावजूद, एआईडीएल को लागू करने से पहले ज़रूरी सेवाओं को अच्छी तरह से समझ लें.

अपना ��आईडीएल इंटरफ़ेस डिज़ाइन करने से पहले, ध्यान रखें कि एआईडीएल इंटरफ़ेस पर किए जाने वाले कॉल, डायरेक्ट फ़ंक्शन कॉल होते हैं. उस थ्रेड के बारे में कोई अनुमान न लगाएं जिसमें कॉल होता है. कॉल, लोकल प्रोसेस की थ्रेड से है या रिमोट प्रोसेस की, इस आधार पर अलग-अलग होता है:

  • स्थानीय प्रोसेस से किए गए कॉल, उसी थ्रेड में लागू होते हैं जिससे कॉल किया जा रहा है. अगर यह आपका मुख्य यूज़र इंटरफ़ेस (यूआई) थ्रेड है, तो वह थ्रेड AIDL इंटरफ़ेस में चलता रहता है. अगर यह कोई दूसरी थ्रेड है, तो यह सेवा में आपका कोड लागू करती है. इसलिए, अगर सेवा को सिर्फ़ लोकल थ्रेड ऐक्सेस कर रहे हैं, तो यह कंट्रोल किया जा सकता है कि इसमें कौनसे थ्रेड एक्ज़ीक्यूट किए जा रहे हैं. हालांकि, अगर ऐसा है, तो AIDL का इस्तेमाल बिलकुल न करें. इसके बजाय, Binder को लागू करके इंटरफ़ेस बनाएं.
  • रिमोट प्रोसेस से आने वाले कॉल, थ्रेड पूल से भेजे जाते हैं. प्लैटफ़ॉर्म, इस पूल को आपकी प्रोसेस में मैनेज करता है. अनजान थ्रेड से आने वाले कॉल के लिए तैयार रहें. साथ ही, एक ही समय पर कई कॉल आने की संभावना भी होती है. दूसरे शब्दों में, किसी AIDL इंटरफ़ेस को लागू करने के लिए, यह ज़रूरी है कि वह पूरी तरह से थ्रेड-सेफ़ हो. एक ही रिमोट ऑब्जेक्ट पर, एक थ्रेड से किए गए कॉल, रिसीवर की ओर से क्रम में आते हैं.
  • oneway कीवर्ड की मदद से, रिमोट कॉल के व्यवहार में बदलाव किया जाता है. इसका इस्तेमाल करने पर, रिमोट कॉल ब्लॉक नहीं होता. यह लेन-देन का डेटा भेजता है और तुरंत वापस आ जाता है. इंटरफ़ेस लागू करने पर, इस�� Binder थ्रेड पूल से सामान्य कॉल के तौर पर मिलता है. अगर oneway का इस्तेमाल लोकल कॉल के साथ किया जाता है, तो इससे कोई फ़र्क़ नहीं पड़ता. साथ ही, कॉल अब भी सिंक होता रहता है.

AIDL इंटरफ़ेस तय करना

Java प्रोग्रामिंग भाषा के सिंटैक्स का इस्तेमाल करके, .aidl फ़ाइल में अप��ा AIDL इंटरफ़ेस तय करें. इसके बाद, इसे सेवा को होस्ट करने वाले ऐप्लिकेशन और सेवा से बंधे किसी भी अन्य ऐप्लिकेशन, दोनों के सोर्स कोड में src/ डायरेक्ट्री में सेव करें.

.aidl फ़ाइल वाले हर ऐप्लिकेशन को बिल्ड करने पर, Android SDK टूल .aidl फ़ाइल के आधार पर IBinder इंटरफ़ेस जनरेट करते हैं और उसे प्रोजेक्ट की gen/ डायरेक्ट्री में सेव करते हैं. सेवा के लिए, IBinder इंटरफ़ेस को ज़रूरत के मुताबिक लागू करना ज़रूरी है. इसके बाद, क्लाइंट ऐप्लिकेशन आईपीसी का इस्तेमाल करने के लिए, IBinder की सेवा और कॉल के तरीकों से बाइंड कर सकते हैं.

एआईडीएल का इस्तेमाल करके बाउंड सेवा बनाने के लिए, यह तरीका अपनाएं. इसके बारे में इस सेक्शन में बताया गया है:

  1. .aidl फ़ाइल बनाना

    यह फ़ाइल, प्रोग्रामिंग इंटरफ़ेस को मेथड सिग्नेचर के साथ तय करती है.

  2. इंटरफ़ेस लागू करना

    Android SDK टूल, आपकी .aidl फ़ाइल के आधार पर, Java प्रोग्रामिंग भाषा में इंटरफ़ेस जनरेट करते हैं. इस इंटरफ़ेस में Stub नाम की एक इनर ऐब्स्ट्रैक्ट क्लास है, जो Binder का विस्तार करती है और आपके एआईडीएल इंटरफ़ेस से तरीकों को लागू करती है. आपको Stub क्लास को बढ़ाना होगा और मेथड को लागू करना होगा.

  3. क्लाइंट को इंटरफ़ेस दिखाना

    Stub क्लास को लागू करने के लिए, Service लागू करें और onBind() को बदलें.

चेतावनी: पहली रिलीज़ के बाद, अपने AIDL इंटरफ़ेस में किए गए किसी भी बदलाव को, पुराने वर्शन के साथ काम करना चाहिए. ऐसा इसलिए, ताकि आपकी सेवा का इस्तेमाल करने वाले अन्य ऐप्लिकेशन काम करना बंद न कर दें. इसका मतलब है कि आपकी .aidl फ़ाइल को दूसरे ऐप्लिकेशन में कॉपी करना ज़रूरी है, ताकि वे आपकी सेवा के इंटरफ़ेस को ऐक्सेस कर सकें. इसलिए, आपके लिए ओरिजनल इंटरफ़ेस पर काम करना ज़रूरी है.

.aidl फ़ाइल बनाएं

AIDL, सिंटैक्स का इस्तेमाल करता है. इससे, एक या एक से ज़्यादा तरीकों वाले इंटरफ़ेस का एलान किया जा सकता है. ये तरीके, पैरामीटर ले सकते हैं और वैल्यू दिखा सकते हैं. पैरामीटर और रिटर्न वैल्यू किसी भी टाइप की हो सकती हैं. यहां तक कि, एआईडीएल से जनरेट किए गए अन्य इंटरफ़ेस भी हो सकते हैं.

आपको Java प्रोग्रामिंग लैंग्वेज का इस्तेमाल करके, .aidl फ़ाइल बनानी होगी. हर .aidl फ़ाइल में एक इंटरफ़ेस होना चाहिए. साथ ही, इसमें सिर्फ़ इंटरफ़ेस डिक्लेरेशन और तरीकों के हस्ताक्षर होने चाहिए.

डिफ़ॉल्ट रूप से, AIDL इन डेटा टाइप के साथ काम करता है:

  • Java प्रोग्रामिंग भाषा में सभी प्रिमिटिव टाइप (जैसे कि int, long, char, boolean वगैरह)
  • किसी भी तरह की सरणियां, जैसे कि int[] या MyParcelable[]
  • String
  • CharSequence
  • List

    List में मौजूद सभी एलिमेंट, इस सूची में मौजूद डेटा टाइप में से किसी एक के होने चाहिए. इसके अलावा, वे एआईडीएल से जनरेट किए गए किसी इंटरफ़ेस या पार्सल किए जा सकने वाले किसी ऐसे आइटम में से किसी एक के भी होने चाहिए जिसका आपने एलान किया हो. List को वैकल्पिक तौर पर पैरामीटर वाले टाइप क्लास के तौर पर इस्तेमाल किया जा सकता है, जैसे कि List<String>. दूसरे पक्ष को मिलने वाली असल क्लास हमेशा ArrayList होती है. हालांकि, List इंटरफ़ेस का इस्तेमाल करने के लिए, तरीका जनरेट किया जाता है.

  • Map

    Map के सभी एलिमेंट, इस सूची में बताए गए डेटा टाइप या एआईडीएल के जनरेट किए गए अन्य इंटरफ़ेस या पार्स किए जा सकने वाले ऐसे डेटा टाइप में से एक होने चाहिए जिनका आपने एलान किया है. पैरामीटर वाले मैप, जैसे कि Map<String,Integer> फ़ॉर्म वाले मैप काम नहीं करते. दूसरी साइड को मिलने वाली असल कंक्रीट क्लास हमेशा HashMap होती है. हालांकि, Map इंटरफ़ेस का इस्तेमाल करने के लिए यह तरीका जनरेट किया जाता है. Map के विकल्प के तौर पर, Bundle का इस्तेमाल करें.

आपको हर उस तरह के import स्टेटमेंट को शामिल करना होगा जो पहले शामिल नहीं किया गया है. भले ही, वे उसी पैकेज में शामिल हों जिसमें आपका इंटरफ़ेस है.

सेवा इंटरफ़ेस तय करते समय, इन बातों का ध्यान रखें:

  • तरीके में शून्य या उससे ज़्यादा पैरामीटर हो सकते हैं. साथ ही, वे ��ोई वैल्यू या कोई वैल्यू नहीं दिखा सकते.
  • सभी नॉन-प्रिमिटिव पैरामीटर के लिए, डायरेक्शनल टैग की ज़रूरत होती है. इससे पता चलता है कि डेटा किस तरह से जाता है: in, out या inout (नीचे दिया गया उदाहरण देखें).

    प्रिमिटिव, String, IBinder, और एआईडीएल के जनरेट किए गए इंटरफ़ेस, डिफ़ॉल्ट रूप से in होते हैं. इनके अलावा, ये इंटरफ़ेस नहीं बनाए जा सकते.

    चेतावनी: सिर्फ़ ज़रूरी निर्देश दें, क्योंकि पैरामीटर को मार्शल करने की प्रोसेस महंगी होती है.

  • .aidl फ़ाइल में शामिल सभी कोड टिप्पणियां, जनरेट किए गए IBinder इंटरफ़ेस में शामिल होती हैं. हालांकि, इंपोर्ट और पैकेज स्टेटमेंट से पहले की गई टिप्पणियां शामिल नहीं होती हैं.
  • स्ट्रिंग और 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 टूल आपके प्रोजेक्ट की gen/ डायरेक्ट्री में IBinder इंटरफ़ेस फ़ाइल ��नरेट करते हैं. जनरेट की गई फ़ाइल का नाम .aidl फ़ाइल के नाम से मेल खाता है. हालांकि, इसका एक्सटेंशन .java है. उदाहरण के लिए, IRemoteService.aidl से IRemoteService.java मिलता है.

Android Studio का इस्तेमाल करने पर, इंक्रीमेंटल बिल्ड, बाइंडर क्लास को तुरंत जनरेट करता है. अगर Android Studio का इस्तेमाल नहीं किया जाता है, तो अगली बार ऐप्लिकेशन बनाने पर Gradle टूल, बाइंडर क्लास जनरेट करता है. .aidl फ़ाइल लिखने के बाद, gradle assembleDebug या gradle assembleRelease का इस्तेमाल करके अपना प्रोजेक्ट बनाएं, ताकि आपका कोड जनरेट की गई क्लास से लिंक हो सके.

इंटरफ़ेस लागू करना

ऐप्लिकेशन बनाते समय, Android SDK टूल एक .java इंटरफ़ेस फ़ाइल जनरेट करते हैं. इस फ़ाइल का नाम आपकी .aidl फ़ाइल के नाम पर रखा जाएगा. जनरेट किए गए इंटरफ़ेस में Stub नाम का एक सबक्लास शामिल होता है. यह YourInterface.Stub जैसे पैरंट इंटरफ़ेस का एब्स्ट्रैक्ट वर्शन होता है. साथ ही, इसमें .aidl फ़ाइल के सभी तरीकों का एलान किया जाता है.

ध्यान दें: Stub में कुछ हेल्पर तरीके भी तय किए गए हैं. इनमें सबसे अहम asInterface() है, जो IBinder लेता है. आम तौर पर, यह क्लाइंट के onServiceConnected() कॉलबैक तरीके में पास किया जाता है और स्टब इंटरफ़ेस का इंस्टेंस दिखाता है. इस कास्ट को बनाने के तरीके के बारे में ज़्यादा जानने के लिए, IPC का कोई तरीका कॉल करना सेक्शन देखें.

.aidl से जनरेट किए गए इंटरफ़ेस को लागू करने के लिए, जनरेट किए गए Binder इंटरफ़ेस को बढ़ाएं, जैसे कि YourInterface.Stub. साथ ही, .aidl फ़ाइल से इनहेरिट किए गए तरीकों को लागू करें.

यहां IRemoteService नाम के इंटरफ़ेस को लागू करने का उदाहरण दिया गया है. इसे पिछले IRemoteService.aidl उदाहरण में बताए गए तरीके से, किसी अनाम इंस्टेंस का इस्तेमाल करके लागू किया गया है:

Kotlin

private val binder = object : IRemoteService.Stub() {

    override fun getPid(): Int =
            Process.myPid()

    override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String
    ) {
        // Does nothing.
    }
}

Java

private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing.
    }
};

अब binder, Stub क्लास (Binder) का एक इंस्टेंस है, जो सेवा के लिए IPC इंटरफ़ेस तय करता है. अगले चरण में, इस इंस्टेंस को क्लाइंट को दिखाया जाता है, ताकि वे सेवा से इंटरैक्ट कर सकें.

अपना AIDL इंटरफ़ेस लागू करते समय, इन नियमों का ध्यान रखें:

  • यह ज़रूरी नहीं है कि इनकमिंग कॉल, मुख्य थ्रेड पर ही प्रोसेस हों. इसलिए, आपको शुरू से ही मल्टीथ्रेडिंग के बारे में सोचना होगा. साथ ही, अपनी सेवा को थ्रेड-सेफ़ बनाने के लिए सही तरीके से बनाना होगा.
  • डिफ़ॉल्ट रूप से, आईपीसी कॉल सिंक्रोनस होते हैं. अगर आपको पता है कि किसी अनुरोध को पूरा करने में सेवा को कुछ मिलीसेकंड से ज़्यादा समय लगता है, तो उसे गतिविधि के मुख्य थ्रेड से न बुलाएं. इससे ऐप्लिकेशन हैंग हो सकता है. इससे Android पर "ऐप्लिकेशन काम नहीं कर रहा है" डायलॉग दिखने लगेगा. इसे क्लाइंट में किसी दूसरे थ्रेड से कॉल करें.
  • Parcel.writeException() के लिए रेफ़रंस दस्तावेज़ में बताए गए अपवाद टाइप ही कॉल करने वाले को भेजे जाते हैं.

क्लाइंट को इंटरफ़ेस दिखाना

अपनी सेवा के लिए इंटरफ़ेस लागू करने के बाद, आपको इसे क्लाइंट के लिए उपलब्ध कराना होगा, ताकि वे उससे कनेक्ट कर सकें. अपनी सेवा के लिए इंटरफ़ेस दिखाने के लिए, Service को एक्सटेंड़ करें और onBind() को लागू करें. इससे, आपकी क्लास का एक इंस्टेंस दिखेगा, जो जनरेट किए गए Stub को लागू करता है. इस बारे में पिछले सेक्शन में बताया गया है. यहां एक उदाहरण के तौर पर ऐसी सेवा दी गई है जो क्लाइंट को IRemoteService उदाहरण इंटरफ़ेस दिखाती है.

Kotlin

class RemoteService : Service() {

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        // Return the interface.
        return binder
    }


    private val binder = object : IRemoteService.Stub() {
        override fun getPid(): Int {
            return Process.myPid()
        }

        override fun basicTypes(
                anInt: Int,
                aLong: Long,
                aBoolean: Boolean,
                aFloat: Float,
                aDouble: Double,
                aString: String
        ) {
            // Does nothing.
        }
    }
}

Java

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface.
        return binder;
    }

    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing.
        }
    };
}

अब, जब कोई क्लाइंट, जैसे कि कोई गतिविधि, इस सेवा से कनेक्ट करने के लिए bindService() को कॉल करता है, तो क्लाइंट के onServiceConnected() कॉलबैक को सेवा के onBind() तरीके से दिखाया गया binder इंस्टेंस मिलता है.

क्लाइंट के पास इंटरफ़ेस क्लास का ऐक्सेस भी होना चाहिए. इसलिए, अगर क्लाइंट और सेवा अलग-अलग ऐप्लिकेशन में हैं, तो क्लाइंट के ऐप्लिकेशन में src/ डायरेक्ट्री में .aidl फ़ाइल की कॉपी होनी चाहिए. यह android.os.Binder इंटरफ़ेस जनरेट करती है, जिससे क्लाइंट को AIDL तरीकों का ऐक्सेस मिलता है.

जब क्लाइंट को onServiceConnected() कॉलबैक में IBinder मिलता है, तो उसे YourServiceInterface.Stub.asInterface(service) को कॉल करना होगा, ताकि लौटाए गए पैरामीटर को YourServiceInterface टाइप पर कास्ट किया जा सके:

Kotlin

var iRemoteService: IRemoteService? = null

val mConnection = object : ServiceConnection {

    // Called when the connection with the service is established.
    override fun onServiceConnected(className: ComponentName, service: IBinder) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service)
    }

    // Called when the connection with the service disconnects unexpectedly.
    override fun onServiceDisconnected(className: ComponentName) {
        Log.e(TAG, "Service has unexpectedly disconnected")
        iRemoteService = null
    }
}

Java

IRemoteService iRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established.
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the preceding example for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service.
        iRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly.
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        iRemoteService = null;
    }
};

ज़्यादा सैंपल कोड के लिए, ApiDemos ��ें RemoteService.java क्लास देखें.

आईपीसी के ज़रिए ऑब्जेक्ट भेजना

Android 10 (एपीआई लेवल 29 या इसके बाद के वर्शन) में, Parcelable ऑब्जेक्ट को सीधे तौर पर एआईडीएल में तय किया जा सकता है. यहां वे टाइप भी काम करते हैं जो AIDL इंटरफ़ेस आर्ग्युमेंट और अन्य पार्सल किए जा सकने वाले आइटम के तौर पर काम करते हैं. इससे, मैन्युअल तरीके से मार्शलिंग कोड और कस्टम क्लास लिखने की ज़रूरत नहीं पड़ती. हालांकि, इससे एक बेयर स्ट्रक्चर भी बनता है. अगर आपको कस्टम ऐक्सेस करने वाले या अन्य फ़ंक्शन चाहिए, तो इसके बजाय Parcelable लागू करें.

package android.graphics;

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

ऊपर दिया गया कोड सैंपल, अपने-आप एक Java क्लास जनरेट करता है. इसमें पूर्णांक फ़ील्ड left, top, right, और bottom होते हैं. काम का सारा मार्शलिंग कोड अपने-आप लागू हो जाता है. साथ ही, ऑब्जेक्ट का इस्तेमाल सीधे तौर पर किया जा सकता है. इसके लिए, कोई कोड जोड़ने की ज़रूरत नहीं होती.

आईपीसी इंटरफ़ेस का इस्तेमाल करके भी, कस्टम क्लास को एक प्रोसेस से दूसरी प्रोसेस में भेजा जा सकता है. हालांकि, पक्का करें कि आपकी क्लास का कोड, आईपीसी चैनल के दूसरे हिस्से के लिए उपलब्ध हो. साथ ही, आपकी क्लास में Parcelable इंटरफ़ेस काम करना चाहिए. Parcelable का इस्तेमाल करना ज़रूरी है, क्योंकि इससे Android सिस्टम, ऑब्जेक्ट को प्राइमिटिव में बांट देता है. इन प्राइमिटिव को सभी प्रोसेस में मार्शल कि��ा जा सकता है.

Parcelable के साथ काम करने वाली कस्टम क्लास बनाने के लिए, यह तरीका अपनाएं:

  1. अपनी क्लास में Parcelable इंटरफ़ेस लागू करें.
  2. writeToParcel लागू करें, जो ऑब्जेक्ट की मौजूदा स्थिति को लेता है और उसे Parcel में लिखता है.
  3. अपनी क्लास में CREATOR नाम का एक स्टैटिक फ़ील्ड जोड़ें, जो Parcelable.Creator इंटरफ़ेस को लागू करने वाला एक ऑब्जेक्ट हो.
  4. आखिर में, एक .aidl फ़ाइल बनाएं, जिसमें आपकी पार्सल की जा सकने वाली क्लास के बारे में बताया गया हो. जैसा कि यहां दी गई Rect.aidl फ़ाइल में दिखाया गया है.

    अगर कस्टम बिल्ड प्रोसेस का इस्तेमाल किया जा रहा है, तो अपने बिल्ड में .aidl फ़ाइल न जोड़ें. C भाषा की हेडर फ़ाइल की तरह ही, इस .aidl फ़ाइल को कंपाइल नहीं किया जाता.

AIDL, आपके ऑब्जेक्ट को मार्शल और अनमार्शल करने के लिए, जनरेट किए गए कोड में इन तरीकों और फ़ील्ड का इस्तेमाल करता है.

उदाहरण के लिए, यहां दी गई Rect.aidl फ़ाइल की मदद से Rect क्लास बनाई जा सकती है, जिसे पार्स किया जा सकता है:

package android.graphics;

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

यहां एक उदाहरण में बताया गया है कि Rect क्लास, Parcelable प्रोटोकॉल को कैसे लागू करती है.

Kotlin

import android.os.Parcel
import android.os.Parcelable

class Rect() : Parcelable {
    var left: Int = 0
    var top: Int = 0
    var right: Int = 0
    var bottom: Int = 0

    companion object CREATOR : Parcelable.Creator<Rect> {
        override fun createFromParcel(parcel: Parcel): Rect {
            return Rect(parcel)
        }

        override fun newArray(size: Int): Array<Rect?> {
            return Array(size) { null }
        }
    }

    private constructor(inParcel: Parcel) : this() {
        readFromParcel(inParcel)
    }

    override fun writeToParcel(outParcel: Parcel, flags: Int) {
        outParcel.writeInt(left)
        outParcel.writeInt(top)
        outParcel.writeInt(right)
        outParcel.writeInt(bottom)
    }

    private fun readFromParcel(inParcel: Parcel) {
        left = inParcel.readInt()
        top = inParcel.readInt()
        right = inParcel.readInt()
        bottom = inParcel.readInt()
    }

    override fun describeContents(): Int {
        return 0
    }
}

Java

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }

    public int describeContents() {
        return 0;
    }
}

Rect क्लास में मार्शल करना आसान है. Parcel में लिखी जा सकने वाली अन्य वैल्यू देखने के लिए, Parcel पर मौजूद अन्य तरीकों पर एक नज़र डालें.

चेतावनी: दूसरी प्रोसेस से डेटा पाने पर, सुरक्षा से जुड़े असर को ध्यान में रखें. इस स्थिति में, Rect, Parcel की चार संख्याएं पढ़ता है, लेकिन यह पक्का करना आपकी ज़िम्मेदारी है कि ये कॉलर जो भी करने की कोशिश कर रहे हैं उनके मानों की मान्य सीमा में हों. अपने ऐप्लिकेशन को मैलवेयर से सुरक्षित रखने के बारे में ज़्यादा जानकारी के लिए, सुरक्षा सलाह देखें.

ऐसे तरीके जिनमें Parcelable वाले बंडल आर्ग्युमेंट शामिल हैं

अगर किसी तरीके में ऐसा Bundle ऑब्जेक्ट स्वीकार किया जाता है जिसमें पार्स किए जा सकने वाले ऑब्जेक्ट शामिल होने चाहिए, तो पक्का करें कि आपने 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);
}
जैसा कि नीचे दिए गए उदाहरण में दिखाया गया है, Rect को पढ़ने से पहले ClassLoader को Bundle में साफ़ तौर पर सेट किया गया है:

Kotlin

private val binder = object : IRectInsideBundle.Stub() {
    override fun saveRect(bundle: Bundle) {
      bundle.classLoader = classLoader
      val rect = bundle.getParcelable<Rect>("rect")
      process(rect) // Do more with the parcelable.
    }
}

Java

private final IRectInsideBundle.Stub binder = new IRectInsideBundle.Stub() {
    public void saveRect(Bundle bundle){
        bundle.setClassLoader(getClass().getClassLoader());
        Rect rect = bundle.getParcelable("rect");
        process(rect); // Do more with the parcelable.
    }
};

IPC का कोई तरीका कॉल करना

AIDL की मदद से तय किए गए किसी रिमोट इंटरफ़ेस को कॉल करने के ल��ए, अपनी कॉलिंग क्लास में यह तरीका अपनाएं:

  1. .aidl फ़ाइल को प्रोजेक्ट src/ डायरेक्ट्री में शामिल करें.
  2. IBinder इंटरफ़ेस का एक इंस्टेंस डिक्लेयर करें, जो एआईडीएल के आधार पर जनरेट होता है.
  3. ServiceConnection लागू करें.
  4. Context.bindService() को कॉल करें, जो आपका ServiceConnection लागू किया गया है.
  5. onServiceConnected() को लागू करने पर, आपको IBinder का एक इंस्टेंस मिलता है, जिसे service कहा जाता है. रिटर्न किए गए पैरामीटर को YourInterface टाइप में कास्ट करने के लिए, YourInterfaceName.Stub.asInterface((IBinder)service) को कॉल करें.
  6. अपने इंटरफ़ेस में बताए गए तरीकों को कॉल करें. DeadObjectException एक्सप्शन को हमेशा ट्रैप करें. ये एक्सप्शन, कनेक्शन टूटने पर ट्रिगर होते हैं. साथ ही, ट्रैप SecurityException अपवाद तब होते हैं, जब आईपीसी तरीके से कॉल में शामिल दो प्रोसेस में अलग-अलग एआईडीएल परिभाषाएं होती हैं.
  7. डिसकनेक्ट करने के लिए, अपने इंटरफ़ेस के इंस्टेंस के साथ Context.unbindService() को कॉल करें.

आईपीसी सेवा को कॉल करते समय, इन बातों का ध्यान रखें:

  • ऑब्जेक्ट की गिनती, सभी प्रोसेस में की जाती है.
  • तरीके के आर्ग्युमेंट के तौर पर, पहचान छिपाने वाले ऑब्जेक्ट भेजे जा सकते हैं.

किसी सेवा से बाइंड करने के बारे में ज़्यादा जानने के लिए, बाइंड की गई सेवाओं की खास जानकारी पढ़ें.

यहां कुछ सैंपल कोड दिए गए हैं. इनसे, ApiDemos प्रोजेक्ट में मौजूद रिमोट सेवा के सैंपल से, एआईडीएल से बनाई गई सेवा को कॉल करने का तरीका पता चलता है.

Kotlin

private const val BUMP_MSG = 1

class Binding : Activity() {

    /** The primary interface you call on the service.  */
    private var mService: IRemoteService? = null

    /** Another interface you use on the service.  */
    internal var secondaryService: ISecondary? = null

    private lateinit var killButton: Button
    private lateinit var callbackText: TextView
    private lateinit var handler: InternalHandler

    private var isBound: Boolean = false

    /**
     * Class for interacting with the main interface of the service.
     */
    private val mConnection = object : ServiceConnection {

        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service)
            killButton.isEnabled = true
            callbackText.text = "Attached."

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService?.registerCallback(mCallback)
            } catch (e: RemoteException) {
                // In this case, the service crashes before we can
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(
                    this@Binding,
                    R.string.remote_service_connected,
                    Toast.LENGTH_SHORT
            ).show()
        }

        override fun onServiceDisconnected(className: ComponentName) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&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)
            }
        }
    }
}

Java

public static class Binding extends Activity {
    /** The primary interface we are calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary secondaryService = null;

    Button killButton;
    TextView callbackText;

    private InternalHandler handler;
    private boolean isBound;

    /**
     * Standard initialization of this activity. Set up the UI, then wait
     * for the user to interact with it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button taps.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(unbindListener);
        killButton = (Button)findViewById(R.id.kill);
        killButton.setOnClickListener(killListener);
        killButton.setEnabled(false);

        callbackText = (TextView)findViewById(R.id.callback);
        callbackText.setText("Not attached.");
        handler = new InternalHandler(callbackText);
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service is
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            killButton.setEnabled(true);
            callbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service crashes before we can even
                // do anything with it. We can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service is
            // unexpectedly disconnected&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);
            }
        }
    }
}