এআইডিএল ব্যাকএন্ড

একটি AIDL ব্যাকএন্ড হল স্টাব কোড জেনারেশনের লক্ষ্য। সর্বদা একটি নির্দিষ্ট ভাষায় একটি নির্দিষ্ট রানটাইম সহ AIDL ফাইল ব্যবহার করুন। প্রেক্ষাপটের উপর নির্ভর করে, আপনার বিভিন্ন AIDL ব্যাকএন্ড ব্যবহার করা উচিত।

নিম্নলিখিত সারণীতে, API পৃষ্ঠের স্থিতিশীলতা বলতে এই API পৃষ্ঠের বিরুদ্ধে কোড কম্পাইল করার ক্ষমতা বোঝায় যাতে কোডটি system.img libbinder.so বাইনারি থেকে স্বাধীনভাবে সরবরাহ করা যায়।

AIDL-এর নিম্নলিখিত ব্যাকএন্ড রয়েছে:

ব্যাকএন্ড ভাষা এপিআই সারফেস সিস্টেম তৈরি করুন
জাভা জাভা SDK অথবা SystemApi (স্থিতিশীল*) সব
এনডিকে সি++ libbinder_ndk (স্থিতিশীল*) aidl_interface
সিপিপি সি++ libbinder (অস্থির) সব
মরিচা মরিচা libbinder_rs (স্থিতিশীল*) aidl_interface
  • এই API সারফেসগুলি স্থিতিশীল, কিন্তু অনেক API, যেমন পরিষেবা পরিচালনার জন্য, অভ্যন্তরীণ প্ল্যাটফর্ম ব্যবহারের জন্য সংরক্ষিত এবং অ্যাপগুলিতে উপলব্ধ নয়। অ্যাপগুলিতে AIDL কীভাবে ব্যবহার করবেন সে সম্পর্কে আরও তথ্যের জন্য, Android ইন্টারফেস ডেফিনিশন ল্যাঙ্গুয়েজ (AIDL) দেখুন।
  • রাস্ট ব্যাকএন্ডটি অ্যান্ড্রয়েড ১২-তে চালু করা হয়েছিল; এনডিকে ব্যাকএন্ডটি অ্যান্ড্রয়েড ১০-এ উপলব্ধ ছিল।
  • রাস্ট ক্রেটটি libbinder_ndk এর উপরে তৈরি করা হয়, যা এটিকে স্থিতিশীল এবং বহনযোগ্য করে তোলে। APEX গুলি সিস্টেমের দিকে স্ট্যান্ডার্ড পদ্ধতিতে বাইন্ডার ক্রেট ব্যবহার করে। রাস্ট অংশটি একটি APEX এর মধ্যে বান্ডিল করা হয় এবং এর ভিতরে পাঠানো হয়। এই অংশটি সিস্টেম পার্টিশনের libbinder_ndk.so এর উপর নির্ভর করে।

সিস্টেম তৈরি করুন

ব্যাকএন্ডের উপর নির্ভর করে, AIDL কে স্টাব কোডে কম্পাইল করার দুটি উপায় রয়েছে। বিল্ড সিস্টেম সম্পর্কে আরও বিস্তারিত জানার জন্য, Soong Modules Reference দেখুন।

কোর বিল্ড সিস্টেম

যেকোনো cc_ অথবা java_ Android.bp module (অথবা তাদের Android.mk সমতুল্য) আপনি AIDL ( .aidl ) ফাইলগুলিকে সোর্স ফাইল হিসেবে নির্দিষ্ট করতে পারেন। এই ক্ষেত্রে, AIDL এর জাভা বা CPP ব্যাকএন্ড ব্যবহার করা হয় (NDK ব্যাকএন্ড নয়), এবং সংশ্লিষ্ট AIDL ফাইলগুলি ব্যবহার করার জন্য ক্লাসগুলি স্বয়ংক্রিয়ভাবে মডিউলে যুক্ত করা হয়। আপনি aidl: গ্রুপের অধীনে এই মডিউলগুলিতে local_include_dirs (যা বিল্ড সিস্টেমকে সেই মডিউলে AIDL ফাইলগুলির রুট পাথ বলে) এর মতো বিকল্পগুলি নির্দিষ্ট করতে পারেন।

Rust ব্যাকএন্ড শুধুমাত্র Rust এর সাথে ব্যবহারের জন্য। rust_ মডিউলগুলি ভিন্নভাবে পরিচালনা করা হয় কারণ AIDL ফাইলগুলিকে সোর্স ফাইল হিসাবে নির্দিষ্ট করা হয় না। পরিবর্তে, aidl_interface মডিউলটি aidl_interface_name -rust নামে একটি rustlib তৈরি করে, যার সাথে লিঙ্ক করা যেতে পারে। বিস্তারিত জানার জন্য, Rust AIDL উদাহরণটি দেখুন।

এইডল_ইন্টারফেস

aidl_interface বিল্ড সিস্টেমের সাথে ব্যবহৃত প্রকারগুলি অবশ্যই কাঠামোগত হতে হবে। কাঠামোগত হওয়ার জন্য, parceables-এ সরাসরি ক্ষেত্র থাকতে হবে এবং লক্ষ্য ভাষায় সরাসরি সংজ্ঞায়িত ধরণের ঘোষণা হতে হবে না। স্থিতিশীল AIDL-এর সাথে কাঠামোগত AIDL কীভাবে খাপ খায় তা জানতে, Structured বনাম স্থিতিশীল AIDL দেখুন।

প্রকারভেদ

aidl কম্পাইলারকে টাইপের জন্য একটি রেফারেন্স বাস্তবায়ন হিসেবে বিবেচনা করুন। যখন আপনি একটি ইন্টারফেস তৈরি করেন, তখন ফলাফল ইন্টারফেস ফাইলটি দেখতে aidl --lang=<backend> ... ব্যবহার করুন। যখন আপনি aidl_interface মডিউল ব্যবহার করেন, তখন আপনি out/soong/.intermediates/ <path to module> / আউটপুট দেখতে পারেন।

জাভা বা AIDL টাইপ সি++ টাইপ এনডিকে টাইপ মরিচা ধরণ
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 নিষিদ্ধ নিষিদ্ধ
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> 2 std::vector<T> 3 ইন: In: &[T]
আউট: Vec<T>
FileDescriptor android::base::unique_fd নিষিদ্ধ নিষিদ্ধ
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
ইন্টারফেসের ধরণ ( T ) android::sp<T> std::shared_ptr<T> binder::Strong
পার্সেলেবল টাইপ ( T ) T T T
ইউনিয়ন টাইপ ( T ) 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[] এর মতো অ্যারে প্রকার ব্যবহার করার পরামর্শ দেয়, কারণ এগুলি সমস্ত ব্যাকএন্ডে কাজ করে।

৩. NDK ব্যাকএন্ড List<T> সমর্থন করে যেখানে T হল String , ParcelFileDescriptor অথবা parcelable এর মধ্যে একটি। Android 13 বা উচ্চতর সংস্করণে, T অ্যারে ছাড়া যেকোনো অ-প্রাথমিক প্রকার হতে পারে।

৪. রাস্ট কোডের জন্য টাইপগুলি ভিন্নভাবে পাস করা হয় তা নির্ভর করে যে সেগুলি ইনপুট (একটি আর্গুমেন্ট), নাকি আউটপুট (একটি ফেরত মান)।

৫. ইউনিয়ন প্রকারগুলি অ্যান্ড্রয়েড ১২ এবং উচ্চতর সংস্করণে সমর্থিত।

৬. অ্যান্ড্রয়েড ১৩ বা তার পরবর্তী সংস্করণে, স্থির-আকারের অ্যারে সমর্থিত। স্থির-আকারের অ্যারেগুলির একাধিক মাত্রা থাকতে পারে (উদাহরণস্বরূপ, int[3][4] )। জাভা ব্যাকএন্ডে, স্থির-আকারের অ্যারেগুলিকে অ্যারের ধরণ হিসাবে উপস্থাপন করা হয়।

৭. একটি বাইন্ডার SharedRefBase অবজেক্টকে ইনস্ট্যান্টিয়েট করতে, SharedRefBase::make\<My\>(... args ...) ব্যবহার করুন। এই ফাংশনটি একটি std::shared_ptr\<T\> অবজেক্ট তৈরি করে, যা অভ্যন্তরীণভাবেও পরিচালিত হয়, যদি বাইন্ডারটি অন্য কোনও প্রক্রিয়ার মালিকানাধীন হয়। অন্যভাবে অবজেক্ট তৈরি করলে দ্বিগুণ মালিকানা তৈরি হয়।

৮. আরও দেখুন জাভা অথবা AIDL টাইপ byte[]

দিকনির্দেশনা (ভিতরে, বাইরে, এবং ভিতরে)

ফাংশনগুলিতে আর্গুমেন্টের ধরণ নির্দিষ্ট করার সময়, আপনি সেগুলিকে 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 ব্যবহার করে আপনি null হতে পারে এমন প্রকারগুলি টীকা করতে পারেন। nullable টীকা সম্পর্কে আরও তথ্যের জন্য, nullable দেখুন।

কাস্টম পার্সেবল

একটি কাস্টম পার্সেলেবল হল একটি পার্সেলেবল যা একটি টার্গেট ব্যাকএন্ডে ম্যানুয়ালি বাস্তবায়িত হয়। কাস্টম পার্সেলেবল ব্যবহার করুন শুধুমাত্র তখনই যখন আপনি একটি বিদ্যমান কাস্টম পার্সেলেবলের জন্য অন্যান্য ভাষার সমর্থন যোগ করার চেষ্টা করছেন যা পরিবর্তন করা যাবে না।

এখানে একটি AIDL পার্সেলযোগ্য ঘোষণার একটি উদাহরণ দেওয়া হল:

    package my.pack.age;
    parcelable Foo;

ডিফল্টরূপে, এটি একটি জাভা parcelable ঘোষণা করে যেখানে 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);
    };

অ্যান্ড্রয়েড ১৫-এ, AIDL-এ একটি কাস্টম রাস্ট পার্সেলযোগ্য ঘোষণার জন্য, 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);

তারপর আপনি এই parceable কে AIDL ফাইলে টাইপ হিসেবে ব্যবহার করতে পারবেন, কিন্তু এটি AIDL দ্বারা তৈরি হবে না। CPP এবং NDK ব্যাকএন্ড কাস্টম parceables কে 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::makeFoo::s>tringField("<abc>")); // maketag(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::String>Field(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 -backed enum হিসাবে সংজ্ঞায়িত করা হয় এবং বাইন্ডার দ্বারা পার্স করা হয় না।

জাভাতে, ত্রুটিগুলি ব্যতিক্রমগুলিতে ম্যাপ করে, যেমন android.os.RemoteException । পরিষেবা-নির্দিষ্ট ব্যতিক্রমগুলির জন্য, জাভা ব্যবহারকারী-সংজ্ঞায়িত ত্রুটির সাথে android.os.ServiceSpecificException ব্যবহার করে।

অ্যান্ড্রয়েডের নেটিভ কোডে ব্যতিক্রম ব্যবহার করা হয় না। CPP ব্যাকএন্ডে android::binder::Status ব্যবহার করা হয়। NDK ব্যাকএন্ডে ndk::ScopedAStatus ব্যবহার করা হয়। AIDL দ্বারা তৈরি প্রতিটি পদ্ধতি এইগুলির মধ্যে একটি ফেরত দেয়, যা পদ্ধতির অবস্থা উপস্থাপন করে। Rust ব্যাকএন্ড NDK-এর মতো একই ব্যতিক্রম কোড মান ব্যবহার করে, কিন্তু ব্যবহারকারীর কাছে পৌঁছে দেওয়ার আগে সেগুলিকে নেটিভ Rust ত্রুটিতে ( 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>

অথবা NDK ব্যাকএন্ডে (অতিরিক্ত aidl নেমস্পেসটি লক্ষ্য করুন):

#include <aidl/my/package/IFoo.h>

অথবা রাস্ট ব্যাকএন্ডে:

use my_package::aidl::my::package::IFoo;

যদিও আপনি জাভাতে একটি নেস্টেড টাইপ আমদানি করতে পারেন, 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();
    }

জাভাতে, আপনাকে জেনারেটেড Stub ক্লাস থেকে প্রসারিত করতে হবে:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

সিপিপি ব্যাকএন্ডে:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

NDK ব্যাকএন্ডে (অতিরিক্ত aidl নেমস্পেসটি লক্ষ্য করুন):

    #include <aidl/my/package/BnFoo.h>
    class MyFoo : public aidl::my::package::BnFoo {
        ndk::ScopedAStatus doFoo(int32_t* out) override;
    }

রাস্ট ব্যাকএন্ডে:

    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"));

সিপিপি ব্যাকএন্ডে:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = ch<eckS>erviceIFoo(String16("s&ervice-name"), myService);
    // waiting until service comes up (new in Android 11)
    myServ<ice >= waitForServiceIFoo(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    mySe<rvic>e = waitForDeclaredServiceIFoo(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 0 হয়। বাইন্ডার লেনদেন প্রসঙ্গের বাইরে, এই ফাংশনগুলি বর্তমান প্রক্রিয়ার PID এবং UID ফেরত দেয়।

জাভা ব্যাকএন্ডে:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

সিপিপি ব্যাকএন্ডে:

    ... = 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 এ অতিরিক্ত আর্গুমেন্ট ব্যবহার করে পরিষেবাগুলি ডাম্প করার ক্রম নিয়ন্ত্রণ করতে পারেন। ডিবাগ করার সময় কোনও পরিষেবার PID পেতে আপনি dumpsys --pid SERVICE ব্যবহার করতে পারেন।

আপনার পরিষেবাতে কাস্টম আউটপুট যোগ করতে, আপনার সার্ভার অবজেক্টে dump পদ্ধতিটি ওভাররাইড করুন যেমন আপনি AIDL ফাইলে সংজ্ঞায়িত অন্য কোনও IPC পদ্ধতি প্রয়োগ করছেন। এটি করার সময়, ডাম্পিংকে অ্যাপ অনুমতি android.permission.DUMP তে সীমাবদ্ধ করুন অথবা নির্দিষ্ট UID-তে ডাম্পিং সীমাবদ্ধ করুন।

জাভা ব্যাকএন্ডে:

    @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;

রাস্ট ব্যাকএন্ডে, ইন্টারফেসটি বাস্তবায়নের সময়, নিম্নলিখিতগুলি নির্দিষ্ট করুন (এটিকে ডিফল্ট অবস্থায় রাখার পরিবর্তে):

    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();

সিপিপি ব্যাকএন্ডে:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

NDK এবং Rust ব্যাকএন্ড এই ক্ষমতা সমর্থন করে না।

স্ট্যাটিক্যালি ইন্টারফেস বর্ণনাকারী পান

কখনও কখনও (যেমন @VintfStability পরিষেবা নিবন্ধন করার সময়), আপনাকে ইন্টারফেস বর্ণনাকারী স্ট্যাটিক্যালি কী তা জানতে হবে। জাভাতে, আপনি কোড যোগ করে বর্ণনাকারী পেতে পারেন যেমন:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

সিপিপি ব্যাকএন্ডে:

    #include <my/package/BnFoo.h>
    ... my::package::BnFoo::descriptor

NDK ব্যাকএন্ডে (অতিরিক্ত aidl নেমস্পেসটি লক্ষ্য করুন):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

রাস্ট ব্যাকএন্ডে:

    aidl::my::package::BnFoo::get_descriptor()

এনাম পরিসর

নেটিভ ব্যাকএন্ডে, আপনি একটি enum কতগুলি সম্ভাব্য মান নিতে পারে তার উপর পুনরাবৃত্তি করতে পারেন। কোডের আকার বিবেচনার কারণে, এটি জাভাতে সমর্থিত নয়।

AIDL-এ সংজ্ঞায়িত একটি enum MyEnum জন্য, পুনরাবৃত্তি নিম্নরূপ প্রদান করা হয়েছে।

সিপিপি ব্যাকএন্ডে:

    ::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();

async Rust ব্যাকএন্ডের সাথে, আপনার দুটি থ্রেডপুল প্রয়োজন: বাইন্ডার এবং টোকিও। এর অর্থ হল async Rust ব্যবহারকারী অ্যাপগুলির বিশেষ বিবেচনার প্রয়োজন, বিশেষ করে যখন join_thread_pool ব্যবহারের কথা আসে। এই বিষয়ে আরও তথ্যের জন্য পরিষেবা নিবন্ধন করার বিভাগটি দেখুন।

সংরক্ষিত নাম

C++, Java, এবং Rust কিছু নাম কীওয়ার্ড হিসেবে অথবা ভাষা-নির্দিষ্ট ব্যবহারের জন্য সংরক্ষণ করে। যদিও AIDL ভাষার নিয়মের উপর ভিত্তি করে বিধিনিষেধ আরোপ করে না, তবে সংরক্ষিত নামের সাথে মেলে এমন ফিল্ড বা টাইপ নাম ব্যবহার করলে C++ বা Java-এর জন্য সংকলন ব্যর্থ হতে পারে। Rust-এর জন্য, raw আইডেন্টিফায়ার সিনট্যাক্স ব্যবহার করে ফিল্ড বা টাইপের নাম পরিবর্তন করা হয়, যা r# প্রিফিক্স ব্যবহার করে অ্যাক্সেসযোগ্য।

অ-অর্গোনমিক বাইন্ডিং বা সম্পূর্ণ সংকলন ব্যর্থতা এড়াতে, আমরা আপনার AIDL সংজ্ঞাগুলিতে সংরক্ষিত নাম ব্যবহার করা এড়িয়ে চলার পরামর্শ দিচ্ছি।

যদি আপনার AIDL সংজ্ঞায় ইতিমধ্যেই সংরক্ষিত নাম থাকে, তাহলে প্রোটোকলের সাথে সামঞ্জস্যপূর্ণ থাকাকালীন আপনি নিরাপদে ক্ষেত্রগুলির নাম পরিবর্তন করতে পারেন। তৈরি করা চালিয়ে যাওয়ার জন্য আপনার কোড আপডেট করার প্রয়োজন হতে পারে, তবে ইতিমধ্যেই তৈরি করা যেকোনো প্রোগ্রাম আন্তঃকার্যকর হতে থাকে।

এড়িয়ে চলার নাম: