زیرینه‌های AIDL

زیرینه AIDL هدفی برای تولید کد چسبان است. همیشه از فایل‌های AIDL به زبان خاص با زمان اجرای مشخص استفاده کنید. بسته به بافت، باید از زیرینه‌های AIDL مختلف استفاده کنید.

در جدول زیر، پایداری سطح API به توانایی کامپایل کردن کد دربرابر این سطح API به‌گونه‌ای که کد بتواند به‌طور مستقل از باینری system.img libbinder.so ارائه شود اشاره دارد.

‫AIDL پس‌زمینه‌های زیر را دارد:

زیرینه زبان سطح میانای برنامه‌سازی کاربردی سیستم‌های ساخت
جاوا جاوا کیت توسعه نرم‌افزار یا SystemApi (پایدار*) همه
NDK C++‎ ‫libbinder_ndk (پایدار*) aidl_interface
هزینه تماس تلفنی CPP C++‎ ‫libbinder (ناپایدار) همه
زنگار زنگار libbinder_rs (پایدار*) aidl_interface
  • این سطوح API پایدار هستند، اما بسیاری از APIها، مانند APIهای مربوط به مدیریت سرویس، برای استفاده داخلی پلاتفرم رزرو شده‌اند و برای برنامه‌ها دردسترس نیستند. برای اطلاعات بیشتر درباره نحوه استفاده از AIDL در برنامه‌ها، زبان تعریف میانای Android (AIDL) را ببینید.
  • قسمت انتهایی Rust در Android 12 معرفی شد؛ قسمت انتهایی NDK از Android 10 دردسترس بوده است.
  • جعبه Rust روی libbinder_ndk ساخته شده است که به آن اجازه می‌دهد پایدار و قابل‌حمل باشد. ‫APEXها از جعبه چسباننده به روش استاندارد در سمت سیستم استفاده می‌کنند. بخش Rust در APEX دسته‌بندی و در آن ارسال می‌شود. این بخش به libbinder_ndk.so در پارتیشن سیستم بستگی دارد.

ساختن سیستم‌ها

بسته به زیرینه، دو روش برای ترجمه AIDL به کد میانجی وجود دارد. برای جزئیات بیشتر درباره سیستم‌های ساخت، به مرجع ماژول‌های Soong مراجعه کنید.

سیستم ساخت اصلی

در هر cc_ یا java_ Android.bp module (یا در Android.mk معادل‌های آن‌ها)، می‌توانید فایل‌های AIDL (.aidl) را به‌عنوان فایل‌های منبع مشخص کنید. در این مورد، از زیرینه‌های Java یا CPP مربوط به AIDL استفاده می‌شود (نه زیرینه NDK)، و کلاس‌های استفاده از فایل‌های AIDL مربوطه به‌طور خودکار به واحد اضافه می‌شوند. می‌توانید گزینه‌هایی مثل local_include_dirs (که به سیستم ساخت مسیر ریشه فایل‌های AIDL در آن واحد را می‌گوید) را در این واحدها در گروه aidl: مشخص کنید.

زیرینه Rust فقط برای استفاده با Rust است. با واحدهای rust_ به‌طور متفاوتی رفتار می‌شود، زیرا فایل‌های AIDL به‌عنوان فایل‌های منبع مشخص نشده‌اند. درعوض، واحد aidl_interface یک rustlib به‌نام aidl_interface_name-rust تولید می‌کند که می‌توان به آن پیوند داد. برای جزئیات، نمونه Rust AIDL را ببینید.

aidl_interface

انواع مورداستفاده با سیستم ساخت aidl_interface باید ساختاریافته باشند. برای ساختاردهی شدن، parcelable باید حاوی فیلدهای مستقیم باشد و نباید اعلامیه‌های انواع تعریف‌شده مستقیم در زبان‌های هدف باشد. برای اینکه ببینید AIDL ساختاریافته چگونه با 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 موجود نیست موجود نیست
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]۴
خروج: 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<dyn T>‫۹
نوع Parcelable‏ (T) T T T۹
نوع اتحادیه (T)۵ T T T
T[N]‫۶ std::array<T, N> std::array<T, N> [T; N]

۱. در Android 12 یا بالاتر، آرایه‌های بایت به‌دلایل سازگاری از uint8_t به‌جای int8_t استفاده می‌کنند.

‫۲. بخش پشتی C++‎ از List<T> پشتیبانی می‌کند که در آن T یکی از String،‏ IBinder،‏ ParcelFileDescriptor یا parcelable است. در Android 13 یا بالاتر، T می‌تواند هر نوع غیرابتدایی (ازجمله انواع رابط) به‌جز آرایه باشد. ‫AOSP توصیه می‌کند از انواع آرایه مثل T[] استفاده کنید، زیرا در همه زیرینه‌ها کار می‌کنند.

‫۳. پس‌زمینه NDK از List<T> پشتیبانی می‌کند که در آن T یکی از String، ParcelFileDescriptor، یا parcelable است. در Android 13 یا بالاتر، T می‌تواند هر نوع غیرابتدایی به‌جز آرایه باشد.

۴. بسته به اینکه انواع برای کد Rust ورودی (یک آرگومان) یا برونداد (یک مقدار برگشتی) باشند، به‌شیوه‌های متفاوتی منتقل می‌شوند.

۵. انواع اتحادیه در Android 12 و بالاتر پشتیبانی می‌شود.

۶. در Android 13 یا بالاتر، آرایه‌های با اندازه ثابت پشتیبانی می‌شوند. آرایه‌های اندازه ثابت می‌توانند چند بُعد داشته باشند (برای مثال، int[3][4]). در زیرینه Java، آرایه‌های اندازه ثابت به‌صورت انواع آرایه نشان داده می‌شوند.

‫۷. برای نمونه‌سازی شیء SharedRefBase پیونددهنده، از SharedRefBase::make\<My\>(... args ...) استفاده کنید. این تابع یک شیء std::shared_ptr\<T\> ایجاد می‌کند که درصورت تعلق داشتن binder به فرایند دیگری، به‌صورت داخلی نیز مدیریت می‌شود. ایجاد شیء به روش‌های دیگر باعث مالکیت مضاعف می‌شود.

۸. همچنین نوع Java یا AIDL byte[] را ببینید.

‫۹. در Rust، انواع داده‌ای که Default (IBinder، ParcelFileDescriptor، انواع میانجی، و parcelableهای بدون ساختار) را پیاده‌سازی نمی‌کنند، وقتی مقدار پیش‌فرض لازم باشد (مثلاً در فیلدهای parcelable، پارامترهای out، و آرایه‌های با اندازه ثابت در آن بافت‌ها)، حتی بدون حاشیه‌نویسی @nullable، به Option<T> نگاشت می‌شوند. در زمان اجرا، غیرقابل‌تهی بودن درطول بسته‌بندی همچنان اعمال می‌شود و اگر مقدار 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 backend، می‌توانید انتخاب کنید که رشته‌ها UTF-8 باشند یا UTF-16. رشته‌ها را در AIDL به‌عنوان @utf8InCpp String تعریف کنید تا به‌طور خودکار به UTF-8 تبدیل شوند. پشتیبان‌های NDK و Rust همیشه از رشته‌های UTF-8 استفاده می‌کنند. برای اطلاعات بیشتر درباره حاشیه‌نویسی utf8InCpp، به utf8InCpp مراجعه کنید.

قابلیت تهی بودن

می‌توانید انواع قابل‌تهی را با @nullable حاشیه‌نویسی کنید. برای اطلاعات بیشتر درباره گزارمان nullable، به تهی‌پذیر مراجعه کنید.

Parcelable سفارشی

Parcelable سفارشی نوعی parcelable است که به‌صورت دستی در پشتیبان هدف پیاده‌سازی می‌شود. فقط زمانی از parcelable سفارشی استفاده کنید که می‌خواهید پشتیبانی از زبان‌های دیگر را به parcelable سفارشی موجودی که قابل‌تغییر نیست اضافه کنید.

در اینجا نمونه‌ای از اعلامیه قابل‌بسته‌بندی AIDL آورده شده است:

    package my.pack.age;
    parcelable Foo;

به‌طور پیش‌فرض، این یک parcelable جاوا را اعلام می‌کند که در آن my.pack.age.Foo یک کلاس جاوا است که میانای Parcelable را پیاده‌سازی می‌کند.

برای اظهاریه بسته‌بندی‌پذیر زیرینه CPP سفارشی در AIDL، از cpp_header استفاده کنید:

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

پیاده‌سازی C++‎ در my/pack/age/Foo.h به این شکل است:

    #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);
    };

برای اعلام بسته‌بندی NDK سفارشی در AIDL، از ndk_header استفاده کنید:

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

پیاده‌سازی NDK در android/pack/age/Foo.h به این شکل است:

    #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، برای اعلام parcelable سفارشی Rust در AIDL، از rust_type استفاده کنید:

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

پیاده‌سازی Rust در 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 backend parcelable سفارشی ارائه دهید تا از آن‌ها در 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;
      ...
    }

مثال جاوا

    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 انواع خطای داخلی را برای استفاده سرویس‌ها هنگام گزارش کردن خطاها ارائه می‌دهد. این‌ها توسط متصل‌کننده‌ها استفاده می‌شوند و می‌توانند توسط هر سرویسی که رابط متصل‌کننده را پیاده‌سازی می‌کند استفاده شوند. استفاده از آن‌ها در تعریف AIDL به‌خوبی مستند شده است و به هیچ وضعیت یا نوع برگشتی تعریف‌شده توسط کاربر نیاز ندارند.

پارامترهای خروجی با خطا

وقتی تابع AIDL خطایی گزارش می‌کند، ممکن است تابع مقداردهی اولیه نشود یا پارامترهای برونداد را تغییر ندهد. به‌طور خاص، اگر خطا درطول باز کردن بسته‌بندی رخ دهد، برخلاف زمانی که درطول پردازش خود تراکنش رخ می‌دهد، ممکن است پارامترهای برونداد اصلاح شوند. به‌طورکلی، هنگام دریافت خطا از تابع AIDL، همه پارامترهای inout و out و همچنین مقدار برگشتی (که در برخی‌از زیرینه‌ها مانند پارامتر out عمل می‌کند) باید در وضعیت نامشخص درنظر گرفته شوند.

از کدام مقادیر خطا استفاده شود

بسیاری از مقادیر خطای داخلی را می‌توان در هر رابط AIDL استفاده کرد، اما برخی‌از آن‌ها به‌صورت ویژه مدیریت می‌شوند. برای مثال، استفاده از EX_UNSUPPORTED_OPERATION و EX_ILLEGAL_ARGUMENT وقتی شرایط خطا را توصیف می‌کنند مشکلی ندارد، اما EX_TRANSACTION_FAILED نباید استفاده شود زیرا زیرساخت زیربنایی با آن به‌طور خاص رفتار می‌کند. برای اطلاعات بیشتر درباره این مقادیر داخلی، تعاریف ویژه زیرینه را بررسی کنید.

اگر میانای AIDL به مقادیر خطای اضافی نیاز داشته باشد که انواع خطای داخلی آن‌ها را پوشش ندهد، می‌تواند از خطای داخلی ویژه سرویس خاص استفاده کند که اجازه می‌دهد مقدار خطای سرویس خاصی که توسط کاربر تعریف شده است به آن اضافه شود. این خطاهای مختص سرویس معمولاً در میانای AIDL به‌عنوان enum با پشتیبانی const int یا int تعریف می‌شوند و توسط binder تجزیه نمی‌شوند.

در جاوا، خطاها به استثناها، مانند 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 backend، به نمونه Rust AIDL در الگوهای Android Rust مراجعه کنید.

انواع وارد کردن

چه نوع تعریف‌شده‌ای واسط، بسته‌شدنی، یا اتحادیه باشد، می‌توانید آن را در 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 باید سرصفحه نوع ریشه‌اش را اضافه کنید. برای مثال، هنگام وارد کردن نوع Bar تودرتو که در my/package/IFoo.aidl تعریف شده است (IFoo نوع ریشه فایل است)، باید <my/package/IFoo.h> را برای زیرینه CPP (یا <aidl/my/package/IFoo.h> را برای زیرینه NDK) اضافه کنید.

پیاده‌سازی میانای کاربری

برای پیاده‌سازی یک میانای، باید از کلاس تهی بومی ارث‌بری کنید. پیاده‌سازی یک میانای اغلب سرویس نامیده می‌شود وقتی که با مدیر سرویس یا 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(())
        }
    }

یا با Rust غیرهمگام:

    use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
    use binder;

    /// This struct is defined to implement IRemoteService AIDL interface.
    pub struct MyFoo;

    impl Interface for MyFoo {}

    #[async_trait]
    impl IFooAsyncServer for MyFoo {
        async fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

ثبت‌نام و دریافت سرویس‌ها

سرویس‌ها در پلاتفرم Android معمولاً با فرایند servicemanager ثبت می‌شوند. علاوه‌بر میاناهای برنامه‌سازی کاربردی زیر، برخی‌از میاناهای برنامه‌سازی کاربردی سرویس را بررسی می‌کنند (یعنی اگر سرویس دردسترس نباشد، فوراً برمی‌گردند). برای اطلاع از جزئیات دقیق، رابط servicemanager مربوطه را بررسی کنید. فقط زمانی می‌توانید این عملیات را انجام دهید که دربرابر پلاتفرم Android کامپایل می‌کنید.

در جاوا:

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

With the multithreaded Tokio runtime, spawned tasks don't execute on the main thread. بنابراین، منطقی‌تر است که join_thread_pool را در رشته اصلی فراخوانی کنید تا رشته اصلی بیکار نباشد. برای ترک کردن زمینه ناهم‌زمان، باید تماس را در block_in_place بپیچید.

می‌توانید درخواست کنید که وقتی سرویس میزبان پوشه می‌میرد، اعلان دریافت کنید. این کار می‌تواند به جلوگیری از نشت پراکسی‌های تماس برگشتی کمک کند یا در بازیابی خطا کمک کند. این تماس‌ها را در اشیای پراکسی پیونددهنده برقرار کنید.

  • در جاوا، از 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 و UID فرایند کنونی را برمی‌گردانند.

در زیرینه جاوا:

    ... = 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 در خط فرمان برای دریافت اطلاعات از سرویس با dumpsys SERVICE [ARGS] استفاده کنید. در زمینه‌های C++ و Java، می‌توانید بااستفاده از آرگومان‌های اضافی برای addService، ترتیب ریختن سرویس‌ها را کنترل کنید. همچنین می‌توانید از dumpsys --pid SERVICE برای دریافت PID سرویس درحین اشکال‌زدایی استفاده کنید.

برای افزودن برونداد سفارشی به سرویس، روش dump را در شیء سرور لغو کنید، درست مثل اینکه روش IPC دیگری را که در فایل AIDL تعریف شده است پیاده‌سازی می‌کنید. هنگام انجام این کار، تخلیه را به اجازه برنامه 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();

به‌صورت پویا توصیفگر میانای کاربری را دریافت کنید

توصیفگر میانای نوع یک میانای را مشخص می‌کند. این ویژگی هنگام اشکال‌زدایی یا زمانی که یک چسباننده ناشناس دارید مفید است.

در جاوا، می‌توانید توصیفگر میانای را با کدی مانند این دریافت کنید:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

در زیرینه CPP:

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

بخش‌های پشتی NDK و Rust از این قابلیت پشتیبانی نمی‌کنند.

دریافت ثابت توصیفگر میانای کاربری

گاهی اوقات (مثلاً هنگام ثبت سرویس‌های @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

در باطن Rust:

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

محدوده شمارشی

در باطن‌های بومی، می‌توانید مقادیر احتمالی را که یک enum می‌تواند داشته باشد تکرار کنید. به‌دلیل ملاحظات اندازه کد، این ویژگی در Java پشتیبانی نمی‌شود.

برای شمارش MyEnum تعریف‌شده در AIDL، تکرار به‌صورت زیر ارائه می‌شود.

در زیرینه CPP:

    ::android::enum_range<MyEnum>()

در زیرینه NDK:

   ::ndk::enum_range<MyEnum>()

در زیرینه Rust:

    MyEnum::enum_values()

مدیریت رشته

هر نمونه از libbinder در یک فرایند یک استخر رشته را حفظ می‌کند. برای اکثر موارد استفاده، این باید دقیقاً یک استخر نخ باشد که در همه زیرینه‌ها مشترک است. تنها استثنا این است که اگر کد فروشنده نسخه دیگری از libbinder را بار کند تا با /dev/vndbinder صحبت کند. این روی گره binder جداگانه‌ای است، بنابراین threadpool هم‌رسانی نمی‌شود.

برای زیرینه 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 ناهم‌زمان، به دو استخر نخ نیاز دارید: binder و Tokio. این یعنی برنامه‌هایی که از Rust ناهمگام استفاده می‌کنند باید ملاحظات ویژه‌ای داشته باشند، به‌ویژه وقتی صحبت از استفاده از join_thread_pool باشد. برای اطلاعات بیشتر درباره این موضوع، بخش مربوط به ثبت سرویس‌ها را ببینید.

نام‌های رزروشده

C++‎،‏ Java، و Rust برخی‌از نام‌ها را به‌عنوان کلیدواژه یا برای استفاده مختص زبان رزرو می‌کنند. اگرچه AIDL محدودیت‌هایی براساس قوانین زبان اعمال نمی‌کند، استفاده از نام‌های فیلد یا نوعی که با نام رزروشده مطابقت داشته باشد می‌تواند منجر به خطای کامپایل برای C++ یا Java شود. برای Rust، نام فیلد یا نوع بااستفاده از نحو شناسه خام تغییر می‌کند و بااستفاده از پیشوند r# قابل‌دسترسی است.

توصیه می‌کنیم در تعریف‌های AIDL خود تا حد امکان از نام‌های رزروشده استفاده نکنید تا از اتصال‌های غیرارگونومیک یا خطای صریح در ترجمه جلوگیری کنید.

اگر ازقبل نام‌های رزروشده‌ای در تعاریف AIDL خود دارید، می‌توانید با حفظ سازگاری پروتکل، نام فیلدها را به‌طور ایمن تغییر دهید. برای ادامه ساختن، شاید لازم باشد کدتان را به‌روز کنید، اما هر برنامه‌ای که قبلاً ساخته شده است همچنان با برنامه‌های دیگر تعامل خواهد داشت.

نام‌هایی که باید از آن‌ها اجتناب کنید: