एआईडीएल बैकएंड, स्टब कोड जनरेशन के लिए टारगेट है. AIDL फ़ाइलों का इस्तेमाल करते समय, उनका इस्तेमाल हमेशा किसी खास भाषा में किया जाता है. साथ ही, उनका इस्तेमाल किसी खास रनटाइम के साथ किया जाता है. संदर्भ के हिसाब से, आपको अलग-अलग AIDL बैकएंड का इस्तेमाल करना चाहिए.
नीचे दी गई टेबल में, एपीआई प्लैटफ़ॉर्म की स्थिरता का मतलब इस एपीआई सरफ़ेस पर कोड को इस तरह से कंपाइल करने की सुविधा से है कि कोड को system.img
libbinder.so
बाइनरी से अलग डिलीवर किया जा सके.
AIDL में ये बैकएंड होते हैं:
बैकएंड | भाषा | एपीआई का प्लैटफ़ॉर्म | सिस्टम बनाना |
---|---|---|---|
Java | Java | SDK/SystemApi (स्टैबल*) | सभी |
एनडीके | C++ | libbinder_ndk (स्टैबल*) | aidl_interface |
सीपीपी | C++ | लिबिंदर (अस्टेबल) | सभी |
रस्ट | रस्ट | libbinder_rs (stable*) | aidl_interface |
- एपीआई के ये प्लैटफ़ॉर्म स्टेबल हैं. हालांकि, सेवा मैनेजमेंट के लिए इस्तेमाल किए जाने वाले कई एपीआई, प्लैटफ़ॉर्म के अंदरूनी इस्तेमाल के लिए रिज़र्व हैं. साथ ही, ये ऐप्लिकेशन के लिए उपलब्ध नहीं हैं. ऐप्लिकेशन में एआईडीएल का इस्तेमाल करने के तरीके के बारे में ज़्यादा जानने के लिए, डेवलपर के लिए दस्तावेज़ देखें.
- Rust बैकएंड को Android 12 में पेश किया गया था. NDK बैकएंड, Android 10 से उपलब्ध है.
- रस्ट क्रेट
libbinder_ndk
के ऊपर बनाया गया है, जिसकी वजह से इसे एक जगह से दूसरी जगह ले जाया जा सकता है. APEX, बाइंडर क्रेट का इस्तेमाल उसी तरह करते हैं जिस तरह सिस्टम साइड पर कोई और करता है. Rust का हिस्सा, APEX में बंडल किया जाता है और उसमें शिप किया जाता है. यह सिस्टम पार्टीशन में मौजूदlibbinder_ndk.so
पर निर्भर करता है.
सिस्टम बनाना
बैकएंड के आधार पर, स्टब कोड में एआईडीएल को कंपाइल करने के दो तरीके हैं. बिल्ड सिस्टम के बारे में ज़्यादा जानकारी के लिए, Soong मॉड्यूल का रेफ़रंस देखें.
कोर बिल्ड सिस्टम
किसी भी cc_
या java_
Android.bp मॉड्यूल (या उनके Android.mk
बराबर के मॉड्यूल) में,
.aidl
फ़ाइलों को सोर्स फ़ाइलों के तौर पर बताया जा सकता है. इस मामले में, NDK बैकएंड के बजाय, AIDL के Java/CPP बैकएंड का इस्तेमाल किया जाता है. साथ ही, मिलती-जुलती AIDL फ़ाइलों का इस्तेमाल करने के लिए, क्लास अपने-आप मॉड्यूल में जुड़ जाती हैं. विकल्प
जैसे कि local_include_dirs
, जो बिल्ड सिस्टम को उस मॉड्यूल में मौजूद एआईडीएल फ़ाइलों का रूट पाथ बताता है. इन मॉड्यूल में, aidl:
ग्रुप के तहत इन विकल्पों की जानकारी दी जा सकती है. ध्यान दें कि Rust बैकएंड का इस्तेमाल सिर्फ़ Rust के साथ किया जा सकता है. rust_
मॉड्यूल को
अलग-अलग तरीके से मैनेज किया जाता है, क्योंकि एआईडीएल फ़ाइलों को सोर्स फ़ाइलों के तौर पर नहीं माना जाता.
इसके बजाय, aidl_interface
मॉड्यूल एक rustlib
जनरेट करता है, जिसे <aidl_interface name>-rust
कहा जाता है. इसे लिंक किया जा सकता है. ज़्यादा जानकारी के लिए, Rust AIDL का उदाहरण देखें.
aidl_इंटरफ़ेस
इस बिल्ड सिस्टम के साथ इस्तेमाल किए जाने वाले टाइप, स्ट्रक्चर्ड होने चाहिए. स्ट्रक्चर्ड होने के लिए, parcels में सीधे तौर पर फ़ील्ड होने चाहिए. साथ ही, वे टारगेट भाषाओं में सीधे तौर पर तय किए गए टाइप के एलान नहीं होने चाहिए. स्ट्रक्चर्ड एआईडीएल और स्थिर एआईडीएल के बीच के अंतर के बारे में जानने के लिए, स्ट्रक्चर्ड बनाम स्थिर एआईडीएल लेख पढ़ें.
प्रकार
aidl
कंपाइलर को टाइप के लिए रेफ़रंस के तौर पर इस्तेमाल किया जा सकता है.
इंटरफ़ेस बनाते समय, उससे जुड़ी इंटरफ़ेस फ़ाइल देखने के लिए aidl --lang=<backend> ...
शुरू करें. aidl_interface
मॉड्यूल का इस्तेमाल करने पर, out/soong/.intermediates/<path to module>/
में आउटपुट देखा जा सकता है.
Java/AIDL टाइप | C++ टाइप | एनडीके (NDK) टाइप | रस्ट टाइप |
---|---|---|---|
बूलियन | बूल | bool | बूल |
बाइट8 | int8_t | पूर्णांक | i8 |
char | char16_t | char16_t | u16 |
आईएनटी | int32_t | int32_t | आई32 |
लंबा | int64_t | int64_t | आई64 |
फ़्लोट | फ़्लोट | फ़्लोट | f32 |
डबल | डबल | डबल | f64 |
स्ट्रिंग | android::String16 | std::string | स्ट्रिंग |
android.os.Parcelable | android::पार्सेबल | लागू नहीं | लागू नहीं |
IBinder | android::IBinder | एनडीके::एसपीएआईबाइंडर | binder::SpIBinder |
टी[] | 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::वेक्टर<T>2 | std::vector<T>3 | इनपुट: &[T]4 आउटपुट: Vec<T> |
फ़ाइल वर्णनकर्ता | android::base::unique_fd | लागू नहीं | बाइंडर::पार्सल::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
कोई भी नॉन-प्रीमिटिव टाइप हो सकता है (इसमें इंटरफ़ेस टाइप भी शामिल हैं). हालांकि, इसमें अरे नहीं हैं. एओएसपी का सुझाव है कि आप
T[]
जैसे कलेक्शन टाइप का इस्तेमाल करें, क्योंकि वे सभी बैकएंड में काम करते हैं.
3. एनडीके बैकएंड, List<T>
के साथ काम करता है, जहां T
, String
, ParcelFileDescriptor
या पार्सल में से एक है. 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 में हैं. स्ट्रिंग को एआईडीएल में @utf8InCpp String
के तौर पर बताएं, ताकि उन्हें utf-8 में अपने-आप बदला जा सके.
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);
इसके बाद, पार्स किए जा सकने वाले इस डेटा को एआईडीएल फ़ाइलों में टाइप के तौर पर इस्तेमाल किया जा सकता है. हालांकि, यह एआईडीएल फ़ाइलों में जनरेट नहीं होगा. 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
फ़ील्ड को शून्य वैल्यू वाले फ़ील्ड के तौर पर शुरू किया जाता है.
यूनियन
एआईडीएल यूनियन टैग होते हैं और सभी बैकएंड में उनकी सुविधाएं एक जैसी होती हैं. ये डिफ़ॉल्ट रूप से पहले फ़ील्ड की डिफ़ॉल्ट वैल्यू के लिए बनाए जाते हैं. साथ ही, इनके साथ इंटरैक्ट करने का तरीका, भाषा के हिसाब से अलग-अलग होता है.
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 फ़ंक्शन गड़बड़ी की सूचना देता है, तो हो सकता है कि फ़ंक्शन, आउटपुट पैरामीटर को शुरू न कर पाए या उनमें बदलाव न कर पाए. खास तौर पर, अगर ट्रांज़ैक्शन की प्रोसेसिंग के दौरान कोई गड़बड़ी होती है, न कि पार्स होने के दौरान कोई गड़बड़ी होती है, तो आउटपुट पैरामीटर में बदलाव किया जा सकता है. आम तौर पर, एआईडीएल फ़ंक्शन से गड़बड़ी मिलने पर, सभी 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
का इस्तेमाल किया गया है. रस्ट बैकएंड का इस्तेमाल करने के तरीके के बारे में जानने के लिए, Android रस्ट पैटर्न वाले पेज पर रस्ट एआईडीएल का उदाहरण देखें.
इंपोर्ट के टाइप
तय किया गया टाइप, इंटरफ़ेस, पार्सल करने लायक या यूनियन हो, इसे Java में इंपोर्ट किया जा सकता है:
import my.package.IFoo;
इसके अलावा, सीपीपी बैकएंड में भी यह जानकारी देखी जा सकती है:
#include <my/package/IFoo.h>
या एनडीके बैकएंड में (अतिरिक्त 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;
}
एनडीके बैकएंड में (अतिरिक्त 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"));
एनडीके बैकएंड में (अतिरिक्त 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
का इस्तेमाल करके शुरू किए गए सभी टास्क, मुख्य थ्रेड पर लागू होंगे.
एक से ज़्यादा थ्रेड वाले रनटाइम के साथ, एसिंक्रोनस रस्ट बैकएंड में:
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>
है.
एनडीके बैकएंड में, 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
एनडीके बैकएंड में (अतिरिक्त 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, कुछ नामों को कीवर्ड के रूप में या भाषा के खास इस्तेमाल के लिए रिज़र्व रखते हैं. हालांकि, एआईडीएल भाषा के नियमों के हिसाब से पाबंदियां लागू नहीं करता है, लेकिन रिज़र्व किए गए नाम से मेल खाने वाले फ़ील्ड या टाइप नेम का इस्तेमाल करने से, C++ या Java के लिए कंपाइलेशन नहीं हो सकता है. Rust के लिए, फ़ील्ड या टाइप का नाम बदलने के लिए, "रॉ आइडेंटिफ़ायर" सिंटैक्स का इस्तेमाल किया जाता है. इसे r#
प्रीफ़िक्स का इस्तेमाल करके ऐक्सेस किया जा सकता है.
हमारा सुझाव है कि जहां भी हो सके, अपनी एआईडीएल परिभाषाओं में रिज़र्व किए गए नामों का इस्तेमाल न करें. इससे, बाइंडिंग को आसानी से इस्तेमाल करने में आने वाली समस्याओं या कंपाइल न होने जैसी समस्याओं से बचा जा सकता है.
अगर आपने अपनी AIDL परिभाषाओं में पहले से ही नाम रिज़र्व कर रखे हैं, तो प्रोटोकॉल के साथ काम करते हुए, फ़ील्ड के नाम को सुरक्षित तरीके से बदला जा सकता है. प्रोग्राम बनाना जारी रखने के लिए, आपको अपना कोड अपडेट करना पड़ सकता है. हालांकि, पहले से बने किसी भी प्रोग्राम में बदलाव नहीं होगा.
इन नामों का इस्तेमाल न करें: