একটি এআইডিএল ব্যাকএন্ড হল স্টাব কোড তৈরির লক্ষ্য। AIDL ফাইলগুলি ব্যবহার করার সময়, আপনি সর্বদা একটি নির্দিষ্ট রানটাইম সহ একটি নির্দিষ্ট ভাষায় ব্যবহার করেন। প্রেক্ষাপটের উপর নির্ভর করে, আপনার বিভিন্ন AIDL ব্যাকএন্ড ব্যবহার করা উচিত।
নিম্নলিখিত সারণীতে, API পৃষ্ঠের স্থায়িত্ব বলতে এই API পৃষ্ঠের বিরুদ্ধে কোড কম্পাইল করার ক্ষমতাকে বোঝায় যাতে কোডটি system.img
libbinder.so
বাইনারি থেকে স্বাধীনভাবে বিতরণ করা যায়।
AIDL এর নিম্নলিখিত ব্যাকএন্ড রয়েছে:
ব্যাকএন্ড | ভাষা | API পৃষ্ঠ | সিস্টেম তৈরি করুন |
---|---|---|---|
জাভা | জাভা | SDK/SystemApi (স্থিতিশীল*) | সব |
এনডিকে | সি++ | libbinder_ndk (স্থিতিশীল*) | aidl_interface |
সিপিপি | সি++ | লিবিন্ডার (অস্থির) | সব |
মরিচা | মরিচা | libbinder_rs (স্থিতিশীল*) | aidl_interface |
- এই API পৃষ্ঠতলগুলি স্থিতিশীল, কিন্তু অনেক API, যেমন পরিষেবা পরিচালনার জন্য, অভ্যন্তরীণ প্ল্যাটফর্ম ব্যবহারের জন্য সংরক্ষিত এবং অ্যাপগুলিতে উপলব্ধ নয়। অ্যাপে এআইডিএল কীভাবে ব্যবহার করবেন সে সম্পর্কে আরও তথ্যের জন্য, বিকাশকারী ডকুমেন্টেশন দেখুন।
- মরিচা ব্যাকএন্ড অ্যান্ড্রয়েড 12 এ চালু করা হয়েছিল; এনডিকে ব্যাকএন্ড অ্যান্ড্রয়েড 10 হিসাবে উপলব্ধ।
- মরিচা ক্রেটটি
libbinder_ndk
এর উপরে নির্মিত, যা এটিকে স্থিতিশীল এবং বহনযোগ্য হতে দেয়। APEXes বাইন্ডার ক্রেটটি একইভাবে ব্যবহার করে যেমনটি সিস্টেমের পাশে অন্য কেউ করে। মরিচা অংশটি একটি এপেক্সে বান্ডিল করা হয় এবং এর ভিতরে পাঠানো হয়। এটিlibbinder_ndk.so
সিস্টেম পার্টিশনের উপর নির্ভর করে।
সিস্টেম তৈরি করুন
ব্যাকএন্ডের উপর নির্ভর করে, স্টাব কোডে AIDL কম্পাইল করার দুটি উপায় রয়েছে। বিল্ড সিস্টেম সম্পর্কে আরো বিস্তারিত জানার জন্য, সুং মডিউল রেফারেন্স দেখুন।
কোর বিল্ড সিস্টেম
যেকোন cc_
বা java_
Android.bp মডিউলে (বা তাদের Android.mk
সমতুল্য), .aidl
ফাইলগুলিকে সোর্স ফাইল হিসাবে নির্দিষ্ট করা যেতে পারে। এই ক্ষেত্রে, AIDL-এর Java/CPP ব্যাকএন্ড ব্যবহার করা হয় (NDK ব্যাকএন্ড নয়), এবং সংশ্লিষ্ট AIDL ফাইলগুলি ব্যবহার করার জন্য ক্লাসগুলি স্বয়ংক্রিয়ভাবে মডিউলে যোগ করা হয়। বিকল্প যেমন local_include_dirs
, যা বিল্ড সিস্টেমকে সেই মডিউলের AIDL ফাইলগুলির রুট পাথ বলে একটি aidl:
গ্রুপের অধীনে এই মডিউলগুলিতে নির্দিষ্ট করা যেতে পারে। মনে রাখবেন যে মরিচা ব্যাকএন্ড শুধুমাত্র মরিচা ব্যবহার করার জন্য। rust_
মডিউলগুলি ভিন্নভাবে পরিচালনা করা হয় যাতে AIDL ফাইলগুলি উত্স ফাইল হিসাবে নির্দিষ্ট করা হয় না। পরিবর্তে, aidl_interface
মডিউলটি <aidl_interface name>-rust
নামে একটি rustlib
তৈরি করে যার সাথে সংযুক্ত করা যেতে পারে। আরো বিস্তারিত জানার জন্য, মরিচা এআইডিএল উদাহরণ দেখুন।
aidl_interface
এই বিল্ড সিস্টেমের সাথে ব্যবহৃত প্রকারগুলি অবশ্যই কাঠামোগত হতে হবে। কাঠামোবদ্ধ হওয়ার জন্য, পার্সেবলে অবশ্যই সরাসরি ক্ষেত্র থাকতে হবে এবং লক্ষ্য ভাষায় সরাসরি সংজ্ঞায়িত প্রকারের ঘোষণা হবে না। স্থিতিশীল AIDL-এর সাথে কীভাবে কাঠামোগত AIDL ফিট করে, দেখুন স্ট্রাকচার্ড বনাম স্থিতিশীল AIDL ।
প্রকারভেদ
আপনি প্রকারের জন্য একটি রেফারেন্স বাস্তবায়ন হিসাবে aidl
কম্পাইলার বিবেচনা করতে পারেন। যখন আপনি একটি ইন্টারফেস তৈরি করেন, তখন aidl --lang=<backend> ...
ব্যবহার করুন ফলে ইন্টারফেস ফাইলটি দেখতে। আপনি যখন aidl_interface
মডিউল ব্যবহার করেন, আপনি out/soong/.intermediates/<path to module>/
এ আউটপুট দেখতে পারেন।
জাভা/এআইডিএল টাইপ | C++ প্রকার | এনডিকে টাইপ | মরিচা প্রকার |
---|---|---|---|
বুলিয়ান | bool | bool | bool |
বাইট 8 | int8_t | int8_t | i8 |
চর | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
দীর্ঘ | int64_t | int64_t | i64 |
ভাসা | ভাসা | ভাসা | f32 |
দ্বিগুণ | দ্বিগুণ | দ্বিগুণ | f64 |
স্ট্রিং | অ্যান্ড্রয়েড::স্ট্রিং16 | std::স্ট্রিং | ইন: &str আউট: স্ট্রিং |
android.os.Parcelable | অ্যান্ড্রয়েড::পার্সেলেবল | N/A | N/A |
আইবিন্ডার | অ্যান্ড্রয়েড::আইবিন্ডার | ndk::SpAIBinder | বাইন্ডার::স্পাইবাইন্ডার |
টি[] | std::ভেক্টর<T> | std::ভেক্টর<T> | মধ্যে: &[টি] আউট: Vec<T> |
বাইট | std::ভেক্টর<uint8_t> | std::ভেক্টর<int8_t> 1 | মধ্যে: &[u8] আউট: Vec<u8> |
তালিকা<T> | std::vector<T> 2 | std::ভেক্টর<T> 3 | In: &[T] 4 আউট: Vec<T> |
ফাইল বর্ণনাকারী | android::base::unique_fd | N/A | বাইন্ডার::parcel::ParcelFileDescriptor |
পার্সেলফাইল বর্ণনাকারী | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | বাইন্ডার::parcel::ParcelFileDescriptor |
ইন্টারফেসের ধরন (টি) | android::sp<T> | std::shared_ptr<T> 7 | বাইন্ডার::শক্তিশালী |
পার্সেলযোগ্য প্রকার (টি) | টি | টি | টি |
ইউনিয়ন টাইপ (T) 5 | টি | টি | টি |
T[N] 6 | std:: array<T, N> | std:: array<T, N> | [টি; ন] |
1. Android 12 বা উচ্চতর সংস্করণে, বাইট অ্যারেগুলি সামঞ্জস্যের কারণে int8_t এর পরিবর্তে uint8_t ব্যবহার করে৷
2. C++ ব্যাকএন্ড List<T>
সমর্থন করে যেখানে T
হল String
, IBinder
, ParcelFileDescriptor
বা parcelable এর মধ্যে একটি। অ্যান্ড্রয়েড 13 বা উচ্চতর, T
অ্যারে ব্যতীত যেকোন নন-প্রিমিটিভ টাইপ (ইন্টারফেসের ধরন সহ) হতে পারে। AOSP সুপারিশ করে যে আপনি T[]
মত অ্যারে প্রকার ব্যবহার করুন, যেহেতু তারা সমস্ত ব্যাকএন্ডে কাজ করে।
3. NDK ব্যাকএন্ড List<T>
সমর্থন করে যেখানে T
হল String
, ParcelFileDescriptor
বা পার্সেলেবলের একটি। অ্যান্ড্রয়েড 13 বা উচ্চতর, T
অ্যারে ব্যতীত যেকোন অ-আদি প্রকার হতে পারে।
4. রাস্ট কোডের জন্য প্রকারগুলি ভিন্নভাবে পাস করা হয় তা নির্ভর করে সেগুলি ইনপুট (একটি যুক্তি), বা একটি আউটপুট (একটি ফেরত মান)।
5. ইউনিয়ন প্রকারগুলি Android 12 এবং উচ্চতর সংস্করণে সমর্থিত।
6. অ্যান্ড্রয়েড 13 বা উচ্চতর, স্থির আকারের অ্যারে সমর্থিত। স্থির-আকারের অ্যারেতে একাধিক মাত্রা থাকতে পারে (যেমন int[3][4]
)। জাভা ব্যাকএন্ডে, স্থির-আকারের অ্যারেগুলিকে অ্যারের প্রকার হিসাবে উপস্থাপন করা হয়।
7. একটি বাইন্ডার SharedRefBase
অবজেক্ট ইনস্ট্যান্ট করতে, SharedRefBase::make\<My\>(... args ...)
ব্যবহার করুন। এই ফাংশনটি একটি std::shared_ptr\<T\>
অবজেক্ট তৈরি করে যা অভ্যন্তরীণভাবেও পরিচালিত হয়, যদি বাইন্ডারটি অন্য প্রক্রিয়ার মালিকানাধীন হয়। বস্তুটি অন্য উপায়ে তৈরি করলে দ্বি-মালিকানা হয়।
8. এছাড়াও Java/AIDL টাইপ byte[]
।
দিকনির্দেশনা (ইন/আউট/ইনআউট)
ফাংশনে আর্গুমেন্টের ধরন নির্দিষ্ট করার সময়, আপনি এগুলিকে in
, out
, বা inout
হিসাবে নির্দিষ্ট করতে পারেন। এটি আইপিসি কলের জন্য কোন দিক থেকে তথ্য পাঠানো হয় তা নিয়ন্ত্রণ করে। in
হল ডিফল্ট দিক, এবং এটি নির্দেশ করে যে কলার থেকে কলকারীর কাছে ডেটা চলে গেছে। out
মানে কলকারী থেকে কলারের কাছে ডেটা প্রেরণ করা হয়। inout
হল এই উভয়ের সমন্বয়। যাইহোক, Android টিম সুপারিশ করে যে আপনি inout
আর্গুমেন্ট স্পেসিফায়ার ব্যবহার এড়ান। আপনি যদি একটি সংস্করণযুক্ত ইন্টারফেস এবং একটি পুরানো কলির সাথে inout
ব্যবহার করেন, তবে অতিরিক্ত ক্ষেত্রগুলি যেগুলি শুধুমাত্র কলারে উপস্থিত থাকে সেগুলি তাদের ডিফল্ট মানগুলিতে পুনরায় সেট করা হয়। মরিচা সংক্রান্ত বিষয়ে, একটি সাধারণ 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
CPP ব্যাকএন্ডের সাহায্যে আপনি স্ট্রিংগুলি utf-8 বা utf-16 কিনা তা চয়ন করতে পারেন। স্বয়ংক্রিয়ভাবে utf-8 তে রূপান্তর করতে AIDL-এ স্ট্রিংগুলিকে @utf8InCpp String
হিসাবে ঘোষণা করুন। NDK এবং Rust ব্যাকএন্ড সবসময় utf-8 স্ট্রিং ব্যবহার করে। utf8InCpp
টীকা সম্পর্কে আরও তথ্যের জন্য, AIDL-এ টীকা দেখুন।
শূন্যতা
আপনি @nullable
এর সাথে নাল হতে পারে এমন ধরনের টীকা দিতে পারেন। nullable
টীকা সম্পর্কে আরও তথ্যের জন্য, AIDL-এ টীকা দেখুন।
কাস্টম পার্সেবল
একটি কাস্টম পার্সেলেবল হল একটি পার্সেবল যা একটি টার্গেট ব্যাকএন্ডে ম্যানুয়ালি প্রয়োগ করা হয়। শুধুমাত্র তখনই কাস্টম পার্সেবল ব্যবহার করুন যখন আপনি একটি বিদ্যমান কাস্টম পার্সেলেবলের জন্য অন্য ভাষায় সমর্থন যোগ করার চেষ্টা করছেন যা পরিবর্তন করা যাবে না।
একটি কাস্টম পার্সেলেবল ঘোষণা করার জন্য যাতে AIDL এটি সম্পর্কে জানে, 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);
};
অ্যান্ড্রয়েড 15-এ, AIDL-এ একটি কাস্টম মরিচা পার্সেলযোগ্য ঘোষণার জন্য, 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);
তারপরে আপনি এই পার্সেলেবলটিকে এআইডিএল ফাইলগুলিতে একটি টাইপ হিসাবে ব্যবহার করতে পারেন, তবে এটি এআইডিএল দ্বারা তৈরি করা হবে না। <
এবং ==
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.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)
মরিচা উদাহরণ
মরিচায়, ইউনিয়নগুলি 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
ত্রুটি হ্যান্ডলিং
অ্যান্ড্রয়েড ওএস ত্রুটি রিপোর্ট করার সময় পরিষেবাগুলির জন্য অন্তর্নির্মিত ত্রুটির প্রকারগুলি সরবরাহ করে৷ এগুলি বাইন্ডার দ্বারা ব্যবহৃত হয় এবং একটি বাইন্ডার ইন্টারফেস বাস্তবায়নকারী যেকোনো পরিষেবা দ্বারা ব্যবহার করা যেতে পারে। এআইডিএল সংজ্ঞায় তাদের ব্যবহার ভালোভাবে নথিভুক্ত করা হয়েছে এবং তাদের কোনো ব্যবহারকারী-সংজ্ঞায়িত অবস্থা বা রিটার্ন টাইপের প্রয়োজন নেই।
ত্রুটি সহ আউটপুট পরামিতি
যখন একটি এআইডিএল ফাংশন একটি ত্রুটি রিপোর্ট করে, তখন ফাংশনটি আউটপুট প্যারামিটারগুলি শুরু বা সংশোধন করতে পারে না। বিশেষত, আউটপুট প্যারামিটার সংশোধন করা যেতে পারে যদি ত্রুটিটি লেনদেনের প্রক্রিয়াকরণের সময় ঘটার বিপরীতে আনপার্সেলিংয়ের সময় ঘটে। সাধারণভাবে, যখন একটি AIDL ফাংশন থেকে একটি ত্রুটি পাওয়া যায়, তখন সমস্ত inout
এবং out
প্যারামিটারের পাশাপাশি রিটার্ন মান (যা কিছু ব্যাকএন্ডে একটি out
প্যারামিটারের মতো কাজ করে) একটি অনির্দিষ্ট অবস্থায় বিবেচনা করা উচিত।
কোন ত্রুটি মান ব্যবহার করতে হবে
বিল্ট-ইন ত্রুটির মানগুলির অনেকগুলি যেকোন এআইডিএল ইন্টারফেসে ব্যবহার করা যেতে পারে, তবে কিছু একটি বিশেষ উপায়ে চিকিত্সা করা হয়। উদাহরণস্বরূপ, EX_UNSUPPORTED_OPERATION
এবং EX_ILLEGAL_ARGUMENT
যখন ত্রুটির অবস্থা বর্ণনা করে তখন ব্যবহার করা ঠিক, কিন্তু EX_TRANSACTION_FAILED
ব্যবহার করা উচিত নয় কারণ এটি অন্তর্নিহিত অবকাঠামো দ্বারা বিশেষভাবে বিবেচিত হয়৷ এই অন্তর্নির্মিত মান সম্পর্কে আরও তথ্যের জন্য ব্যাকএন্ড নির্দিষ্ট সংজ্ঞা পরীক্ষা করুন।
যদি AIDL ইন্টারফেসের জন্য অতিরিক্ত ত্রুটির মান প্রয়োজন হয় যা বিল্ট-ইন ত্রুটির ধরন দ্বারা আচ্ছাদিত না হয়, তাহলে তারা বিশেষ পরিষেবা-নির্দিষ্ট বিল্ট-ইন ত্রুটি ব্যবহার করতে পারে যা ব্যবহারকারীর দ্বারা সংজ্ঞায়িত পরিষেবা-নির্দিষ্ট ত্রুটি মান অন্তর্ভুক্ত করার অনুমতি দেয় . These service-specific errors are typically defined in the AIDL interface as a const int
or int
-backed enum
and aren't parsed by binder.
জাভাতে, ত্রুটিগুলি ব্যতিক্রমগুলিতে ম্যাপ করে, যেমন android.os.RemoteException
। পরিষেবা-নির্দিষ্ট ব্যতিক্রমগুলির জন্য, Java ব্যবহারকারী-সংজ্ঞায়িত ত্রুটি সহ android.os.ServiceSpecificException
ব্যবহার করে।
অ্যান্ড্রয়েডে নেটিভ কোড ব্যতিক্রম ব্যবহার করে না। CPP ব্যাকএন্ড android::binder::Status
ব্যবহার করে। NDK ব্যাকএন্ড ndk::ScopedAStatus
ব্যবহার করে। এআইডিএল দ্বারা উত্পন্ন প্রতিটি পদ্ধতি এইগুলির মধ্যে একটি প্রদান করে, পদ্ধতির অবস্থা উপস্থাপন করে। মরিচা ব্যাকএন্ড NDK-এর মতো একই ব্যতিক্রম কোড মান ব্যবহার করে, কিন্তু ব্যবহারকারীর কাছে সেগুলি সরবরাহ করার আগে সেগুলিকে নেটিভ রাস্ট ত্রুটি ( StatusCode
, ExceptionCode
) এ রূপান্তরিত করে। পরিষেবা-নির্দিষ্ট ত্রুটির জন্য, প্রত্যাবর্তিত Status
বা ScopedAStatus
ব্যবহারকারী-সংজ্ঞায়িত ত্রুটি সহ EX_SERVICE_SPECIFIC
ব্যবহার করে৷
অন্তর্নির্মিত ত্রুটি প্রকারগুলি নিম্নলিখিত ফাইলগুলিতে পাওয়া যাবে:
ব্যাকএন্ড | সংজ্ঞা |
---|---|
জাভা | android/os/Parcel.java |
সিপিপি | binder/Status.h |
এনডিকে | android/binder_status.h |
মরিচা | android/binder_status.h |
বিভিন্ন ব্যাকএন্ড ব্যবহার করুন
এই নির্দেশাবলী Android প্ল্যাটফর্ম কোডের জন্য নির্দিষ্ট। এই উদাহরণগুলি একটি সংজ্ঞায়িত প্রকার, my.package.IFoo
ব্যবহার করে। মরিচা ব্যাকএন্ড কীভাবে ব্যবহার করবেন তার নির্দেশাবলীর জন্য, অ্যান্ড্রয়েড রাস্ট প্যাটার্নস পৃষ্ঠায় রাস্ট এআইডিএল উদাহরণ দেখুন।
আমদানি প্রকার
সংজ্ঞায়িত টাইপটি একটি ইন্টারফেস, পার্সেলেবল বা ইউনিয়ন হোক না কেন, আপনি এটি জাভাতে আমদানি করতে পারেন:
import my.package.IFoo;
অথবা CPP ব্যাকএন্ডে:
#include <my/package/IFoo.h>
অথবা NDK ব্যাকএন্ডে (অতিরিক্ত 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>
অন্তর্ভুক্ত করতে হবে (বা <aidl/my/package/IFoo.h>
NDK ব্যাকএন্ডের জন্য <aidl/my/package/IFoo.h>
)।
সেবা বাস্তবায়ন
একটি পরিষেবা বাস্তবায়ন করতে, আপনাকে অবশ্যই নেটিভ স্টাব ক্লাস থেকে উত্তরাধিকারসূত্রে প্রাপ্ত হতে হবে। এই ক্লাসটি বাইন্ডার ড্রাইভারের কাছ থেকে কমান্ডগুলি পড়ে এবং আপনি যে পদ্ধতিগুলি প্রয়োগ করেন তা কার্যকর করে। কল্পনা করুন যে আপনার কাছে এইরকম একটি AIDL ফাইল আছে:
package my.package;
interface IFoo {
int doFoo();
}
জাভাতে, আপনাকে অবশ্যই এই ক্লাস থেকে প্রসারিত করতে হবে:
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
প্রক্রিয়ার সাথে নিবন্ধিত হয়। নীচের APIগুলি ছাড়াও, কিছু API পরিষেবাটি পরীক্ষা করে (অর্থাৎ পরিষেবাটি উপলব্ধ না হলে তারা অবিলম্বে ফিরে আসে)। সঠিক বিবরণের জন্য সংশ্লিষ্ট 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 will run on this thread.
std::future::pending().await
}
অন্যান্য বিকল্পগুলির থেকে একটি গুরুত্বপূর্ণ পার্থক্য হল যে আমরা async Rust এবং একক-থ্রেডেড রানটাইম ব্যবহার করার সময় 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
ব্যবহার করুন। - মরিচা ব্যাকএন্ডে, একটি
DeathRecipient
অবজেক্ট তৈরি করুন, তারপরmy_binder.link_to_death(&mut my_death_recipient)
কল করুন। নোট করুন যেDeathRecipient
কলব্যাকের মালিক, তাই যতক্ষণ আপনি বিজ্ঞপ্তি পেতে চান ততক্ষণ আপনাকে সেই বস্তুটিকে জীবিত রাখতে হবে।
কলারের তথ্য
একটি কার্নেল বাইন্ডার কল গ্রহণ করার সময়, কলার তথ্য বিভিন্ন API-এ উপলব্ধ। পিআইডি (বা প্রসেস আইডি) সেই প্রক্রিয়ার লিনাক্স প্রসেস আইডিকে বোঝায় যা একটি লেনদেন পাঠাচ্ছে। ইউআইডি (বা ইউজার আইডি) লিনাক্স ইউজার আইডিকে বোঝায়। একটি একমুখী কল গ্রহণ করার সময়, কলিং PID 0 হয়। যখন একটি বাইন্ডার লেনদেন প্রসঙ্গের বাইরে, এই ফাংশনগুলি বর্তমান প্রক্রিয়ার PID এবং UID ফেরত দেয়।
জাভা ব্যাকএন্ডে:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
CPP ব্যাকএন্ডে:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
NDK ব্যাকএন্ডে:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
মরিচা ব্যাকএন্ডে, ইন্টারফেস প্রয়োগ করার সময়, নিম্নলিখিতটি নির্দিষ্ট করুন (এটিকে ডিফল্ট করার অনুমতি দেওয়ার পরিবর্তে):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
বাগ রিপোর্ট এবং পরিষেবার জন্য ডিবাগিং API
যখন বাগ রিপোর্ট চালানো হয় (উদাহরণস্বরূপ, adb bugreport
সহ), তারা বিভিন্ন সমস্যা ডিবাগ করতে সহায়তা করার জন্য সিস্টেমের চারপাশ থেকে তথ্য সংগ্রহ করে। AIDL পরিষেবাগুলির জন্য, বাগ রিপোর্টগুলি বাগ রিপোর্টে তাদের তথ্য ডাম্প করতে পরিষেবা পরিচালকের সাথে নিবন্ধিত সমস্ত পরিষেবাগুলিতে বাইনারি dumpsys
ব্যবহার করে৷ আপনি dumpsys SERVICE [ARGS]
এর সাথে একটি পরিষেবা থেকে তথ্য পেতে কমান্ডলাইনে dumpsys
ব্যবহার করতে পারেন। C++ এবং Java ব্যাকএন্ডে, addService
এ অতিরিক্ত আর্গুমেন্ট ব্যবহার করে আপনি কোন ক্রমে পরিষেবাগুলি ডাম্প করা হবে তা নিয়ন্ত্রণ করতে পারেন। ডিবাগ করার সময় একটি পরিষেবার পিআইডি পেতে আপনি 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;
NDK ব্যাকএন্ডে:
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();
NDK এবং মরিচা ব্যাকএন্ড এই ক্ষমতা সমর্থন করে না।
স্থিরভাবে ইন্টারফেস বর্ণনাকারী পান
কখনও কখনও (যেমন @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 পরিসীমা
নেটিভ ব্যাকএন্ডে, আপনি একটি enum গ্রহণ করতে পারে এমন সম্ভাব্য মানগুলির উপর পুনরাবৃত্তি করতে পারেন। কোড আকার বিবেচনার কারণে, এটি জাভাতে সমর্থিত নয়।
AIDL-এ সংজ্ঞায়িত একটি enum MyEnum
জন্য, পুনরাবৃত্তি নিম্নরূপ প্রদান করা হয়।
CPP ব্যাকএন্ডে:
::android::enum_range<MyEnum>()
NDK ব্যাকএন্ডে:
::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();
একইভাবে, NDK ব্যাকএন্ডে:
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 এর জন্য একটি সংকলন ব্যর্থতা হতে পারে। মরিচা-এর জন্য, ক্ষেত্র বা প্রকারের নামকরণ করা হয় "raw identifier" সিনট্যাক্স ব্যবহার করে, r#
উপসর্গ ব্যবহার করে অ্যাক্সেসযোগ্য।
আমরা সুপারিশ করি যে আপনি আপনার AIDL সংজ্ঞাগুলিতে সংরক্ষিত নামগুলি ব্যবহার করা এড়িয়ে চলুন যেখানে সম্ভব unergonomic বাইন্ডিং বা সম্পূর্ণ সংকলন ব্যর্থতা এড়াতে।
আপনার যদি ইতিমধ্যেই আপনার AIDL সংজ্ঞাগুলিতে সংরক্ষিত নাম থাকে, তবে প্রোটোকল সামঞ্জস্যপূর্ণ থাকা অবস্থায় আপনি নিরাপদে ক্ষেত্রগুলির নাম পরিবর্তন করতে পারেন; বিল্ডিং চালিয়ে যাওয়ার জন্য আপনাকে আপনার কোড আপডেট করতে হতে পারে, কিন্তু যেকোনও আগে থেকে নির্মিত প্রোগ্রাম ইন্টারঅপারেটে চলতে থাকবে।
এড়ানোর জন্য নাম: