একটি AIDL ব্যাকএন্ড হলো স্টাব কোড জেনারেশনের জন্য একটি টার্গেট। সর্বদা একটি নির্দিষ্ট রানটাইম সহ একটি নির্দিষ্ট ভাষার AIDL ফাইল ব্যবহার করুন। প্রেক্ষাপটের উপর নির্ভর করে, আপনার বিভিন্ন AIDL ব্যাকএন্ড ব্যবহার করা উচিত।
নিম্নলিখিত সারণীতে, এপিআই সারফেসের স্থিতিশীলতা বলতে এই এপিআই সারফেসের বিপরীতে কোড কম্পাইল করার এমন ক্ষমতাকে বোঝায়, যাতে কোডটি system.img libbinder.so বাইনারি থেকে স্বাধীনভাবে সরবরাহ করা যায়।
AIDL-এর নিম্নলিখিত ব্যাকএন্ডগুলো রয়েছে:
| ব্যাকএন্ড | ভাষা | এপিআই পৃষ্ঠ | সিস্টেম তৈরি করুন |
|---|---|---|---|
| জাভা | জাভা | SDK বা SystemApi (স্থিতিশীল*) | সব |
| এনডিকে | সি++ | libbinder_ndk (স্থিতিশীল*) | aidl_interface |
| সিপিপি | সি++ | libbinder (অস্থিতিশীল) | সব |
| মরিচা | মরিচা | libbinder_rs (স্থিতিশীল*) | aidl_interface |
- এই API সারফেসগুলো স্থিতিশীল, কিন্তু পরিষেবা ব্যবস্থাপনার মতো অনেক API প্ল্যাটফর্মের অভ্যন্তরীণ ব্যবহারের জন্য সংরক্ষিত এবং অ্যাপের জন্য উপলব্ধ নয়। অ্যাপে AIDL কীভাবে ব্যবহার করবেন সে সম্পর্কে আরও তথ্যের জন্য, Android Interface Definition Language (AIDL) দেখুন।
- রাস্ট ব্যাকএন্ডটি অ্যান্ড্রয়েড ১২-এ চালু করা হয়েছিল; এনডিকে ব্যাকএন্ডটি অ্যান্ড্রয়েড ১০ থেকেই উপলব্ধ রয়েছে।
- রাস্ট ক্রেটটি
libbinder_ndkএর উপর ভিত্তি করে তৈরি, যা এটিকে স্থিতিশীল এবং পোর্টেবল করে তোলে। APEX-গুলো সিস্টেম সাইডে প্রমিত পদ্ধতিতে বাইন্ডার ক্রেটটি ব্যবহার করে। রাস্ট অংশটি একটি APEX-এর মধ্যে বান্ডল করা হয় এবং এর ভেতরেই পাঠানো হয়। এই অংশটি সিস্টেম পার্টিশনে থাকাlibbinder_ndk.soএর উপর নির্ভরশীল।
সিস্টেম তৈরি করুন
ব্যাকএন্ডের উপর নির্ভর করে, AIDL-কে স্টাব কোডে কম্পাইল করার দুটি উপায় রয়েছে। বিল্ড সিস্টেমগুলো সম্পর্কে আরও বিস্তারিত জানতে, সুং মডিউলস রেফারেন্স দেখুন।
কোর বিল্ড সিস্টেম
যেকোনো cc_ বা java_ Android.bp module (অথবা তাদের Android.mk সমতুল্য মডিউলে), আপনি সোর্স ফাইল হিসেবে AIDL ( .aidl ) ফাইল নির্দিষ্ট করতে পারেন। এক্ষেত্রে, AIDL-এর Java বা CPP ব্যাকএন্ড ব্যবহৃত হয় (NDK ব্যাকএন্ড নয়), এবং সংশ্লিষ্ট AIDL ফাইলগুলো ব্যবহার করার জন্য ক্লাসগুলো মডিউলে স্বয়ংক্রিয়ভাবে যুক্ত হয়ে যায়। এই মডিউলগুলোতে আপনি একটি aidl: গ্রুপের অধীনে local_include_dirs এর মতো অপশন নির্দিষ্ট করতে পারেন (যা বিল্ড সিস্টেমকে সেই মডিউলের AIDL ফাইলগুলোর রুট পাথ বলে দেয়)।
রাস্ট ব্যাকএন্ডটি শুধুমাত্র রাস্টের সাথেই ব্যবহারের জন্য। rust_ মডিউলগুলো ভিন্নভাবে পরিচালিত হয়, কারণ এক্ষেত্রে AIDL ফাইলগুলোকে সোর্স ফাইল হিসেবে নির্দিষ্ট করা হয় না। এর পরিবর্তে, aidl_interface মডিউলটি aidl_interface_name -rust নামের একটি rustlib তৈরি করে, যার সাথে লিঙ্ক করা যায়। বিস্তারিত জানতে, রাস্ট AIDL উদাহরণটি দেখুন।
aidl_interface
aidl_interface বিল্ড সিস্টেমের সাথে ব্যবহৃত টাইপগুলো অবশ্যই স্ট্রাকচার্ড হতে হবে। স্ট্রাকচার্ড হওয়ার জন্য, পার্সেলেবলগুলোতে সরাসরি ফিল্ড থাকতে হবে এবং সেগুলো টার্গেট ল্যাঙ্গুয়েজে সরাসরি সংজ্ঞায়িত টাইপের ডিক্লারেশন হতে পারবে না। স্ট্রাকচার্ড AIDL কীভাবে স্টেবল AIDL-এর সাথে সামঞ্জস্যপূর্ণ, তা জানতে “স্ট্রাকচার্ড বনাম স্টেবল AIDL” দেখুন।
প্রকারভেদ
aidl কম্পাইলারকে টাইপের জন্য একটি রেফারেন্স ইমপ্লিমেন্টেশন হিসেবে বিবেচনা করুন। যখন আপনি একটি ইন্টারফেস তৈরি করেন, তখন ফলাফলস্বরূপ ইন্টারফেস ফাইলটি দেখতে aidl --lang=<backend> ... কমান্ডটি চালান। যখন আপনি aidl_interface মডিউলটি ব্যবহার করেন, তখন আপনি এর আউটপুট out/soong/.intermediates/ <path to module> / -এ দেখতে পারেন।
| জাভা বা AIDL টাইপ | C++ টাইপ | এনডিকে টাইপ | মরিচা টাইপ |
|---|---|---|---|
boolean | bool | bool | bool |
byte ৮ | 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 | In: &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::vector ১ | In: &[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] ৬ | std::array<T, N> | std::array<T, N> | [T; N] |
অ্যান্ড্রয়েড ১২ বা তার পরবর্তী সংস্করণগুলোতে সামঞ্জস্যতার কারণে বাইট অ্যারেতে int8_t এর পরিবর্তে uint8_t ব্যবহৃত হয়।
২. C++ ব্যাকএন্ড List<T> সমর্থন করে, যেখানে T হলো String , IBinder , ParcelFileDescriptor বা parcelable-এর মধ্যে যেকোনো একটি। Android 13 বা তার পরবর্তী সংস্করণে, T অ্যারে ছাড়া যেকোনো নন-প্রিমিটিভ টাইপ (ইন্টারফেস টাইপ সহ) হতে পারে। AOSP, T[] মতো অ্যারে টাইপ ব্যবহার করার পরামর্শ দেয়, কারণ এগুলো সব ব্যাকএন্ডে কাজ করে।
৩. এনডিকে ব্যাকএন্ড List<T> সমর্থন করে, যেখানে T হলো String , ParcelFileDescriptor বা parcelable-এর মধ্যে যেকোনো একটি। অ্যান্ড্রয়েড ১৩ বা তার পরবর্তী সংস্করণে, T অ্যারে ছাড়া যেকোনো নন-প্রিমিটিভ টাইপ হতে পারে।
৪. রাস্ট কোডে টাইপগুলো ইনপুট (আর্গুমেন্ট) নাকি আউটপুট (রিটার্ন করা ভ্যালু) তার উপর নির্ভর করে ভিন্নভাবে পাস করা হয়।
৫. ইউনিয়ন টাইপ অ্যান্ড্রয়েড ১২ এবং এর পরবর্তী সংস্করণগুলোতে সমর্থিত।
৬. অ্যান্ড্রয়েড ১৩ বা তার পরবর্তী সংস্করণে, নির্দিষ্ট আকারের অ্যারে সমর্থিত। নির্দিষ্ট আকারের অ্যারের একাধিক ডাইমেনশন থাকতে পারে (উদাহরণস্বরূপ, int[3][4] )। জাভা ব্যাকএন্ডে, নির্দিষ্ট আকারের অ্যারেগুলোকে অ্যারে টাইপ হিসেবে উপস্থাপন করা হয়।
৭. একটি বাইন্ডার SharedRefBase অবজেক্ট ইনস্ট্যানশিয়েট করতে, SharedRefBase::make\<My\>(... args ...) ব্যবহার করুন। এই ফাংশনটি একটি std::shared_ptr\<T\> অবজেক্ট তৈরি করে, যা অভ্যন্তরীণভাবেও পরিচালিত হয়, যদি বাইন্ডারটি অন্য কোনো প্রসেসের মালিকানাধীন থাকে। অন্য কোনো উপায়ে অবজেক্টটি তৈরি করলে দ্বৈত মালিকানা তৈরি হয়।
৮. আরও দেখুন Java বা AIDL টাইপ byte[] ।
দিকনির্দেশনা (ভিতরে, বাইরে, এবং ভিতরে-বাইরে)
ফাংশনের আর্গুমেন্টের ধরন নির্দিষ্ট করার সময়, আপনি সেগুলোকে in , out , বা inout হিসেবে উল্লেখ করতে পারেন। এটি একটি IPC কলের জন্য তথ্য প্রেরণের দিক নিয়ন্ত্রণ করে।
inআর্গুমেন্ট স্পেসিফায়ারটি নির্দেশ করে যে ডেটা কলার থেকে ক্যালি-র কাছে পাঠানো হচ্ছে। 'inস্পেসিফায়ারটি হলো ডিফল্ট দিক, কিন্তু যদি ডেটা টাইপগুলো 'outহিসেবেও পাঠানো যায়, তাহলে আপনাকে অবশ্যই দিকটি নির্দিষ্ট করে দিতে হবে।'
outআর্গুমেন্ট স্পেসিফায়ারটির অর্থ হলো, ডেটা ক্যালি (callee) থেকে কলারের (caller) কাছে পাঠানো হয়।inoutআর্গুমেন্ট স্পেসিফায়ারটি এই দুটিরই সংমিশ্রণ। তবে, আমরাinoutআর্গুমেন্ট স্পেসিফায়ারটি ব্যবহার করা এড়িয়ে চলার পরামর্শ দিই। যদি আপনি একটি ভার্সনযুক্ত ইন্টারফেস এবং একটি পুরোনো ক্যালি-এর সাথেinoutব্যবহার করেন, তাহলে যে অতিরিক্ত ফিল্ডগুলো শুধুমাত্র কলারে উপস্থিত থাকে, সেগুলো তাদের ডিফল্ট মানে রিসেট হয়ে যায়। রাস্টের ক্ষেত্রে, একটি সাধারণ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
CPP ব্যাকএন্ডের সাহায্যে, আপনি স্ট্রিংগুলো UTF-8 নাকি UTF-16 হবে তা বেছে নিতে পারেন। স্ট্রিংগুলোকে স্বয়ংক্রিয়ভাবে UTF-8-এ রূপান্তর করতে AIDL-এ সেগুলোকে @utf8InCpp String হিসেবে ঘোষণা করুন। NDK এবং Rust ব্যাকএন্ডগুলো সর্বদা UTF-8 স্ট্রিং ব্যবহার করে। utf8InCpp অ্যানোটেশন সম্পর্কে আরও তথ্যের জন্য, utf8InCpp দেখুন।
বাতিলযোগ্যতা
যেসব টাইপ null হতে পারে, সেগুলোকে আপনি @nullable অ্যানোটেশন দিয়ে চিহ্নিত করতে পারেন। nullable অ্যানোটেশন সম্পর্কে আরও তথ্যের জন্য, nullable দেখুন।
কাস্টম পার্সেলযোগ্য
কাস্টম পার্সেল্যাবল হলো এমন একটি পার্সেল্যাবল যা টার্গেট ব্যাকএন্ডে ম্যানুয়ালি ইমপ্লিমেন্ট করা হয়। শুধুমাত্র তখনই কাস্টম পার্সেল্যাবল ব্যবহার করুন, যখন আপনি এমন একটি বিদ্যমান কাস্টম পার্সেল্যাবলের জন্য অন্যান্য ভাষার সাপোর্ট যোগ করতে চাইছেন যা পরিবর্তন করা সম্ভব নয়।
এখানে একটি AIDL পার্সেলযোগ্য ঘোষণার উদাহরণ দেওয়া হলো:
package my.pack.age;
parcelable Foo;
ডিফল্টরূপে, এটি একটি জাভা পার্সেলএবল ডিক্লেয়ার করে, যেখানে my.pack.age.Foo হলো Parcelable ইন্টারফেস ইমপ্লিমেন্টকারী একটি জাভা ক্লাস।
AIDL-এ পার্সেলযোগ্য একটি কাস্টম CPP ব্যাকএন্ড ঘোষণার জন্য, 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 এ থাকা রাস্ট ইমপ্লিমেন্টেশনটি দেখতে এইরকম:
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 দ্বারা জেনারেট করা হবে না। CPP এবং NDK ব্যাকএন্ডের কাস্টম পার্সেলেবলগুলোকে union ব্যবহার করার জন্য < এবং == অপারেটর প্রদান করুন।
ডিফল্ট মান
স্ট্রাকচার্ড পার্সেল্যাবলগুলো প্রিমিটিভ, String ফিল্ড এবং এই ধরনের অ্যারেগুলোর জন্য ফিল্ড-ভিত্তিক ডিফল্ট মান ঘোষণা করতে পারে।
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
জাভা ব্যাকএন্ডে, ডিফল্ট মান অনুপস্থিত থাকলে, প্রিমিটিভ টাইপের জন্য ফিল্ডগুলোকে শূন্য মান এবং নন-প্রিমিটিভ টাইপের জন্য null দিয়ে ইনিশিয়ালাইজ করা হয়।
অন্যান্য ব্যাকএন্ডে, ডিফল্ট মান সংজ্ঞায়িত না থাকলে ফিল্ডগুলিকে ডিফল্ট মান দিয়ে ইনিশিয়ালাইজ করা হয়। উদাহরণস্বরূপ, C++ ব্যাকএন্ডে, String ফিল্ডগুলিকে একটি খালি স্ট্রিং হিসাবে এবং List<T> ফিল্ডগুলিকে একটি খালি vector<T> হিসাবে ইনিশিয়ালাইজ করা হয়। @nullable ফিল্ডগুলিকে null-value ফিল্ড হিসাবে ইনিশিয়ালাইজ করা হয়।
ইউনিয়ন
AIDL ইউনিয়নগুলো ট্যাগযুক্ত থাকে এবং সব ব্যাকএন্ডে এদের বৈশিষ্ট্যগুলো একই রকম। এগুলো প্রথম ফিল্ডের ডিফল্ট মান অনুযায়ী গঠিত হয় এবং এদের সাথে ইন্টারঅ্যাক্ট করার একটি ভাষা-নির্দিষ্ট পদ্ধতি রয়েছে:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
জাভা উদাহরণ
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)
মরিচার উদাহরণ
রাস্টে, ইউনিয়নগুলোকে এনাম হিসেবে প্রয়োগ করা হয় এবং এগুলোর কোনো সুস্পষ্ট গেটার ও সেটার থাকে না।
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
ত্রুটি পরিচালনা
অ্যান্ড্রয়েড ওএস সার্ভিসগুলোর ত্রুটি জানানোর জন্য বিল্ট-ইন এরর টাইপ প্রদান করে। এগুলো বাইন্ডার দ্বারা ব্যবহৃত হয় এবং বাইন্ডার ইন্টারফেস ইমপ্লিমেন্টকারী যেকোনো সার্ভিস এগুলো ব্যবহার করতে পারে। AIDL ডেফিনিশনে এদের ব্যবহার বিশদভাবে নথিভুক্ত করা আছে এবং এগুলোর জন্য কোনো ব্যবহারকারী-সংজ্ঞায়িত স্ট্যাটাস বা রিটার্ন টাইপের প্রয়োজন হয় না।
ত্রুটি সহ আউটপুট প্যারামিটার
যখন একটি AIDL ফাংশন কোনো ত্রুটি রিপোর্ট করে, তখন ফাংশনটি আউটপুট প্যারামিটারগুলো ইনিশিয়ালাইজ বা মডিফাই নাও করতে পারে। বিশেষত, ট্রানজ্যাকশন প্রসেসিংয়ের সময় ত্রুটি ঘটার পরিবর্তে, আনপার্সেলিং-এর সময় ত্রুটি ঘটলে আউটপুট প্যারামিটারগুলো মডিফাই করা হতে পারে। সাধারণত, একটি AIDL ফাংশন থেকে ত্রুটি পেলে, সমস্ত inout ও out প্যারামিটার এবং রিটার্ন ভ্যালু (যা কিছু ব্যাকএন্ডে out প্যারামিটারের মতো কাজ করে) একটি অনির্দিষ্ট অবস্থায় আছে বলে ধরে নেওয়া উচিত।
কোন ত্রুটির মান ব্যবহার করতে হবে
অনেক বিল্ট-ইন এরর ভ্যালু যেকোনো AIDL ইন্টারফেসে ব্যবহার করা যায়, কিন্তু কিছু ভ্যালুকে বিশেষ উপায়ে বিবেচনা করা হয়। উদাহরণস্বরূপ, যখন এরর কন্ডিশন বর্ণনা করা হয়, তখন EX_UNSUPPORTED_OPERATION এবং EX_ILLEGAL_ARGUMENT ব্যবহার করা যায়, কিন্তু EX_TRANSACTION_FAILED ব্যবহার করা যাবে না, কারণ অন্তর্নিহিত ইনফ্রাস্ট্রাকচার এটিকে বিশেষভাবে বিবেচনা করে। এই বিল্ট-ইন ভ্যালুগুলো সম্পর্কে আরও তথ্যের জন্য ব্যাকএন্ড-নির্দিষ্ট ডেফিনিশনগুলো দেখুন।
যদি AIDL ইন্টারফেসে এমন অতিরিক্ত এরর ভ্যালুর প্রয়োজন হয় যা বিল্ট-ইন এরর টাইপগুলোর আওতায় পড়ে না, তবে তারা বিশেষ সার্ভিস-নির্দিষ্ট বিল্ট-ইন এরর ব্যবহার করতে পারে, যা ব্যবহারকারী দ্বারা সংজ্ঞায়িত একটি সার্ভিস-নির্দিষ্ট এরর ভ্যালু অন্তর্ভুক্ত করার সুযোগ দেয়। এই সার্ভিস-নির্দিষ্ট এররগুলো সাধারণত AIDL ইন্টারফেসে একটি const int বা int ব্যাকড enum হিসেবে সংজ্ঞায়িত করা হয় এবং বাইন্ডার দ্বারা পার্স করা হয় না।
জাভাতে, এররগুলো এক্সেপশনের সাথে সম্পর্কিত, যেমন android.os.RemoteException । সার্ভিস-নির্দিষ্ট এক্সেপশনের জন্য, জাভা ব্যবহারকারী-সংজ্ঞায়িত এররের পাশাপাশি android.os.ServiceSpecificException ব্যবহার করে।
অ্যান্ড্রয়েডের নেটিভ কোডে এক্সেপশন ব্যবহার করা হয় না। CPP ব্যাকএন্ড android::binder::Status ব্যবহার করে। NDK ব্যাকএন্ড ndk::ScopedAStatus ব্যবহার করে। AIDL দ্বারা জেনারেট করা প্রতিটি মেথড এগুলোর মধ্যে একটি রিটার্ন করে, যা মেথডটির স্ট্যাটাস নির্দেশ করে। রাস্ট ব্যাকএন্ড NDK-এর মতোই একই এক্সেপশন কোড ভ্যালু ব্যবহার করে, কিন্তু ব্যবহারকারীর কাছে পৌঁছে দেওয়ার আগে সেগুলোকে নেটিভ রাস্ট এরর ( StatusCode , ExceptionCode )-এ রূপান্তর করে। সার্ভিস-নির্দিষ্ট এররের ক্ষেত্রে, রিটার্ন করা Status বা ScopedAStatus ব্যবহারকারী-সংজ্ঞায়িত এররের সাথে EX_SERVICE_SPECIFIC ব্যবহার করে।
অন্তর্নির্মিত ত্রুটির ধরণগুলো নিম্নলিখিত ফাইলগুলিতে পাওয়া যাবে:
| ব্যাকএন্ড | সংজ্ঞা |
|---|---|
| জাভা | android/os/Parcel.java |
| সিপিপি | binder/Status.h |
| এনডিকে | android/binder_status.h |
| মরিচা | android/binder_status.h |
বিভিন্ন ব্যাকএন্ড ব্যবহার করুন
এই নির্দেশাবলী বিশেষভাবে অ্যান্ড্রয়েড প্ল্যাটফর্ম কোডের জন্য প্রযোজ্য। এই উদাহরণগুলিতে my.package.IFoo নামক একটি সংজ্ঞায়িত টাইপ ব্যবহার করা হয়েছে। রাস্ট ব্যাকএন্ড কীভাবে ব্যবহার করতে হয়, তার নির্দেশাবলীর জন্য অ্যান্ড্রয়েড রাস্ট প্যাটার্নস -এ থাকা রাস্ট AIDL উদাহরণটি দেখুন।
আমদানির প্রকারভেদ
সংজ্ঞায়িত টাইপটি ইন্টারফেস, পার্সেলযোগ্য বা ইউনিয়ন যাই হোক না কেন, আপনি জাভাতে এটি ইম্পোর্ট করতে পারেন:
import my.package.IFoo;
অথবা CPP ব্যাকএন্ডে:
#include <my/package/IFoo.h>
অথবা এনডিকে ব্যাকএন্ডে (অতিরিক্ত aidl নেমস্পেসটি লক্ষ্য করুন):
#include <aidl/my/package/IFoo.h>
অথবা রাস্ট ব্যাকএন্ডে:
use my_package::aidl::my::package::IFoo;
যদিও জাভাতে আপনি একটি নেস্টেড টাইপ ইম্পোর্ট করতে পারেন, CPP এবং NDK ব্যাকএন্ডে আপনাকে অবশ্যই এর রুট টাইপের হেডার অন্তর্ভুক্ত করতে হবে। উদাহরণস্বরূপ, my/package/IFoo.aidl এ সংজ্ঞায়িত একটি নেস্টেড টাইপ Bar (যেখানে IFoo হলো ফাইলটির রুট টাইপ) ইম্পোর্ট করার সময়, আপনাকে CPP ব্যাকএন্ডের জন্য অবশ্যই <my/package/IFoo.h> (অথবা NDK ব্যাকএন্ডের জন্য <aidl/my/package/IFoo.h> ) অন্তর্ভুক্ত করতে হবে।
একটি ইন্টারফেস বাস্তবায়ন করুন
একটি ইন্টারফেস ইমপ্লিমেন্ট করতে হলে, আপনাকে অবশ্যই নেটিভ স্টাব ক্লাস থেকে ইনহেরিট করতে হবে। একটি ইন্টারফেসের ইমপ্লিমেন্টেশনকে প্রায়শই সার্ভিস বলা হয় যখন এটি সার্ভিস ম্যানেজার বা android.app.ActivityManager সাথে রেজিস্টার করা হয় এবং যখন এটি কোনো সার্ভিসের ক্লায়েন্ট দ্বারা রেজিস্টার করা হয় তখন একে কলব্যাক বলা হয়। তবে, সঠিক ব্যবহারের উপর নির্ভর করে ইন্টারফেস ইমপ্লিমেন্টেশন বর্ণনা করার জন্য বিভিন্ন নাম ব্যবহার করা হয়। স্টাব ক্লাসটি বাইন্ডার ড্রাইভার থেকে কমান্ড পড়ে এবং আপনার ইমপ্লিমেন্ট করা মেথডগুলো এক্সিকিউট করে। কল্পনা করুন যে আপনার কাছে এইরকম একটি AIDL ফাইল আছে:
package my.package;
interface IFoo {
int doFoo();
}
জাভাতে, আপনাকে অবশ্যই জেনারেটেড Stub ক্লাস থেকে এক্সটেন্ড করতে হবে:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
CPP ব্যাকএন্ডে:
#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;
}
রাস্ট ব্যাকএন্ডে:
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(())
}
}
নিবন্ধন করুন এবং পরিষেবাগুলি গ্রহণ করুন
অ্যান্ড্রয়েড প্ল্যাটফর্মে সার্ভিসগুলো সাধারণত servicemanager প্রসেসের মাধ্যমে রেজিস্টার করা হয়। নিম্নলিখিত এপিআইগুলো ছাড়াও, কিছু এপিআই সার্ভিসটি চেক করে (অর্থাৎ সার্ভিসটি উপলব্ধ না থাকলে সেগুলো সাথে সাথে রিটার্ন করে)। সঠিক বিবরণের জন্য সংশ্লিষ্ট servicemanager ইন্টারফেসটি দেখুন। আপনি এই অপারেশনগুলো শুধুমাত্র অ্যান্ড্রয়েড প্ল্যাটফর্মের জন্য কম্পাইল করার সময়ই সম্পাদন করতে পারবেন।
জাভাতে:
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"));
CPP ব্যাকএন্ডে:
#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")));
রাস্ট ব্যাকএন্ডে:
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::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();
});
}
মাল্টিথ্রেডেড টোকিও রানটাইমের ক্ষেত্রে, স্পন করা টাস্কগুলো মেইন থ্রেডে এক্সিকিউট হয় না। তাই, মেইন থ্রেডে join_thread_pool কল করাই বেশি যুক্তিযুক্ত, যাতে মেইন থ্রেডটি নিষ্ক্রিয় না থাকে। অ্যাসিঙ্ক কনটেক্সট থেকে বেরিয়ে আসার জন্য আপনাকে অবশ্যই কলটিকে block_in_place মধ্যে র্যাপ করতে হবে।
মৃত্যুর সাথে সংযোগ
যখন কোনো বাইন্ডার হোস্টকারী সার্ভিস বন্ধ হয়ে যায়, তখন তার জন্য নোটিফিকেশন পাওয়ার অনুরোধ করতে পারেন। এটি কলব্যাক প্রক্সি লিক হওয়া এড়াতে বা ত্রুটি পুনরুদ্ধারে সহায়তা করতে পারে। বাইন্ডার প্রক্সি অবজেক্টগুলিতে এই কলগুলি করুন।
- জাভাতে,
android.os.IBinder::linkToDeathব্যবহার করুন। - CPP ব্যাকএন্ডে
android::IBinder::linkToDeathব্যবহার করুন। - NDK ব্যাকএন্ডে,
AIBinder_linkToDeathব্যবহার করুন। আপনার ডেথ রিসিপিয়েন্ট কুকির জীবনকাল নিয়ন্ত্রণ করতে সর্বদাAIBinder_DeathRecipient_setOnUnlinkedব্যবহার করুন। - রাস্ট ব্যাকএন্ডে, একটি
DeathRecipientঅবজেক্ট তৈরি করুন, তারপরmy_binder.link_to_death(&mut my_death_recipient)কল করুন। মনে রাখবেন, যেহেতুDeathRecipientকলব্যাকটির মালিক, তাই যতক্ষণ আপনি নোটিফিকেশন পেতে চান, ততক্ষণ আপনাকে অবশ্যই সেই অবজেক্টটিকে সচল রাখতে হবে।
কলার তথ্য
একটি কার্নেল বাইন্ডার কল গ্রহণ করার সময়, কলারের তথ্য বিভিন্ন API-তে পাওয়া যায়। প্রসেস আইডি (PID) বলতে সেই প্রসেসের লিনাক্স প্রসেস আইডিকে বোঝায় যা একটি ট্রানজ্যাকশন পাঠাচ্ছে। ইউজার আইডি (UI) বলতে লিনাক্স ইউজার আইডিকে বোঝায়। একটি একমুখী কল গ্রহণ করার সময়, কলিং PID-এর মান ০ হয়। বাইন্ডার ট্রানজ্যাকশন কনটেক্সটের বাইরে, এই ফাংশনগুলো বর্তমান প্রসেসের PID এবং UID রিটার্ন করে।
জাভা ব্যাকএন্ডে:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
CPP ব্যাকএন্ডে:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
এনডিকে ব্যাকএন্ডে:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
রাস্ট ব্যাকএন্ডে, ইন্টারফেসটি ইমপ্লিমেন্ট করার সময়, ডিফল্ট অবস্থা বজায় না রেখে নিম্নলিখিতটি উল্লেখ করুন:
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
পরিষেবাগুলির জন্য বাগ রিপোর্ট এবং ডিবাগিং এপিআই
যখন বাগ রিপোর্ট চালানো হয় (উদাহরণস্বরূপ, adb bugreport দিয়ে), তখন এটি বিভিন্ন সমস্যা ডিবাগ করতে সাহায্য করার জন্য সিস্টেমের চারপাশ থেকে তথ্য সংগ্রহ করে। AIDL সার্ভিসগুলোর ক্ষেত্রে, বাগ রিপোর্টগুলো সার্ভিস ম্যানেজারের সাথে রেজিস্টার করা সমস্ত সার্ভিসের তথ্য বাগ রিপোর্টে ডাম্প করার জন্য dumpsys dumpsys ব্যবহার করে। আপনি কমান্ড লাইনেও dumpsys SERVICE [ARGS] ব্যবহার করে কোনো সার্ভিস থেকে তথ্য পেতে পারেন। C++ এবং Java ব্যাকএন্ডে, আপনি addService এ অতিরিক্ত আর্গুমেন্ট ব্যবহার করে সার্ভিসগুলো কোন ক্রমে ডাম্প হবে তা নিয়ন্ত্রণ করতে পারেন। ডিবাগ করার সময় কোনো সার্ভিসের PID পেতে আপনি dumpsys --pid SERVICE ও ব্যবহার করতে পারেন।
আপনার সার্ভিসে কাস্টম আউটপুট যোগ করতে, আপনার সার্ভার অবজেক্টে ` dump মেথডটি ওভাররাইড করুন, ঠিক যেমনভাবে আপনি একটি AIDL ফাইলে সংজ্ঞায়িত অন্য যেকোনো IPC মেথড ইমপ্লিমেন্ট করেন। এটি করার সময়, ডাম্পিংকে android.permission.DUMP অ্যাপ পারমিশনে অথবা নির্দিষ্ট UID-গুলিতে সীমাবদ্ধ করুন।
জাভা ব্যাকএন্ডে:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
CPP ব্যাকএন্ডে:
status_t dump(int, const android::android::Vector<android::String16>&) override;
এনডিকে ব্যাকএন্ডে:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
রাস্ট ব্যাকএন্ডে, ইন্টারফেসটি ইমপ্লিমেন্ট করার সময়, ডিফল্ট অবস্থা বজায় না রেখে নিম্নলিখিতটি উল্লেখ করুন:
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
দুর্বল পয়েন্টার ব্যবহার করুন
আপনি একটি বাইন্ডার অবজেক্টের প্রতি একটি দুর্বল রেফারেন্স ধরে রাখতে পারেন।
যদিও জাভা WeakReference সমর্থন করে, এটি নেটিভ লেয়ারে উইক বাইন্ডার রেফারেন্স সমর্থন করে না।
CPP ব্যাকএন্ডে, দুর্বল টাইপটি হলো wp<IFoo> ।
NDK ব্যাকএন্ডে, ScopedAIBinder_Weak ব্যবহার করুন:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
রাস্ট ব্যাকএন্ডে WpIBinder অথবা Weak<IFoo> ব্যবহার করুন:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
ডায়নামিকভাবে ইন্টারফেস ডেসক্রিপ্টর পান
ইন্টারফেস ডেসক্রিপ্টর একটি ইন্টারফেসের ধরন শনাক্ত করে। ডিবাগিং করার সময় অথবা যখন কোনো অজানা বাইন্ডার থাকে, তখন এটি কাজে আসে।
জাভাতে, আপনি নিম্নলিখিত কোডের মাধ্যমে ইন্টারফেস ডেসক্রিপ্টর পেতে পারেন:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
CPP ব্যাকএন্ডে:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
এনডিকে এবং রাস্ট ব্যাকএন্ড এই সক্ষমতা সমর্থন করে না।
স্ট্যাটিক্যালি ইন্টারফেস ডেসক্রিপ্টর পান
কখনও কখনও (যেমন @VintfStability সার্ভিস রেজিস্টার করার সময়), ইন্টারফেস ডেসক্রিপ্টরটি স্ট্যাটিক্যালি কী তা আপনার জানার প্রয়োজন হয়। জাভাতে, আপনি নিম্নলিখিত কোডের মতো কোড যোগ করে ডেসক্রিপ্টরটি পেতে পারেন:
import my.package.IFoo;
... IFoo.DESCRIPTOR
CPP ব্যাকএন্ডে:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
NDK ব্যাকএন্ডে (অতিরিক্ত aidl নেমস্পেসটি লক্ষ্য করুন):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
রাস্ট ব্যাকএন্ডে:
aidl::my::package::BnFoo::get_descriptor()
এনাম পরিসর
নেটিভ ব্যাকএন্ডে, একটি enum-এর সম্ভাব্য মানগুলোর উপর পুনরাবৃত্তি করা যায়। কোডের আকার সীমিত রাখার কারণে, জাভাতে এটি সমর্থিত নয়।
AIDL-এ সংজ্ঞায়িত MyEnum নামক একটি enum-এর জন্য, পুনরাবৃত্তি নিম্নরূপভাবে প্রদান করা হয়।
CPP ব্যাকএন্ডে:
::android::enum_range<MyEnum>()
এনডিকে ব্যাকএন্ডে:
::ndk::enum_range<MyEnum>()
রাস্ট ব্যাকএন্ডে:
MyEnum::enum_values()
থ্রেড ব্যবস্থাপনা
একটি প্রসেসের মধ্যে libbinder এর প্রতিটি ইনস্ট্যান্স একটি থ্রেডপুল বজায় রাখে। বেশিরভাগ ক্ষেত্রে, এটি ঠিক একটিই থ্রেডপুল হওয়া উচিত, যা সমস্ত ব্যাকএন্ড জুড়ে শেয়ার করা হয়। এর একমাত্র ব্যতিক্রম হলো যদি ভেন্ডর কোড /dev/vndbinder এর সাথে যোগাযোগের জন্য libbinder এর আরেকটি কপি লোড করে। এটি একটি আলাদা বাইন্ডার নোডে থাকে, তাই থ্রেডপুলটি শেয়ার করা হয় না।
জাভা ব্যাকএন্ডের ক্ষেত্রে, থ্রেডপুলের আকার কেবল বাড়তেই পারে (কারণ এটি ইতিমধ্যেই চালু হয়ে গেছে):
BinderInternal.setMaxThreads(<new larger value>);
CPP ব্যাকএন্ডের জন্য নিম্নলিখিত অপারেশনগুলো উপলব্ধ রয়েছে:
// 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();
একইভাবে, এনডিকে ব্যাকএন্ডে:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
রাস্ট ব্যাকএন্ডে:
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-এর ক্ষেত্রে, ফিল্ড বা টাইপের নাম পরিবর্তন করা হয় 'raw identifier' সিনট্যাক্স ব্যবহার করে, যা r# প্রিফিক্স ব্যবহার করে অ্যাক্সেস করা যায়।
অসুবিধাজনক বাইন্ডিং বা সরাসরি কম্পাইলেশন ব্যর্থতা এড়ানোর জন্য, আমরা সুপারিশ করি যে সম্ভব হলে আপনার AIDL ডেফিনিশনে সংরক্ষিত নাম ব্যবহার করা থেকে বিরত থাকুন।
আপনার AIDL ডেফিনিশনে যদি আগে থেকেই সংরক্ষিত নাম থাকে, তাহলে আপনি প্রোটোকল সামঞ্জস্যতা বজায় রেখে নিরাপদে ফিল্ডগুলির নাম পরিবর্তন করতে পারেন। বিল্ড চালিয়ে যাওয়ার জন্য আপনার কোড আপডেট করার প্রয়োজন হতে পারে, কিন্তু ইতিমধ্যে বিল্ড করা প্রোগ্রামগুলি একে অপরের সাথে কাজ করা অব্যাহত রাখবে।
যেসব নাম এড়িয়ে চলবেন: