স্টাব কোড জেনারেশনের জন্য AIDL ব্যাকএন্ড হল একটি টার্গেট। নির্দিষ্ট রানটাইম সহ কোনও নির্দিষ্ট ভাষায় সবসময় AIDL ফাইল ব্যবহার করুন। প্রসঙ্গের উপর নির্ভর করে, আপনাকে আলাদা আলাদা AIDL ব্যাকএন্ড ব্যবহার করতে হবে।
নিচের সারণীতে, API সারফেসের স্থায়িত্ব বলতে এই API সারফেসের বিপরীতে কোড কম্পাইল করার ক্ষমতাকে বোঝায় যাতে কোডটি system.img libbinder.so বাইনারি থেকে স্বাধীনভাবে ডেলিভার করা যায়।
AIDL-এর নিম্নলিখিত ব্যাকএন্ড আছে:
| ব্যাকএন্ড | ভাষা | API সারফেস | বিল্ড সিস্টেম |
|---|---|---|---|
| জাভা | জাভা | SDK বা SystemApi (স্টেবল*) |
সব |
| NDK | C++ | libbinder_ndk (স্টেবল*) |
aidl_interface |
| সিপিপি (CPP) | C++ | libbinder (অস্থির) |
সব |
| মরচে | মরচে | libbinder_rs (stable*) |
aidl_interface |
- এইসব API সারফেস স্থিতিশীল, তবে পরিষেবা ম্যানেজমেন্টের মতো অনেক API ইন্টার্নাল প্ল্যাটফর্ম ব্যবহারের জন্য সংরক্ষিত এবং অ্যাপের জন্য উপলভ্য নয়। অ্যাপে কীভাবে AIDL ব্যবহার করতে হয় সেই সম্পর্কে আরও জানতে, Android ইন্টারফেস ডেফিনিশন ল্যাঙ্গুয়েজ (AIDL) দেখুন।
- Android 12-এ Rust ব্যাক-এন্ড চালু করা হয়েছে; Android 10 থেকে NDK ব্যাক-এন্ড উপলভ্য আছে।
- Rust crate
libbinder_ndk-এর উপরে তৈরি করা হয়েছে, যা এটিকে স্থিতিশীল ও পোর্টেবল করে তোলে। APEX, সিস্টেমের দিক থেকে স্ট্যান্ডার্ড পদ্ধতিতে বাইন্ডার ক্রেট ব্যবহার করে। Rust অংশটি APEX-এ বান্ডেল করা হয় এবং এর মধ্যে শিপ করা হয়। এই অংশটি সিস্টেম পার্টিশনেlibbinder_ndk.so-এর উপর নির্ভর করে।
সিস্টেম তৈরি করা
ব্যাকএন্ডের উপর নির্ভর করে, স্টাব কোডে AIDL কম্পাইল করার দুটি উপায় রয়েছে। বিল্ড সিস্টেম সম্পর্কে আরও জানতে, Soong মডিউল রেফারেন্স দেখুন।
কোর বিল্ড সিস্টেম
যেকোনও cc_ বা java_ Android.bp module (বা তাদের Android.mk
সমতুল্য)-এ, আপনি সোর্স ফাইল হিসেবে AIDL (.aidl) ফাইল নির্দিষ্ট করতে পারেন। এই
ক্ষেত্রে, AIDL-এর Java বা CPP ব্যাকএন্ড ব্যবহার করা হয় (NDK ব্যাকএন্ড নয়) এবং সংশ্লিষ্ট AIDL ফাইল ব্যবহার করার জন্য
ক্লাসগুলি মডিউলে অটোমেটিক যোগ করা হয়।
আপনি aidl: গ্রুপের অধীনে এইসব মডিউলে local_include_dirs-এর (যা
বিল্ড সিস্টেমকে সেই মডিউলে AIDL ফাইলের রুট পাথ বলে) মতো বিকল্প নির্দিষ্ট করতে পারবেন।
Rust ব্যাকএন্ড শুধুমাত্র Rust-এর সাথে ব্যবহার করা যায়।
rust_ মডিউলগুলি আলাদাভাবে ম্যানেজ করা হয় কারণ AIDL ফাইলগুলিকে সোর্স ফাইল হিসেবে
নির্দিষ্ট করা হয় না। এর পরিবর্তে, aidl_interface মডিউল aidl_interface_name-rust নামে একটি
rustlib তৈরি করে, যা
লিঙ্ক করা যেতে পারে। বিস্তারিত জানতে, Rust AIDL
উদাহরণ দেখুন।
aidl_interface
aidl_interface বিল্ড সিস্টেমের সাথে ব্যবহৃত ধরন অবশ্যই স্ট্রাকচার্ড হতে হবে। পার্সেলেবলকে স্ট্রাকচারড হতে হলে, সেটিতে সরাসরি ফিল্ড থাকতে হবে এবং টার্গেট
ভাষায় সরাসরি ডিফাইন করা টাইপের ডিক্লারেশন হলে চলবে না।
কীভাবে স্ট্রাকচার্ড AIDL, স্টেবল AIDL-এর সাথে ফিট করে, তা জানতে স্ট্রাকচার্ড বনাম স্টেবল AIDL দেখুন।
ধরন
aidl কম্পাইলারকে টাইপের জন্য রেফারেন্স ইমপ্লিমেন্টেশন হিসেবে বিবেচনা করুন।
আপনি কোনও ইন্টারফেস তৈরি করলে, ফলাফল হিসেবে পাওয়া ইন্টারফেস ফাইল দেখতে
aidl --lang=<backend> ... কল করুন। আপনি aidl_interface মডিউল ব্যবহার করলে, out/soong/.intermediates/<path to module>/-এ
আউটপুট দেখতে পাবেন।
| Java বা AIDL-এর ধরন | C++ টাইপ | NDK-এর ধরন | মরচের ধরন |
|---|---|---|---|
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 |
ইন: &strআউট: String |
android.os.Parcelable |
android::Parcelable |
N/A | N/A |
IBinder |
android::IBinder |
ndk::SpAIBinder |
binder::SpIBinder৯ |
T[] |
std::vector<T> |
std::vector<T> |
ইন: &[T]আউট: Vec<T> |
byte[] |
std::vector |
std::vector১ |
ইন: &[u8]আউট: Vec<u8> |
List<T> |
std::vector<T>২ |
std::vector<T>৩ |
ইন: In: &[T]4আউট: Vec<T> |
FileDescriptor |
android::base::unique_fd |
N/A | N/A |
ParcelFileDescriptor |
android::os::ParcelFileDescriptor |
ndk::ScopedFileDescriptor |
binder::parcel::ParcelFileDescriptor৯ |
ইন্টারফেসের ধরন (T) |
android::sp<T> |
std::shared_ptr<T>৭ |
binder::Strong<dyn T>৯ |
পার্সেল করার উপযুক্ত ধরন (T) |
T |
T |
T৯ |
ইউনিয়ন টাইপ (T)৫ |
T |
T |
T |
T[N]৬ |
std::array<T, N> |
std::array<T, N> |
[T; N] |
১. Android 12 বা তার পরবর্তী ভার্সনে, মানানসই হওয়ার কারণে বাইট অ্যারে int8_t-এর পরিবর্তে
uint8_t ব্যবহার করে।
২. C++ ব্যাকএন্ড List<T>-কে সাপোর্ট করে যেখানে T হল
String, IBinder, ParcelFileDescriptor বা পার্সেল করা যায় এমন কিছুর মধ্যে একটি। Android
13 বা এর পরবর্তী যেকোনও ভার্সনে, T যেকোনও ননপ্রিমিটিভ টাইপ (অ্যারে
বাদ দিয়ে ইন্টারফেস টাইপ সহ) হতে পারে। AOSP, T[]-এর মতো অ্যারে টাইপ ব্যবহার করার সাজেশন দেয়, কারণ সেগুলি
সব ব্যাকএন্ডে কাজ করে।
৩. NDK ব্যাকএন্ড List<T>-এ কাজ করে যেখানে T হল String,
ParcelFileDescriptor বা পার্সেলযোগ্য। Android 13
বা এর পরের যেকোনও ভার্সনে, T অ্যারে ছাড়া যেকোনও ননপ্রিমিটিভ টাইপ হতে পারে।
৪. Rust কোডের জন্য টাইপ আলাদাভাবে পাস করা হয়, এটি নির্ভর করে সেগুলি ইনপুট (একটি আর্গুমেন্ট) নাকি আউটপুট (একটি রিটার্ন করা ভ্যালু) তার উপর।
৫. Android 12 ও তার পরবর্তী যেকোনও ভার্সনে ইউনিয়ন টাইপ কাজ করে।
৬. Android 13 বা তার পরবর্তী যেকোনও ভার্সনে, নির্দিষ্ট সাইজের অ্যারে কাজ করে।
নির্দিষ্ট সাইজের অ্যারেতে একাধিক ডাইমেনশন থাকতে পারে (যেমন, int[3][4])। Java ব্যাকএন্ডে, নির্দিষ্ট সাইজের অ্যারে
অ্যারের ধরন হিসেবে দেখানো হয়।
৭. বাইন্ডার SharedRefBase অবজেক্ট ইনস্ট্যানশিয়েট করতে, SharedRefBase::make\<My\>(... args ...) ব্যবহার করুন। এই ফাংশনটি একটি
std::shared_ptr\<T\> অবজেক্ট তৈরি করে, যা ইন্টার্নালিও ম্যানেজ করা হয়, যদি
বাইন্ডারটি অন্য কোনও প্রসেসের মালিকানাধীন হয়। অন্যান্য উপায়ে অবজেক্ট তৈরি করলে
ডবল মালিকানা তৈরি হয়।
৮. এছাড়াও, Java বা AIDL টাইপ byte[] দেখুন।
৯. Rust-এ, Default (IBinder,
ParcelFileDescriptor, ইন্টারফেসের ধরন এবং আনস্ট্রাকচার্ড পার্সেলযোগ্য) ইমপ্লিমেন্ট করে না এমন ধরনগুলি
Option<T>-এর সাথে ম্যাপ করে যখন ডিফল্ট ভ্যালু প্রয়োজন হয় (যেমন, পার্সেলযোগ্য ফিল্ডে,
out প্যারামিটার এবং সেইসব প্রসঙ্গে নির্দিষ্ট-সাইজের অ্যারে), এমনকি
@nullable অ্যানোটেশন না থাকলেও। পার্সেলিংয়ের সময় রানটাইমে এখনও নন-নালিবিলিটি এনফোর্স করা হয়, ভ্যালু None হলে StatusCode::UNEXPECTED_NULL রিটার্ন করা হয়।
মেথড রিটার্ন ভ্যালু, in প্যারামিটার (&T হিসেবে পাস করা হয়েছে) এবং inout প্যারামিটারের
(&mut T হিসেবে পাস করা হয়েছে) ক্ষেত্রে, এগুলি Option<T>-এ র্যাপ করা হয় না, যদি না
@nullable দিয়ে অ্যানোটেশন করা হয়।
দিকনির্দেশ (ইন, আউট ও ইনআউট)
ফাংশনে আর্গুমেন্টের ধরন উল্লেখ করার সময়, আপনি সেগুলি
in, out বা inout হিসেবে উল্লেখ করতে পারেন। IPC কলের জন্য তথ্য কোন দিকে
পাস করা হবে তা এটি নিয়ন্ত্রণ করে।
inআর্গুমেন্ট স্পেসিফায়ার থেকে বোঝা যায় যে ডেটা কলার থেকে কলির কাছে পাঠানো হয়েছে।inস্পেসিফায়ার হল ডিফল্ট দিকনির্দেশ, কিন্তু ডেটার ধরনoutহলে, আপনাকে দিকনির্দেশ নির্দিষ্ট করতে হবে।outআর্গুমেন্ট স্পেসিফায়ার মানে হল, ডেটা কলি থেকে কলারের কাছে পাস করা হয়েছে।inoutআর্গুমেন্ট স্পেসিফায়ার হল এই দুটিরই কম্বিনেশন। তবে, আমরা আর্গুমেন্ট স্পেসিফায়ারinoutব্যবহার না করার সাজেশন দিই। আপনি যদি ভার্সন করা ইন্টারফেস এবং পুরনো কলির সাথেinoutব্যবহার করেন, তাহলে শুধুমাত্র কলারে উপস্থিত অতিরিক্ত ফিল্ডগুলি তাদের ডিফল্ট ভ্যালুতে রিসেট হয়ে যায়। Rust-এর ক্ষেত্রে, সাধারণinoutটাইপ&mut Tপায় এবং তালিকাinoutটাইপ&mut Vec<T>পায়।
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8 এবং UTF-16
CPP ব্যাকএন্ডের সাহায্যে, আপনি বেছে নিতে পারবেন যে স্ট্রিং UTF-8 বা UTF-16 হবে কিনা।
AIDL-এ স্ট্রিং @utf8InCpp String হিসেবে ঘোষণা করুন যাতে সেগুলি অটোমেটিক UTF-8-এ
কনভার্ট করা যায়। NDK ও Rust ব্যাকএন্ড সবসময় UTF-8 স্ট্রিং ব্যবহার করে। utf8InCppঅ্যানোটেশন সম্পর্কে আরও
তথ্য পেতে, utf8InCpp
দেখুন।
নালিবিলিটি
আপনি @nullable-এর মাধ্যমে নাল হতে পারে এমন ধরন অ্যানোটেট করতে পারবেন।
nullable অ্যানোটেশন সম্পর্কে আরও জানতে, নালেবেল দেখুন।
কাস্টম পার্সেলযোগ্য
কাস্টম পার্সেলযোগ্য হল এমন একটি পার্সেলযোগ্য যা টার্গেট ব্যাকএন্ডে ম্যানুয়ালি প্রয়োগ করা হয়। আপনি যদি কোনও বিদ্যমান কাস্টম পার্সেলযোগ্য আইটেমের জন্য অন্যান্য ভাষা যোগ করার চেষ্টা করেন যা পরিবর্তন করা যাবে না, তাহলেই শুধুমাত্র কাস্টম পার্সেলযোগ্য আইটেম ব্যবহার করুন।
এখানে AIDL পার্সেলযোগ্য ঘোষণার একটি উদাহরণ দেওয়া হল:
package my.pack.age;
parcelable Foo;
ডিফল্ট হিসেবে, এটি একটি Java পার্সেলযোগ্য ঘোষণা করে যেখানে my.pack.age.Foo হল Parcelable ইন্টারফেস প্রয়োগকারী একটি Java
ক্লাস।
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-এ 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 দ্বারা
জেনারেট করা হবে না। < এবং == অপারেটরদের CPP এবং NDK ব্যাকএন্ডের জন্য প্রদান করুন
union-এ ব্যবহার করার জন্য কাস্টম পার্সেলযোগ্য।
ডিফল্ট ভ্যালু
স্ট্রাকচার্ড পার্সেলযোগ্য আইটেম, প্রিমিটিভ, String ফিল্ড এবং এই ধরনের অ্যারের জন্য ফিল্ড-পিছু ডিফল্ট ভ্যালু ঘোষণা করতে পারে।
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Java ব্যাকএন্ডে, ডিফল্ট ভ্যালু না থাকলে, প্রিমিটিভ টাইপের জন্য ফিল্ডের ভ্যালু শূন্য
এবং নন-প্রিমিটিভ টাইপের জন্য null হিসেবে শুরু করা হয়।
অন্যান্য ব্যাকএন্ডে, ডিফল্ট ভ্যালু সংজ্ঞায়িত করা না থাকলে
ডিফল্ট ইনিশিয়ালাইজড ভ্যালু দিয়ে ফিল্ড ইনিশিয়ালাইজ করা হয়। যেমন, C++ ব্যাকএন্ডে, String ফিল্ড
খালি স্ট্রিং হিসেবে এবং List<T> ফিল্ড খালি vector<T> হিসেবে
ইনিশিয়ালাইজ করা হয়। @nullableটি ফিল্ড নাল-ভ্যালু ফিল্ড হিসেবে শুরু করা হয়েছে।
ইউনিয়ন
AIDL ইউনিয়ন ট্যাগ করা হয় এবং সব ব্যাকএন্ডে সেগুলির ফিচার একই রকম হয়। এগুলি প্রথম ফিল্ডের ডিফল্ট ভ্যালু অনুযায়ী তৈরি করা হয় এবং এগুলির সাথে ইন্টার্যাক্ট করার জন্য ভাষা-নির্দিষ্ট উপায় রয়েছে:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Java-র উদাহরণ
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setStringField("abc"); // setter
C++ ও NDK উদাহরণ
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Rust-এর উদাহরণ
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 ফাংশন সমস্যার কথা জানালে, ফাংশনটি হয়ত ইনিশিয়ালাইজ বা
আউটপুট প্যারামিটার পরিবর্তন করতে পারবে না। বিশেষত, আনপার্সেলিংয়ের সময় সমস্যা হলে আউটপুট প্যারামিটার পরিবর্তন করা হতে পারে, কারণ
ট্রানজ্যাকশন প্রসেস করার সময় সমস্যা হয়নি। সাধারণত, AIDL ফাংশন থেকে কোনও সমস্যা হলে, সব inout এবং out প্যারামিটার এবং রিটার্ন ভ্যালু (যা কিছু ব্যাকএন্ডে out প্যারামিটারের মতো কাজ করে) অনির্দিষ্ট অবস্থায় আছে বলে বিবেচনা করা উচিত।
কোন ত্রুটি সংক্রান্ত ভ্যালু ব্যবহার করতে হবে
অনেক বিল্ট-ইন ত্রুটি মান যেকোনও AIDL ইন্টারফেসে ব্যবহার করা যেতে পারে, তবে কিছু
বিশেষ উপায়ে ব্যবহার করা হয়। যেমন, EX_UNSUPPORTED_OPERATION ও
EX_ILLEGAL_ARGUMENT ত্রুটিপূর্ণ অবস্থা বর্ণনা করার সময় ব্যবহার করা যায়, কিন্তু
EX_TRANSACTION_FAILED ব্যবহার করা যায় না কারণ
আন্ডারলায়িং ইনফ্রাস্ট্রাকচার এটিকে বিশেষ হিসেবে গণ্য করে। এইসব বিল্ট-ইন ভ্যালু সম্পর্কে আরও
তথ্য পেতে ব্যাকএন্ড-নির্দিষ্ট সংজ্ঞা চেক করুন।
AIDL ইন্টারফেসে যদি অতিরিক্ত ত্রুটি সংক্রান্ত ভ্যালুর প্রয়োজন হয় যা বিল্ট-ইন ত্রুটির ধরনের মধ্যে
কভার করা নেই, তাহলে এটি বিশেষ পরিষেবা-নির্দিষ্ট বিল্ট-ইন ত্রুটি ব্যবহার করতে পারে যা
ব্যবহারকারীর দ্বারা সংজ্ঞায়িত পরিষেবা-নির্দিষ্ট ত্রুটি সংক্রান্ত ভ্যালু অন্তর্ভুক্ত করার অনুমতি দেয়। এইসব পরিষেবা-নির্দিষ্ট সমস্যা সাধারণত AIDL ইন্টারফেসে
const int বা int-ব্যাকড enum হিসেবে সংজ্ঞায়িত করা হয় এবং বাইন্ডার
দ্বারা পার্স করা হয় না।
Java-তে, android.os.RemoteException-এর মতো ব্যতিক্রমের সাথে ত্রুটি ম্যাপ করা হয়। পরিষেবা-নির্দিষ্ট
ব্যতিক্রমের জন্য, Java ব্যবহারকারীর-নির্ধারিত ত্রুটির সাথে android.os.ServiceSpecificException
ব্যবহার করে।
Android-এ নেটিভ কোড ব্যতিক্রম ব্যবহার করে না। CPP ব্যাকএন্ড
android::binder::Status ব্যবহার করে। NDK ব্যাকএন্ড ndk::ScopedAStatus ব্যবহার করে। AIDL-এর মাধ্যমে জেনারেট করা প্রতিটি
মেথড এগুলির মধ্যে একটি রিটার্ন করে, যা মেথডের
স্ট্যাটাসকে উপস্থাপন করে। Rust ব্যাকএন্ড NDK-এর মতোই একই ব্যতিক্রমী কোড ভ্যালু ব্যবহার করে, কিন্তু
ব্যবহারকারীকে ডেলিভার করার আগে সেগুলিকে নেটিভ Rust সংক্রান্ত সমস্যায় (StatusCode, ExceptionCode) কনভার্ট করে
নেয়। পরিষেবা-নির্দিষ্ট সমস্যার ক্ষেত্রে, ফেরত আসা
Status বা ScopedAStatus, EX_SERVICE_SPECIFIC-এর সাথে
ব্যবহারকারীর সংজ্ঞায়িত সমস্যা ব্যবহার করে।
বিল্ট-ইন সমস্যার ধরনগুলি নিম্নলিখিত ফাইলগুলিতে পাওয়া যাবে:
| ব্যাকএন্ড | সংজ্ঞা |
|---|---|
| জাভা | android/os/Parcel.java |
| সিপিপি (CPP) | binder/Status.h |
| NDK | android/binder_status.h |
| মরচে | android/binder_status.h |
বিভিন্ন ব্যাকএন্ড ব্যবহার করুন
এইসব নির্দেশাবলী Android প্ল্যাটফর্ম কোডের জন্য নির্দিষ্ট। এইসব উদাহরণে
নির্ধারিত ধরন, my.package.IFoo ব্যবহার করা হয়েছে। Rust
ব্যাকএন্ড কীভাবে ব্যবহার করতে হয় সেই সংক্রান্ত নির্দেশাবলীর জন্য, Android Rust
প্যাটার্নে
Rust AIDL
উদাহরণ দেখুন।
ইমপোর্ট করার ধরন
নির্দিষ্ট করা ধরনটি ইন্টারফেস, পার্সেল করা যায় এমন বা ইউনিয়ন যাই হোক না কেন, আপনি এটি Java-তে ইম্পোর্ট করতে পারবেন:
import my.package.IFoo;
অথবা CPP ব্যাকএন্ডে:
#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-এ (IFoo হল ফাইলের রুট টাইপ) সংজ্ঞায়িত নেস্টেড
টাইপ Bar ইমপোর্ট করার সময়, আপনাকে CPP ব্যাকএন্ডের জন্য <my/package/IFoo.h> (অথবা NDK ব্যাকএন্ডের জন্য
<aidl/my/package/IFoo.h>) যোগ করতে হবে।
ইন্টারফেস প্রয়োগ করা
ইন্টারফেস প্রয়োগ করতে, আপনাকে অবশ্যই নেটিভ স্টাব ক্লাস থেকে ইনহেরিট করতে হবে। কোনও ইন্টারফেসের
ইমপ্লিমেন্টেশনকে প্রায়ই পরিষেবা বলা হয়, যখন এটি পরিষেবা ম্যানেজার বা android.app.ActivityManager-এর সাথে রেজিস্টার করা হয় এবং এটিকে
কলব্যাক বলা হয়, যখন এটি কোনও পরিষেবার ক্লায়েন্টের দ্বারা রেজিস্টার করা হয়। তবে, ইন্টারফেস ইমপ্লিমেন্টেশন বর্ণনা করার জন্য বিভিন্ন
নাম ব্যবহার করা হয়, যা সঠিক
ব্যবহারের উপর নির্ভর করে। স্টাব ক্লাস বাইন্ডার ড্রাইভার থেকে কমান্ড পড়ে এবং আপনার প্রয়োগ করা
মেথড এক্সিকিউট করে। ধরুন, আপনার কাছে এই ধরনের একটি AIDL ফাইল আছে:
package my.package;
interface IFoo {
int doFoo();
}
Java-তে, আপনাকে অবশ্যই জেনারেট করা Stub ক্লাস থেকে এক্সটেন্ড করতে হবে:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
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;
}
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
প্রসেসের মাধ্যমে রেজিস্টার করা হয়। নিম্নলিখিত API ছাড়াও, কিছু API পরিষেবা
চেক করে (অর্থাৎ, পরিষেবা উপলভ্য না থাকলে সেগুলি সঙ্গে সঙ্গে রিটার্ন করে)।
সঠিক বিবরণের জন্য সংশ্লিষ্ট servicemanager ইন্টারফেস চেক করুন। আপনি
শুধুমাত্র Android প্ল্যাটফর্মের জন্য কম্পাইল করার সময় এই অপারেশনগুলি করতে পারবেন।
জাভাতে:
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")));
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 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ব্যবহার করুন। - CPP ব্যাকএন্ডে,
android::IBinder::linkToDeathব্যবহার করুন। - NDK ব্যাকএন্ডে,
AIBinder_linkToDeathব্যবহার করুন। আপনার ডেথ রেসিপিয়েন্ট কুকির লাইফটাইম কন্ট্রোল করতে সবসময়AIBinder_DeathRecipient_setOnUnlinkedব্যবহার করুন। - Rust ব্যাকএন্ডে, একটি
DeathRecipientঅবজেক্ট তৈরি করুন, তারপরmy_binder.link_to_death(&mut my_death_recipient)কল করুন। মনে রাখবেন, যেহেতুDeathRecipientকলব্যাকটি নিজের কাছে রাখে, তাই আপনি যতক্ষণ বিজ্ঞপ্তি পেতে চান ততক্ষণ আপনাকে সেই অবজেক্টটি লাইভ রাখতে হবে।
কলার সংক্রান্ত তথ্য
কার্নেল বাইন্ডার কল রিসিভ করার সময়, কলারের তথ্য বিভিন্ন API-তে উপলভ্য থাকে। প্রসেস আইডি (PID) বলতে সেই প্রসেসের Linux প্রসেস আইডিকে বোঝায় যা ট্রানজ্যাকশন পাঠাচ্ছে। ব্যবহারকারীর আইডি (UI) বলতে Linux ব্যবহারকারীর আইডিকে বোঝানো হয়। একমুখী কল রিসিভ করার সময়, কলিং PID হল ০। বাইন্ডার ট্রানজ্যাকশন কনটেক্সটের বাইরে, এই ফাংশনগুলি বর্তমান প্রসেসের PID এবং UID রিটার্ন করে।
Java ব্যাকএন্ডে:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
CPP ব্যাকএন্ডে:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
NDK ব্যাকএন্ডে:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Rust ব্যাকএন্ডে, ইন্টারফেস প্রয়োগ করার সময়, নিম্নলিখিতগুলি নির্দিষ্ট করুন (এটি ডিফল্ট হিসেবে সেট করার অনুমতি দেওয়ার পরিবর্তে):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
পরিষেবার জন্য বাগ রিপোর্ট ও ডিবাগিং API
বাগ রিপোর্ট রান করলে (যেমন, adb bugreport-এর মাধ্যমে), বিভিন্ন সমস্যা ডিবাগ করার জন্য
সারা সিস্টেম থেকে তথ্য সংগ্রহ করে।
AIDL পরিষেবার ক্ষেত্রে, বাগ রিপোর্ট, পরিষেবা ম্যানেজারের সাথে রেজিস্টার করা dumpsys সমস্ত পরিষেবার
বাইনারি ব্যবহার করে, যাতে তাদের তথ্য
বাগ রিপোর্টে ডাম্প করা যায়। এছাড়াও, dumpsys SERVICE [ARGS] সহ কোনও পরিষেবা থেকে তথ্য পেতে, আপনি কমান্ড লাইনে dumpsys ব্যবহার করতে পারেন।
C++ এবং Java ব্যাকএন্ডে, আপনি
addService-এ অতিরিক্ত আর্গুমেন্ট ব্যবহার করে পরিষেবাগুলি ডাম্প করার ক্রম নিয়ন্ত্রণ করতে
পারেন। এছাড়াও, ডিবাগ করার সময় কোনও
পরিষেবার PID পেতে আপনি dumpsys --pid SERVICE ব্যবহার করতে পারবেন।
আপনার পরিষেবায় কাস্টম আউটপুট যোগ করতে, AIDL ফাইলে সংজ্ঞায়িত করা অন্য কোনও IPC পদ্ধতি
ইমপ্লিমেন্ট করার মতো আপনার সার্ভার অবজেক্টে dump
পদ্ধতি ওভাররাইড করুন। এটি করার সময়, অ্যাপ
অনুমতি android.permission.DUMP-তে ডাম্প করা সীমিত করুন অথবা নির্দিষ্ট UID-তে ডাম্প করা সীমিত করুন।
Java ব্যাকএন্ডে:
@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;
Rust ব্যাকএন্ডে, ইন্টারফেস প্রয়োগ করার সময়, নিম্নলিখিতগুলি উল্লেখ করুন (ডিফল্ট হিসেবে এটি করার অনুমতি দেওয়ার পরিবর্তে):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
দুর্বল পয়েন্টার ব্যবহার করা
আপনি বাইন্ডার অবজেক্টের জন্য একটি দুর্বল রেফারেন্স হোল্ড করতে পারেন।
Java-তে WeakReference কাজ করলেও, নেটিভ লেভেলে উইক বাইন্ডার রেফারেন্স
কাজ করে না।
CPP ব্যাকএন্ডে, দুর্বল টাইপ হল 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();
CPP ব্যাকএন্ডে:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
NDK এবং Rust ব্যাকএন্ডে এই ক্ষমতা কাজ করে না।
স্ট্যাটিক্যালি ইন্টারফেস ডেসক্রিপ্টর পাওয়া
কখনও কখনও (যেমন @VintfStability পরিষেবা রেজিস্টার করার সময়), আপনাকে
ইন্টারফেস ডেসক্রিপ্টর স্ট্যাটিক্যালি কী তা জানতে হবে। Java-তে, আপনি এই ধরনের কোড যোগ করে
ডেসক্রিপ্টর পেতে পারেন:
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
Rust ব্যাকএন্ডে:
aidl::my::package::BnFoo::get_descriptor()
Enum রেঞ্জ
নেটিভ ব্যাকএন্ডে, আপনি সম্ভাব্য মানগুলির উপর পুনরাবৃত্তি করতে পারেন যা একটি enum নিতে পারে। কোডের সাইজ সংক্রান্ত কারণে, এটি Java-তে কাজ করে না।
AIDL-এ সংজ্ঞায়িত MyEnum এনামের জন্য, ইটারেশন এইভাবে প্রদান করা হয়।
CPP ব্যাকএন্ডে:
::android::enum_range<MyEnum>()
NDK ব্যাকএন্ডে:
::ndk::enum_range<MyEnum>()
Rust ব্যাকএন্ডে:
MyEnum::enum_values()
থ্রেড ম্যানেজমেন্ট
কোনও প্রসেসে libbinder-এর প্রতিটি ইনস্ট্যান্স একটি থ্রেডপুল বজায় রাখে। বেশিরভাগ
ব্যবহারের ক্ষেত্রে, এটি ঠিক একটি থ্রেডপুল হতে হবে, যা সমস্ত ব্যাকএন্ড জুড়ে শেয়ার করা হবে।
একমাত্র ব্যতিক্রম হল, যদি ভেন্ডর কোড /dev/vndbinder-এর সাথে কথা বলার জন্য libbinder-এর আরেকটি কপি লোড করে। এটি একটি আলাদা বাইন্ডার নোডে আছে, তাই
থ্রেডপুল শেয়ার করা হয় না।
Java ব্যাকএন্ডের জন্য, থ্রেডপুল শুধুমাত্র সাইজে বাড়তে পারে (কারণ এটি আগে থেকেই শুরু হয়ে গেছে):
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();
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 সংজ্ঞায় রিজার্ভ করা নাম ব্যবহার না করার সাজেশন দিই যেখানে সম্ভব, যাতে আন-এরগোনমিক বাইন্ডিং বা সরাসরি কম্পাইলেশন ব্যর্থতা এড়ানো যায়।
আপনার AIDL ডেফিনিশনে আগে থেকেই রিজার্ভ করা নাম থাকলে, আপনি নিরাপদে প্রোটোকল কম্প্যাটিবল থাকা অবস্থাতেই ফিল্ডের নাম পরিবর্তন করতে পারবেন। চালিয়ে যেতে আপনাকে হয়ত নিজের কোড আপডেট করতে হবে, তবে আগে থেকেই তৈরি করা যেকোনও প্রোগ্রাম ইন্টারঅপারেট করা চালিয়ে যাবে।
যেসব নাম এড়িয়ে চলবেন: