एआईडीएल बैकएंड

स्टब कोड जनरेट करने के लिए, AIDL बैकएंड को टारगेट किया जाता है. AIDL फ़ाइलों का इस्तेमाल करते समय, उनका इस्तेमाल हमेशा किसी खास भाषा में किया जाता है. साथ ही, उनका इस्तेमाल किसी खास रनटाइम के साथ किया जाता है. संदर्भ के हिसाब से, आपको अलग-अलग एआईडीएल बैकएंड का इस्तेमाल करना चाहिए.

नीचे दी गई टेबल में, एपीआई प्लैटफ़ॉर्म के स्थिर होने का मतलब है कि इस एपीआई प्लैटफ़ॉर्म के लिए कोड को इस तरह से कॉम्पाइल किया जा सकता है कि कोड को system.img libbinder.so बाइनरी से अलग डिलीवर किया जा सके.

AIDL में ये बैकएंड हैं:

बैकएंड भाषा एपीआई का प्लैटफ़ॉर्म सिस्टम बनाना
Java Java SDK/SystemApi (स्टैबल*) सभी
एनडीके C++ libbinder_ndk (स्टैबल*) aidl_interface
सीपीपी C++ libbinder (अस्थिर) सभी
रस्ट रस्ट libbinder_rs (stable*) aidl_interface
  • ये एपीआई प्लैटफ़ॉर्म के लिए उपलब्ध हैं. हालांकि, कई एपीआई, जैसे कि सेवा मैनेजमेंट के लिए उपलब्ध एपीआई, प्लैटफ़ॉर्म के इंटरनल इस्तेमाल के लिए ही उपलब्ध हैं. ये ऐप्लिकेशन के लिए उपलब्ध नहीं हैं. ऐप्लिकेशन में AIDL का इस्तेमाल करने के तरीके के बारे में ज़्यादा जानने के लिए, डेवलपर दस्तावेज़ देखें.
  • Rust बैकएंड को Android 12 में पेश किया गया था. NDK बैकएंड, Android 10 से उपलब्ध है.
  • Rust crate, libbinder_ndk के ऊपर बनाया गया है. इससे यह स्थिर और पोर्टेबल हो पाता है. APEX, बाइंडर क्रेट का इस्तेमाल उसी तरह करते हैं जिस तरह सिस्टम साइड पर कोई और करता है. Rust का हिस्सा, APEX में बंडल किया जाता है और उसमें शिप किया जाता है. यह सिस्टम पार्टीशन में मौजूद libbinder_ndk.so पर निर्भर करता है.

सिस्टम बनाना

बैकएंड के आधार पर, AIDL को स्टब कोड में कंपाइल करने के दो तरीके हैं. बिल्ड सिस्टम के बारे में ज़्यादा जानकारी के लिए, Soong मॉड्यूल रेफ़रंस देखें.

कोर बिल्ड सिस्टम

किसी भी cc_ या java_ Android.bp मॉड्यूल (या उनके Android.mk बराबर के मॉड्यूल) में, .aidl फ़ाइलों को सोर्स फ़ाइलों के तौर पर बताया जा सकता है. इस मामले में, NDK बैकएंड के बजाय, AIDL के Java/CPP बैकएंड का इस्तेमाल किया जाता है. साथ ही, मिलती-जुलती AIDL फ़ाइलों का इस्तेमाल करने के लिए, क्लास अपने-आप मॉड्यूल में जुड़ जाती हैं. local_include_dirs जैसे विकल्प, जो बिल्ड सिस्टम को उस मॉड्यूल में एआईडीएल फ़ाइलों के रूट पाथ के बारे में बताते हैं. इन मॉड्यूल में, aidl: ग्रुप के तहत इन विकल्पों की जानकारी दी जा सकती है. ध्यान दें कि Rust बैकएंड का इस्तेमाल सिर्फ़ Rust के साथ किया जा सकता है. rust_ मॉड्यूल को अलग तरह से मैनेज किया जाता है. इसमें, AIDL फ़ाइलों को सोर्स फ़ाइलों के तौर पर नहीं दिखाया जाता. इसके बजाय, aidl_interface मॉड्यूल एक rustlib जनरेट करता है, जिसे <aidl_interface name>-rust कहा जाता है. इसे लिंक किया जा सकता है. ज़्यादा जानकारी के लिए, Rust AIDL का उदाहरण देखें.

aidl_interface

इस बिल्ड सिस्टम के साथ इस्तेमाल किए जाने वाले टाइप, स्ट्रक्चर्ड होने चाहिए. स्ट्रक्चर्ड होने के लिए, parcels में सीधे तौर पर फ़ील्ड होने चाहिए. साथ ही, वे टारगेट भाषाओं में सीधे तौर पर तय किए गए टाइप के एलान नहीं होने चाहिए. स्ट्रक्चर्ड एआईडीएल और स्थिर एआईडीएल के बीच के अंतर के बारे में जानने के लिए, स्ट्रक्चर्ड बनाम स्थिर एआईडीएल लेख पढ़ें.

प्रकार

aidl कंपाइलर को टाइप के लिए रेफ़रंस के तौर पर इस्तेमाल किया जा सकता है. इंटरफ़ेस बनाने के बाद, इंटरफ़ेस फ़ाइल देखने के लिए aidl --lang=<backend> ... को चालू करें. aidl_interface मॉड्यूल का इस्तेमाल करने पर, out/soong/.intermediates/<path to module>/ में आउटपुट देखा जा सकता है.

Java/AIDL टाइप C++ टाइप एनडीके (NDK) टाइप जंग का टाइप
बूलियन bool bool bool
बाइट8 int8_t int8_t i8
char char16_t char16_t u16
आईएनटी int32_t int32_t i32
लंबा int64_t int64_t i64
फ़्लोट फ़्लोट फ़्लोट f32
डबल डबल डबल f64
स्ट्रिंग android::String16 std::string इन: &str
आउट: स्ट्रिंग
android.os.Parcelable android::Parcelable लागू नहीं लागू नहीं
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> इनपुट: &[T]
आउटपुट: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 इन: &[u8]
आउट: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 इनपुट: &[T]4
आउटपुट: Vec<T>
FileDescriptor android::base::unique_fd लागू नहीं binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
इंटरफ़ेस टाइप (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
पार्सल करने की सुविधा वाला प्रॉडक्ट टाइप (T) T T T
यूनियन टाइप (T)5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. Android 12 या इसके बाद के वर्शन में, बाइट ऐरे के साथ काम करने के लिए, int8_t के बजाय uint8_t का इस्तेमाल किया जाता है.

2. C++ बैकएंड, List<T> के साथ काम करता है, जहां T, String, IBinder, ParcelFileDescriptor या parcelable में से कोई एक है. Android 13 या उसके बाद के वर्शन में, T कोई भी ऐसा टाइप हो सकता है जो प्राइमटिव टाइप (इसमें इंटरफ़ेस टाइप भी शामिल हैं) के अलावा हो. AOSP का सुझाव है कि आप T[] जैसे ऐरे टाइप का इस्तेमाल करें, क्योंकि ये सभी बैकएंड में काम करते हैं.

3. NDK बैकएंड, List<T> के साथ काम करता है. यहां T, String, ParcelFileDescriptor या parcelable में से कोई एक होता है. Android 13 या उसके बाद के वर्शन में, T, ऐरे को छोड़कर कोई भी नॉन-प्राइमटिव टाइप हो सकता है.

4. Rust कोड के लिए, टाइप अलग-अलग तरीके से पास किए जाते हैं. यह इस बात पर निर्भर करता है कि वे इनपुट (एक आर्ग्युमेंट) हैं या आउटपुट (वापस की गई वैल्यू).

5. यूनियन टाइप, Android 12 और उसके बाद के वर्शन पर काम करते हैं.

6. Android 13 या इसके बाद के वर्शन में, तय साइज़ के ऐरे इस्तेमाल किए जा सकते हैं. तय साइज़ वाले ऐरे में कई डाइमेंशन हो सकते हैं (उदाहरण के लिए, int[3][4]). Java बैकएंड में, तय साइज़ वाले ऐरे को ऐरे टाइप के तौर पर दिखाया जाता है.

7. बाइंडर SharedRefBase ऑब्जेक्ट का इंस्टेंस बनाने के लिए, SharedRefBase::make\<My\>(... args ...) का इस्तेमाल करें. यह फ़ंक्शन एक std::shared_ptr\<T\> ऑब्जेक्ट बनाता है, जिसे इंटरनल तौर पर भी मैनेज किया जाता है. ऐसा तब होता है, जब बाइंडर का मालिकाना हक किसी दूसरी प्रोसेस के पास हो. ऑब्जेक्ट को किसी दूसरे तरीके से बनाने पर, उस पर दो लोगों का मालिकाना हक हो जाता है.

8. Java/AIDL टाइप byte[] भी देखें.

डायरेक्शनलिटी (इन/आउट/इनआउट)

फ़ंक्शन के लिए आर्ग्युमेंट के टाइप तय करते समय, उन्हें in, out या inout के तौर पर तय किया जा सकता है. इससे यह कंट्रोल होता है कि आईपीसी कॉल के लिए जानकारी किस दिशा में भेजी जाए. in डिफ़ॉल्ट डायरेक्शन है. इससे पता चलता है कि डेटा, कॉल करने वाले व्यक्ति से कॉल पाने वाले व्यक्ति को भेजा गया है. out का मतलब है कि डेटा, कॉल पाने वाले व्यक्ति से कॉल करने वाले व्यक्ति को भेजा जाता है. inout, इन दोनों का कॉम्बिनेशन है. हालांकि, Android टीम का सुझाव है कि आप आर्ग्युमेंट स्पेसिफ़ायर inout का इस्तेमाल न करें. अगर inout का इस्तेमाल, वर्शन वाले इंटरफ़ेस और कॉल पाने वाले पुराने व्यक्ति के साथ किया जाता है, तो कॉल करने वाले व्यक्ति के लिए मौजूद अतिरिक्त फ़ील्ड, डिफ़ॉल्ट वैल्यू पर रीसेट हो जाते हैं. Rust के लिए, सामान्य inout टाइप को &mut Vec<T> और सूची inout टाइप को &mut Vec<T> मिलता है.

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF8/UTF16

सीपीपी बैकएंड की मदद से, यह चुना जा सकता है कि स्ट्रिंग utf-8 या utf-16 में हैं. स्ट्रिंग को अपने-आप utf-8 में बदलने के लिए, उन्हें AIDL में @utf8InCpp String के तौर पर दिखाएं. NDK और Rust बैकएंड हमेशा utf-8 स्ट्रिंग का इस्तेमाल करते हैं. utf8InCpp एनोटेशन के बारे में ज़्यादा जानकारी के लिए, एआईडीएल में एनोटेशन देखें.

शून्य होने की सुविधा

@nullable का इस्तेमाल करके, उन एलिमेंट के लिए एनोटेशन जोड़े जा सकते हैं जो शून्य हो सकते हैं. nullable एनोटेशन के बारे में ज़्यादा जानने के लिए, एआईडीएल में एनोटेशन देखें.

कस्टम पार्सल करने की सुविधा

कस्टम पार्सल करने लायक ऑब्जेक्ट, ऐसा ऑब्जेक्ट होता है जिसे टारगेट बैकएंड में मैन्युअल तरीके से लागू किया जाता है. कस्टम पार्सलबल का इस्तेमाल सिर्फ़ तब करें, जब आपको किसी मौजूदा कस्टम पार्सलबल के लिए, दूसरी भाषाओं में काम करने की सुविधा जोड़नी हो. इस सुविधा को बदला नहीं जा सकता.

कस्टम पार्सल करने लायक ऑब्जेक्ट का एलान करने के लिए, AIDL को इसकी जानकारी दी जानी चाहिए. AIDL के लिए, पार्सल करने लायक ऑब्जेक्ट का एलान इस तरह दिखता है:

    package my.pack.age;
    parcelable Foo;

डिफ़ॉल्ट रूप से, यह एक Java पार्सल करने लायक ऑब्जेक्ट के तौर पर काम करता है. इसमें my.pack.age.Foo, Parcelable इंटरफ़ेस को लागू करने वाली Java क्लास है.

AIDL में कस्टम सीपीपी बैकएंड पार्सल करने के एलान के लिए, cpp_header का इस्तेमाल करें:

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

my/pack/age/Foo.h में C++ लागू करने का तरीका ऐसा दिखता है:

    #include <binder/Parcelable.h>

    class MyCustomParcelable : public android::Parcelable {
    public:
        status_t writeToParcel(Parcel* parcel) const override;
        status_t readFromParcel(const Parcel* parcel) override;

        std::string toString() const;
        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

AIDL में कस्टम NDK पार्सल करने लायक इकाई का एलान करने के लिए, ndk_header का इस्तेमाल करें:

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

android/pack/age/Foo.h में NDK लागू करने का तरीका कुछ ऐसा दिखता है:

    #include <android/binder_parcel.h>

    class MyCustomParcelable {
    public:

        binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
        binder_status_t readFromParcel(const AParcel* _Nonnull parcel);

        std::string toString() const;

        friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
        friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
    };

Android 15 में, AIDL में कस्टम Rust के पैकेज के एलान के लिए, rust_type का इस्तेमाल करें:

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

rust_crate/src/lib.rs में Rust को लागू करने का तरीका कुछ ऐसा दिखता है:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

इसके बाद, इस पार्सल किए जा सकने वाले आइटम का इस्तेमाल, AIDL फ़ाइलों में टाइप के तौर पर किया जा सकता है. हालांकि, इसे AIDL जनरेट नहीं करेगा. union में इस्तेमाल करने के लिए, CPP/NDK बैकएंड के कस्टम पार्सलबल के लिए < और == ऑपरेटर दें.

डिफ़ॉल्ट वैल्यू

स्ट्रक्चर्ड पार्सल करने की सुविधा, प्राइमिटिव, String, और इन टाइप के कलेक्शन के लिए, हर फ़ील्ड की डिफ़ॉल्ट वैल्यू तय कर सकती है.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

जब Java बैकएंड में डिफ़ॉल्ट वैल्यू मौजूद नहीं होती हैं, तो फ़ील्ड को प्राइमिटिव टाइप के लिए शून्य वैल्यू और नॉन-प्राइमिटिव टाइप के लिए null के तौर पर शुरू किया जाता है.

अन्य बैकएंड में, डिफ़ॉल्ट वैल्यू तय न होने पर, फ़ील्ड को डिफ़ॉल्ट वैल्यू के साथ शुरू किया जाता है. उदाहरण के लिए, C++ बैकएंड में, String फ़ील्ड को खाली स्ट्रिंग के तौर पर शुरू किया जाता है और List<T> फ़ील्ड को खाली vector<T> के तौर पर शुरू किया जाता है. @nullable फ़ील्ड को शून्य वैल्यू वाले फ़ील्ड के तौर पर शुरू किया जाता है.

यूनियन

AIDL यूनियन टैग किए जाते हैं और उनकी सुविधाएं सभी बैकएंड में एक जैसी होती हैं. ये डिफ़ॉल्ट रूप से पहले फ़ील्ड की डिफ़ॉल्ट वैल्यू के लिए बनाए जाते हैं. साथ ही, इनके साथ इंटरैक्ट करने का तरीका, भाषा के हिसाब से अलग-अलग होता है.

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Java का उदाहरण

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setSringField("abc");                // setter

C++ और NDK का उदाहरण

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

रस्ट का उदाहरण

Rust में, यूनियन को एनम के तौर पर लागू किया जाता है. साथ ही, इनमें साफ़ तौर पर गेट्टर और सेटर नहीं होते.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

गड़बड़ी ठीक करना

Android OS, सेवाओं के लिए गड़बड़ी के टाइप पहले से उपलब्ध कराता है, ताकि गड़बड़ियों की शिकायत करते समय उनका इस्तेमाल किया जा सके. इनका इस्तेमाल बाइंडर करता है. साथ ही, बाइंडर इंटरफ़ेस को लागू करने वाली किसी भी सेवा के लिए इनका इस्तेमाल किया जा सकता है. इनका इस्तेमाल, एआईडीएल की परिभाषा में अच्छी तरह से बताया गया है और इनके लिए, उपयोगकर्ता के तय किए गए स्टेटस या रिटर्न टाइप की ज़रूरत नहीं होती.

गड़बड़ियों वाले आउटपुट पैरामीटर

जब कोई AIDL फ़ंक्शन गड़बड़ी की सूचना देता है, तो हो सकता है कि फ़ंक्शन, आउटपुट पैरामीटर को शुरू न कर पाए या उनमें बदलाव न कर पाए. खास तौर पर, अगर गड़बड़ी लेन-देन की प्रोसेस के दौरान होने के बजाय, पैकेज को अनपैक करने के दौरान होती है, तो आउटपुट पैरामीटर में बदलाव किया जा सकता है. आम तौर पर, जब किसी AIDL फ़ंक्शन से गड़बड़ी का मैसेज मिलता है, तो सभी inout और out पैरामीटर के साथ-साथ रिटर्न वैल्यू (जो कुछ बैकएंड में out पैरामीटर की तरह काम करती है) को अनिश्चित स्थिति में माना जाना चाहिए.

गड़बड़ी की कौनसी वैल्यू इस्तेमाल करनी हैं

गड़बड़ी की कई ऐसी वैल्यू हैं जिनका इस्तेमाल किसी भी एआईडीएल इंटरफ़ेस में किया जा सकता है. हालांकि, कुछ वैल्यू के लिए खास तरीके का इस्तेमाल किया जाता है. उदाहरण के लिए, गड़बड़ी की स्थिति बताने के लिए EX_UNSUPPORTED_OPERATION और EX_ILLEGAL_ARGUMENT का इस्तेमाल किया जा सकता है. हालांकि, EX_TRANSACTION_FAILED का इस्तेमाल नहीं किया जाना चाहिए, क्योंकि इसे बुनियादी इन्फ़्रास्ट्रक्चर में खास तरीके से माना जाता है. इन पहले से मौजूद वैल्यू के बारे में ज़्यादा जानकारी के लिए, बैकएंड की खास परिभाषाएं देखें.

अगर AIDL इंटरफ़ेस को गड़बड़ी की ऐसी अतिरिक्त वैल्यू की ज़रूरत है जो गड़बड़ी के पहले से मौजूद टाइप में शामिल नहीं हैं, तो सेवा के हिसाब से गड़बड़ी की पहले से मौजूद उस खास वैल्यू का इस्तेमाल किया जा सकता है जिसे उपयोगकर्ता ने तय किया है. सेवा से जुड़ी ये गड़बड़ियां, आम तौर पर const int या int के साथ काम करने वाले enum के तौर पर, एआईडीएल इंटरफ़ेस में तय की जाती हैं. साथ ही, इन्हें बाइंडर से पार्स नहीं किया जाता.

Java में, गड़बड़ियां android.os.RemoteException जैसे अपवादों से मैप होती हैं. सेवा से जुड़े अपवादों के लिए, Java, उपयोगकर्ता की बताई गई गड़बड़ी के साथ-साथ android.os.ServiceSpecificException का इस्तेमाल करता है.

Android में नेटिव कोड, अपवादों का इस्तेमाल नहीं करता. सीपीपी बैकएंड, android::binder::Status का इस्तेमाल करता है. NDK बैकएंड, ndk::ScopedAStatus का इस्तेमाल करता है. AIDL से जनरेट किया गया हर तरीका, इनमें से कोई एक वैल्यू दिखाता है. इससे, उस तरीके की स्थिति का पता चलता है. Rust बैकएंड, NDK की तरह ही अपवाद कोड की वैल्यू का इस्तेमाल करता है. हालांकि, उपयोगकर्ता को दिखाने से पहले, वह उन्हें नेटिव Rust गड़बड़ियों (StatusCode, ExceptionCode) में बदल देता है. सेवा से जुड़ी गड़बड़ियों के लिए, दिखाया गया Status या ScopedAStatus, उपयोगकर्ता की बताई गई गड़बड़ी के साथ-साथ EX_SERVICE_SPECIFIC का इस्तेमाल करता है.

गड़बड़ी के इन टाइप को इन फ़ाइलों में देखा जा सकता है:

बैकएंड परिभाषा
Java android/os/Parcel.java
सीपीपी binder/Status.h
एनडीके android/binder_status.h
रस्ट android/binder_status.h

अलग-अलग बैकएंड का इस्तेमाल करना

ये निर्देश, Android प्लैटफ़ॉर्म कोड के लिए खास तौर पर हैं. इन उदाहरणों में, पहले से तय किए गए टाइप, my.package.IFoo का इस्तेमाल किया गया है. Rust बैकएंड का इस्तेमाल करने के तरीके के बारे में जानने के लिए, Android Rust पैटर्न पेज पर Rust AIDL का उदाहरण देखें.

इंपोर्ट के टाइप

तय किया गया टाइप, इंटरफ़ेस, पार्सल करने लायक या यूनियन हो, इसे Java में इंपोर्ट किया जा सकता है:

import my.package.IFoo;

इसके अलावा, सीपीपी बैकएंड में भी यह जानकारी देखी जा सकती है:

#include <my/package/IFoo.h>

इसके अलावा, NDK बैकएंड में भी ऐसा किया जा सकता है. इसके लिए, अतिरिक्त aidl नेमस्पेस पर ध्यान दें:

#include <aidl/my/package/IFoo.h>

इसके अलावा, Rust बैकएंड में भी ऐसा किया जा सकता है:

use my_package::aidl::my::package::IFoo;

Java में नेस्ट किए गए टाइप को इंपोर्ट किया जा सकता है. हालांकि, CPP/NDK बैकएंड में, आपको इसके रूट टाइप के लिए हेडर शामिल करना होगा. उदाहरण के लिए, my/package/IFoo.aidl में तय किए गए नेस्ट किए गए टाइप Bar को इंपोर्ट करते समय (IFoo फ़ाइल का रूट टाइप है), आपको सीपीपी बैकएंड के लिए <my/package/IFoo.h> (या एनडीके बैकएंड के लिए <aidl/my/package/IFoo.h>) शामिल करना होगा.

सेवाएं लागू करना

किसी सेवा को लागू करने के लिए, आपको नेटिव स्टब क्लास से इनहेरिट करना होगा. यह क्लास, बाइंडर ड्राइवर से निर्देश पढ़ती है और आपके लागू किए गए तरीकों को लागू करती है. मान लें कि आपके पास इस तरह की AIDL फ़ाइल है:

    package my.package;
    interface IFoo {
        int doFoo();
    }

Java में, आपको इस क्लास से एक्सटेंड करना होगा:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

सीपीपी बैकएंड में:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

NDK बैकएंड में (अतिरिक्त aidl नेमस्पेस पर ध्यान दें):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

Rust बैकएंड में:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

इसके अलावा, async Rust का इस्तेमाल करके भी ऐसा किया जा सकता है:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

रजिस्टर करना और सेवाएं पाना

Android प्लैटफ़ॉर्म पर मौजूद सेवाओं को आम तौर पर servicemanager प्रोसेस के ज़रिए रजिस्टर किया जाता है. यहां दिए गए एपीआई के अलावा, कुछ एपीआई भी सेवा की जांच करते हैं. इसका मतलब है कि अगर सेवा उपलब्ध नहीं है, तो वे तुरंत जवाब देते हैं. पूरी जानकारी के लिए, उससे जुड़ा servicemanager इंटरफ़ेस देखें. ये कार्रवाइयां सिर्फ़ Android प्लैटफ़ॉर्म के लिए कॉम्पाइल करते समय की जा सकती हैं.

Java में:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // return if service is started now
    myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

सीपीपी बैकएंड में:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

NDK बैकएंड में (अतिरिक्त aidl नेमस्पेस पर ध्यान दें):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

Rust बैकएंड में:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

सिंगल-थ्रेड वाले रनटाइम के साथ, असाइनिक Rust बैकएंड में:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks will run on this thread.
    std::future::pending().await
}

अन्य विकल्पों से एक अहम अंतर यह है कि हम एसिंक्रोनस Rust और सिंगल-थ्रेड वाले रनटाइम का इस्तेमाल करते समय, join_thread_pool को नहीं बुलाते. ऐसा इसलिए है, क्योंकि आपको Tokio को एक थ्रेड देनी होगी, जहां वह स्पॉन किए गए टास्क को पूरा कर सके. इस उदाहरण में, मुख्य थ्रेड उस मकसद को पूरा करेगी. tokio::spawn का इस्तेमाल करके शुरू किए गए सभी टास्क, मुख्य थ्रेड पर लागू होंगे.

मल्टी-थ्रेड वाले रनटाइम के साथ, असाइन किए गए Rust बैकएंड में:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

मल्टी-थ्रेड वाले Tokio रनटाइम की मदद से, स्पॉन किए गए टास्क मुख्य थ्रेड पर नहीं चलते. इसलिए, मुख्य सिलसिलेवार सूची पर join_thread_pool को कॉल करना ज़्यादा सही होता है, ताकि मुख्य सिलसिलेवार सूची सिर्फ़ निष्क्रिय न रहे. असाइनमेंट के साथ काम करने के दौरान, कॉल को block_in_place में रैप करना ज़रूरी है.

आपके पास यह अनुरोध करने का विकल्प है कि बाइंडर को होस्ट करने वाली सेवा बंद होने पर आपको सूचना मिले. इससे कॉलबैक प्रॉक्सी को लीक होने से रोका जा सकता है या गड़बड़ी को ठीक करने में मदद मिल सकती है. ये कॉल, बाइंडर प्रॉक्सी ऑब्जेक्ट पर करें.

  • Java में, android.os.IBinder::linkToDeath का इस्तेमाल करें.
  • सीपीपी बैकएंड में, android::IBinder::linkToDeath का इस्तेमाल करें.
  • NDK बैकएंड में, AIBinder_linkToDeath का इस्तेमाल करें.
  • Rust बैकएंड में, DeathRecipient ऑब्जेक्ट बनाएं. इसके बाद, my_binder.link_to_death(&mut my_death_recipient) को कॉल करें. ध्यान दें कि कॉलबैक का मालिकाना हक DeathRecipient के पास होता है. इसलिए, जब तक आपको सूचनाएं चाहिए, तब तक उस ऑब्जेक्ट को चालू रखें.

कॉल करने वाले की जानकारी

कर्नेल बाइंडर कॉल मिलने पर, कॉल करने वाले की जानकारी कई एपीआई में उपलब्ध होती है. पीआईडी (या प्रोसेस आईडी) से उस प्रोसेस के Linux प्रोसेस आईडी का पता चलता है जो लेन-देन भेज रही है. यूआईडी (या यूज़र आईडी) का मतलब, Linux यूज़र आईडी से है. एकतरफ़ा कॉल मिलने पर, कॉल करने वाले का पीआईडी 0 होता है. बाइंडर ट्रांज़ैक्शन कॉन्टेक्स्ट के बाहर होने पर, ये फ़ंक्शन मौजूदा प्रोसेस का पीआईडी और यूआईडी दिखाते हैं.

Java बैकएंड में:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

सीपीपी बैकएंड में:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

NDK बैकएंड में:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

Rust बैकएंड में इंटरफ़ेस लागू करते समय, डिफ़ॉल्ट तौर पर सेट होने की अनुमति देने के बजाय, इनके बारे में बताएं:

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

सेवाओं के लिए गड़बड़ी की रिपोर्ट और डीबगिंग एपीआई

गड़बड़ी की रिपोर्ट (उदाहरण के लिए, adb bugreport के साथ) चलने पर, वे अलग-अलग समस्याओं को डीबग करने में मदद करने के लिए, सिस्टम से जानकारी इकट्ठा करती हैं. AIDL सेवाओं के लिए, गड़बड़ी की रिपोर्ट में अपनी जानकारी डालने के लिए, सेवा मैनेजर के साथ रजिस्टर की गई सभी सेवाओं पर बाइनरी dumpsys का इस्तेमाल किया जाता है. dumpsys SERVICE [ARGS] की मदद से किसी सेवा से जानकारी पाने के लिए, कमांडलाइन पर dumpsys का इस्तेमाल भी किया जा सकता है. C++ और Java बैकएंड में, addService के लिए अतिरिक्त आर्ग्युमेंट का इस्तेमाल करके, यह कंट्रोल किया जा सकता है कि सेवाएं किस क्रम में डंप की जाएं. डीबग करते समय, किसी सेवा का पीआईडी पाने के लिए भी dumpsys --pid SERVICE का इस्तेमाल किया जा सकता है.

अपनी सेवा में कस्टम आउटपुट जोड़ने के लिए, अपने सर्वर ऑब्जेक्ट में dump method को बदला जा सकता है. ऐसा करने के लिए, AIDL फ़ाइल में बताए गए किसी अन्य आईपीसी तरीके को लागू करें. ऐसा करते समय, आपको ऐप्लिकेशन की अनुमति android.permission.DUMP या कुछ खास UID के लिए डेटा डंप करने पर पाबंदी लगानी चाहिए.

Java बैकएंड में:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

सीपीपी बैकएंड में:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

NDK बैकएंड में:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

Rust बैकएंड में इंटरफ़ेस लागू करते समय, डिफ़ॉल्ट तौर पर सेट होने के बजाय, इनके बारे में बताएं:

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

वेक पॉइंटर का इस्तेमाल करना

आपके पास बाइंडर ऑब्जेक्ट का वेक रेफ़रंस रखने का विकल्प होता है.

Java, WeakReference के साथ काम करता है. हालांकि, यह नेटिव लेयर पर कमज़ोर बाइंडर रेफ़रंस के साथ काम नहीं करता.

सीपीपी बैकएंड में, कमज़ोर टाइप wp<IFoo> है.

NDK बैकएंड में, ScopedAIBinder_Weak का इस्तेमाल करें:

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

Rust बैकएंड में, WpIBinder या Weak<IFoo> का इस्तेमाल किया जाता है:

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

इंटरफ़ेस डिस्क्रिप्टर को डाइनैमिक तौर पर पाना

इंटरफ़ेस डिस्क्रिप्टर, इंटरफ़ेस के टाइप की पहचान करता है. यह तब काम आता है, जब डीबग किया जा रहा हो या आपके पास कोई अनजान बाइंडर हो.

Java में, इंटरफ़ेस डिस्क्रिप्टर को इस तरह के कोड से देखा जा सकता है:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

सीपीपी बैकएंड में:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

NDK और Rust बैकएंड, इस सुविधा के साथ काम नहीं करते.

इंटरफ़ेस डिस्क्रिप्टर को स्टैटिक तौर पर पाना

कभी-कभी, आपको यह जानना ज़रूरी होता है कि इंटरफ़ेस डिस्क्रिप्टर स्टैटिक तौर पर क्या है. जैसे, @VintfStability सेवाओं को रजिस्टर करते समय. Java में, डिस्क्रिप्टर पाने के लिए, इस तरह का कोड जोड़ें:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

सीपीपी बैकएंड में:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

NDK बैकएंड में (अतिरिक्त aidl नेमस्पेस पर ध्यान दें):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

Rust बैकएंड में:

    aidl::my::package::BnFoo::get_descriptor()

Enum रेंज

नेटिव बैकएंड में, उन संभावित वैल्यू को दोहराया जा सकता है जो किसी एनम में हो सकती हैं. कोड के साइज़ की वजह से, यह Java में काम नहीं करता.

AIDL में तय किए गए किसी एनम MyEnum के लिए, दोहराव इस तरह दिया गया है.

सीपीपी बैकएंड में:

    ::android::enum_range<MyEnum>()

NDK बैकएंड में:

   ::ndk::enum_range<MyEnum>()

Rust बैकएंड में:

    MyEnum::enum_values()

थ्रेड मैनेजमेंट

किसी प्रोसेस में libbinder का हर इंस्टेंस, एक थ्रेडपूल बनाए रखता है. ज़्यादातर इस्तेमाल के उदाहरणों के लिए, यह एक थ्रेडपूल होना चाहिए, जो सभी बैकएंड के साथ शेयर किया जाता है. हालांकि, ऐसा तब होता है, जब वेंडर कोड /dev/vndbinder से बात करने के लिए, libbinder की एक और कॉपी लोड कर सकता है. यह एक अलग बाइंडर नोड पर है, इसलिए थिरेडपूल शेयर नहीं किया जाता.

Java बैकएंड के लिए, थ्रेडपूल का साइज़ सिर्फ़ बढ़ाया जा सकता है (क्योंकि यह पहले से शुरू है):

    BinderInternal.setMaxThreads(<new larger value>);

सीपीपी बैकएंड के लिए, ये कार्रवाइयां उपलब्ध हैं:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

इसी तरह, NDK बैकएंड में:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

Rust बैकएंड में:

    binder::ProcessState::start_thread_pool();
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    binder::ProcessState::join_thread_pool();

असाइन किए गए काम को बाद में करने की सुविधा वाले Rust बैकएंड के साथ, आपको दो थ्रेडपूल की ज़रूरत होती है: बाइंडर और Tokio. इसका मतलब है कि एक साथ काम नहीं करने वाले Rust का इस्तेमाल करने वाले ऐप्लिकेशन के लिए, खास बातों का ध्यान रखना ज़रूरी है. खास तौर पर, join_thread_pool का इस्तेमाल करते समय. इस बारे में ज़्यादा जानकारी के लिए, सेवाओं को रजिस्टर करने से जुड़ा सेक्शन देखें.

रिज़र्व किए गए नाम

C++, Java, और Rust में कुछ नामों को कीवर्ड के तौर पर या भाषा के हिसाब से इस्तेमाल करने के लिए रिज़र्व किया जाता है. AIDL, भाषा के नियमों के आधार पर पाबंदियां नहीं लगाता. हालांकि, रिज़र्व किए गए नाम से मेल खाने वाले फ़ील्ड या टाइप के नाम का इस्तेमाल करने पर, C++ या Java के लिए कंपाइलेशन की प्रोसेस पूरी नहीं हो पाती. Rust के लिए, फ़ील्ड या टाइप का नाम बदलने के लिए, "रॉ आइडेंटिफ़ायर" सिंटैक्स का इस्तेमाल किया जाता है. इसे r# प्रीफ़िक्स का इस्तेमाल करके ऐक्सेस किया जा सकता है.

हमारा सुझाव है कि जहां भी हो सके, अपनी एआईडीएल परिभाषाओं में रिज़र्व किए गए नामों का इस्तेमाल न करें. इससे, बाइंडिंग को आसानी से इस्तेमाल करने में आने वाली समस्याओं या कंपाइल न होने जैसी समस्याओं से बचा जा सकता है.

अगर आपने अपनी AIDL परिभाषाओं में पहले से ही नाम रिज़र्व कर रखे हैं, तो प्रोटोकॉल के साथ काम करते हुए, फ़ील्ड के नाम को सुरक्षित तरीके से बदला जा सकता है. प्रोग्राम बनाना जारी रखने के लिए, आपको अपना कोड अपडेट करना पड़ सकता है. हालांकि, पहले से बने किसी भी प्रोग्राम का इंटरऑपरेट करना जारी रहेगा.

इन नामों का इस्तेमाल न करें: