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

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

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

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

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

सिस्टम बनाना

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

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

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

Rust बैकएंड का इस्तेमाल सिर्फ़ Rust के साथ किया जा सकता है. rust_ मॉड्यूल को अलग-अलग तरीके से हैंडल किया जाता है, क्योंकि AIDL फ़ाइलों को सोर्स फ़ाइलों के तौर पर नहीं बताया जाता. इसके बजाय, aidl_interface मॉड्यूल, rustlib बनाता है. इसे aidl_interface_name-rust कहा जाता है. इसे लिंक किया जा सकता है. ज़्यादा जानकारी के लिए, Rust AIDL का उदाहरण देखें.

aidl_interface

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

प्रकार

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

Java या AIDL टाइप C++ टाइप एनडीके टाइप रस्ट टाइप
boolean bool bool bool
byte8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string इन: &str
आउट: String
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 std::vector1 इन: &[u8]
आउट: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 इन: In: &[T]4
आउट: Vec<T>
FileDescriptor android::base::unique_fd लागू नहीं लागू नहीं
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 या इसके बाद के वर्शन में, बाइट ऐरे uint8_t का इस्तेमाल करते हैं. हालांकि, ऐसा int8_t के बजाय इसलिए किया जाता है, ताकि यह सुविधा अन्य डिवाइसों के साथ काम कर सके.

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

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

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

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

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

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

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

दिशा (in, out, और inout)

फ़ंक्शन के तर्कों के टाइप तय करते समय, उन्हें in, out या inout के तौर पर तय किया जा सकता है. इससे यह कंट्रोल किया जाता है कि आईपीसी कॉल के लिए जानकारी किस दिशा में भेजी जाए.

  • in आर्ग्युमेंट स्पेसिफ़ायर से पता चलता है कि डेटा, कॉल करने वाले से कॉल किए गए व्यक्ति को पास किया जाता है. in स्पेसिफ़ायर, डिफ़ॉल्ट दिशा होती है. हालांकि, अगर डेटा टाइप भी out हो सकते हैं, तो आपको दिशा तय करनी होगी.

  • out आर्ग्युमेंट स्पेसिफ़ायर का मतलब है कि डेटा को कॉलर से कॉली में पास किया जाता है.

  • inout आर्ग्युमेंट स्पेसिफ़ायर, इन दोनों का कॉम्बिनेशन होता है. हालांकि, हमारा सुझाव है कि आप आर्ग्युमेंट स्पेसिफ़ायर inout का इस्तेमाल न करें. अगर वर्शन वाले इंटरफ़ेस और पुराने कॉलर के साथ inout का इस्तेमाल किया जाता है, तो कॉलर में मौजूद अतिरिक्त फ़ील्ड, अपनी डिफ़ॉल्ट वैल्यू पर रीसेट हो जाते हैं. Rust के हिसाब से, सामान्य inout टाइप को &mut 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);
}

UTF-8 और UTF-16

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

शून्यता

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

कस्टम पार्सल

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

यहां 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 में, एआईडीएल में कस्टम रस्ट पार्सलेबल के एलान के लिए, 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 में इस्तेमाल किया जा सके.

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

स्ट्रक्चर्ड पार्सल, प्रिमिटिव, 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.setStringField("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 में, यूनियनों को enums के तौर पर लागू किया जाता है. साथ ही, इनमें साफ़ तौर पर गेटर और सेटर नहीं होते हैं.

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

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

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

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

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

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

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

किसी इंटरफ़ेस को लागू करने के लिए, आपको नेटिव स्टब क्लास से इनहेरिट करना होगा. किसी इंटरफ़ेस को लागू करने की प्रोसेस को अक्सर सेवा कहा जाता है. ऐसा तब होता है, जब इसे सेवा मैनेजर या android.app.ActivityManager के साथ रजिस्टर किया जाता है. इसे कॉलबैक कहा जाता है, जब इसे किसी सेवा के क्लाइंट के ज़रिए रजिस्टर किया जाता है. हालांकि, इंटरफ़ेस को लागू करने के तरीके के आधार पर, अलग-अलग नामों का इस्तेमाल किया जाता है. स्टब क्लास, बाइंडर ड्राइवर से कमांड पढ़ती है और आपके लागू किए गए तरीकों को लागू करती है. मान लें कि आपके पास इस तरह की कोई AIDL फ़ाइल है:

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

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

    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(())
        }
    }

या एसिंक रस्ट के साथ:

    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()
}

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

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 run on this thread.
    std::future::pending().await
}

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

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

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 का इस्तेमाल कमांड लाइन पर भी किया जा सकता है. इससे dumpsys की सुविधा वाली किसी सेवा से जानकारी मिलती है.dumpsys SERVICE [ARGS] C++ और Java बैकएंड में, addService में अतिरिक्त आर्ग्युमेंट का इस्तेमाल करके, यह कंट्रोल किया जा सकता है कि सेवाओं को किस क्रम में डंप किया जाए. डीबग करते समय, किसी सेवा का पीआईडी पाने के लिए भी dumpsys --pid SERVICE का इस्तेमाल किया जा सकता है.

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

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 रेंज

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

एआईडीएल में तय किए गए किसी enum MyEnum के लिए, इस तरह से इटरेशन किया जाता है.

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

    ::android::enum_range<MyEnum>()

NDK बैकएंड में:

   ::ndk::enum_range<MyEnum>()

Rust बैकएंड में:

    MyEnum::enum_values()

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

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

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();

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

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

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

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

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

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