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