حمایت های AIDL

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

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

AIDL دارای backend های زیر است:

بک‌اند زبان سطح API ساخت سیستم‌ها
جاوا جاوا SDK یا SystemApi (پایدار*) همه
ان دی کی سی++ libbinder_ndk (پایدار*) aidl_interface
سی پی پی سی++ libbinder (ناپایدار) همه
زنگ زدگی زنگ زدگی libbinder_rs (پایدار*) aidl_interface
  • این سطوح API پایدار هستند، اما بسیاری از APIها، مانند APIهای مربوط به مدیریت سرویس، برای استفاده داخلی پلتفرم رزرو شده‌اند و در دسترس برنامه‌ها نیستند. برای اطلاعات بیشتر در مورد نحوه استفاده از AIDL در برنامه‌ها، به زبان تعریف رابط اندروید (AIDL) مراجعه کنید.
  • بک‌اند Rust در اندروید ۱۲ معرفی شد؛ بک‌اند NDK از اندروید ۱۰ در دسترس قرار گرفته است.
  • جعبه Rust بر روی libbinder_ndk ساخته شده است که به آن امکان پایداری و قابلیت حمل می‌دهد. APEXها از جعبه binder به روش استاندارد در سمت سیستم استفاده می‌کنند. بخش Rust در یک APEX قرار گرفته و درون آن ارسال می‌شود. این بخش به libbinder_ndk.so در پارتیشن سیستم بستگی دارد.

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

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

سیستم ساخت هسته

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

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

رابط کاربری aidl

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

انواع

کامپایلر aidl را به عنوان یک پیاده‌سازی مرجع برای انواع در نظر بگیرید. وقتی یک رابط ایجاد می‌کنید، aidl --lang=<backend> ... را برای دیدن فایل رابط حاصل فراخوانی کنید. وقتی از ماژول aidl_interface استفاده می‌کنید، می‌توانید خروجی را در out/soong/.intermediates/ <path to module> / مشاهده کنید.

نوع جاوا یا 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 9
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] 4
خروجی: Vec<T>
FileDescriptor android::base::unique_fd ناموجود ناموجود
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor 9
نوع رابط ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong<dyn T> 9
نوع بسته‌بندی ( T ) T T T ۹
نوع اتحادیه ( T ) 5 T T T
T[N] ۶ std::array<T, N> std::array<T, N> [T; N]

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

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

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

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

۵. انواع Union در اندروید ۱۲ و بالاتر پشتیبانی می‌شوند.

۶. در اندروید ۱۳ یا بالاتر، آرایه‌های با اندازه ثابت پشتیبانی می‌شوند. آرایه‌های با اندازه ثابت می‌توانند چندین بعد داشته باشند (برای مثال، int[3][4] ). در بک‌اند جاوا، آرایه‌های با اندازه ثابت به صورت انواع آرایه نمایش داده می‌شوند.

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

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

۹. در Rust، انواعی که Default پیاده‌سازی نمی‌کنند ( IBinder ، ParcelFileDescriptor ، انواع رابط و parcelableهای بدون ساختار) در صورت نیاز به مقدار پیش‌فرض (مانند فیلدهای parcelable، پارامترهای out و آرایه‌های با اندازه ثابت در آن زمینه‌ها)، حتی بدون حاشیه‌نویسی @nullable ، به Option<T> نگاشت می‌شوند. عدم nullability همچنان در زمان اجرا در طول parceling اعمال می‌شود و اگر مقدار 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 ، به nullable مراجعه کنید.

بسته‌های سفارشی

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

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

    package my.pack.age;
    parcelable Foo;

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

برای اعلان یک بسته‌بندی backend سفارشی 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);
    };

در اندروید ۱۵، برای تعریف یک بسته سفارشی 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);

سپس می‌توانید از این parcelable به عنوان یک نوع در فایل‌های AIDL استفاده کنید، اما توسط AIDL تولید نخواهد شد. برای parcelableهای سفارشی backend CPP و NDK، عملگرهای < و == را ارائه دهید تا از آنها در union استفاده کنید.

مقادیر پیش‌فرض

parcelableهای ساختاریافته می‌توانند مقادیر پیش‌فرض به ازای هر فیلد را برای مقادیر اولیه، فیلدهای String و آرایه‌هایی از این نوع‌ها تعریف کنند.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

در بک‌اند جاوا، وقتی مقادیر پیش‌فرض وجود نداشته باشند، فیلدها برای انواع اولیه با مقادیر صفر و برای انواع غیراولیه null مقداردهی اولیه می‌شوند.

در سایر backendها، فیلدها با مقادیر پیش‌فرض مقداردهی اولیه می‌شوند، زمانی که مقادیر پیش‌فرض تعریف نشده باشند. برای مثال، در backend سی‌پلاس‌پلاس، فیلدهای String به صورت یک رشته خالی و فیلدهای List<T> به صورت یک vector<T> خالی مقداردهی اولیه می‌شوند. فیلدهای @nullable به صورت فیلدهای null-value مقداردهی اولیه می‌شوند.

اتحادیه‌ها

اتحادیه‌های AIDL برچسب‌گذاری شده‌اند و ویژگی‌های آنها در همه backendها مشابه است. آنها با مقدار پیش‌فرض اولین فیلد ساخته می‌شوند و روشی مختص به زبان برای تعامل با آنها وجود دارد:

    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، یونیون‌ها به صورت enum پیاده‌سازی می‌شوند و getter و setter صریح ندارند.

    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

مدیریت خطا

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

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

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

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

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

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

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

انواع واردات

چه نوع تعریف‌شده یک رابط، parcelable یا union باشد، می‌توانید آن را در جاوا وارد کنید:

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;

اگرچه می‌توانید یک نوع داده تودرتو را در جاوا وارد کنید، اما در بک‌اندهای CPP و NDK باید سرآیند مربوط به نوع ریشه آن را وارد کنید. برای مثال، هنگام وارد کردن یک نوع داده تودرتو Bar که در my/package/IFoo.aidl تعریف شده است ( IFoo نوع ریشه فایل است)، باید <my/package/IFoo.h> را برای بک‌اند CPP (یا <aidl/my/package/IFoo.h> را برای بک‌اند NDK) وارد کنید.

پیاده‌سازی یک رابط

برای پیاده‌سازی یک رابط، باید از کلاس stub بومی ارث‌بری کنید. پیاده‌سازی یک رابط اغلب وقتی در service manager یا android.app.ActivityManager ثبت می‌شود، سرویس نامیده می‌شود و وقتی توسط کلاینت یک سرویس ثبت می‌شود، callback نامیده می‌شود. با این حال، بسته به کاربرد دقیق، از نام‌های مختلفی برای توصیف پیاده‌سازی‌های رابط استفاده می‌شود. کلاس stub دستورات را از درایور binder می‌خواند و متدهایی را که شما پیاده‌سازی می‌کنید، اجرا می‌کند. تصور کنید که یک فایل AIDL مانند این دارید:

    package my.package;
    interface IFoo {
        int doFoo();
    }

در جاوا، شما باید از کلاس Stub تولید شده ارث بری کنید:

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

در پشت صحنه CPP:

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

در بک‌اند NDK (به فضای نام اضافی aidl توجه کنید):

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

در بک‌اند 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(())
        }
    }

ثبت نام و دریافت خدمات

سرویس‌ها در پلتفرم اندروید معمولاً در فرآیند 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")));

در بک‌اند 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()
}

در backend ناهمزمان Rust، با یک runtime تک رشته‌ای:

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
}

یک تفاوت مهم با گزینه‌های دیگر این است که هنگام استفاده از async Rust و یک محیط اجرای تک‌رشته‌ای، join_thread_pool را فراخوانی نمی‌کنید . دلیل این امر این است که شما باید به Tokio یک نخ بدهید که بتواند وظایف ایجاد شده را در آن اجرا کند. در مثال زیر، نخ اصلی این هدف را برآورده می‌کند. هر وظیفه‌ای که با استفاده از tokio::spawn ایجاد شود، روی نخ اصلی اجرا می‌شود.

در backend ناهمزمان 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 قرار دهید.

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

  • در جاوا android.os.IBinder::linkToDeath استفاده کنید.
  • در بخش مدیریت CPP، android::IBinder::linkToDeath استفاده کنید.
  • در backend NDK، از AIBinder_linkToDeath استفاده کنید. همیشه AIBinder_DeathRecipient_setOnUnlinked برای کنترل طول عمر کوکی گیرنده مرگ خود استفاده کنید.
  • در backend زبان Rust، یک شیء DeathRecipient ایجاد کنید، سپس my_binder.link_to_death(&mut my_death_recipient) را فراخوانی کنید. توجه داشته باشید که از آنجایی که DeathRecipient مالک callback است، باید آن شیء را تا زمانی که می‌خواهید اعلان‌ها را دریافت کنید، فعال نگه دارید.

اطلاعات تماس‌گیرنده

هنگام دریافت یک فراخوانی binder هسته، اطلاعات فراخوانی‌کننده در چندین API موجود است. شناسه فرآیند (PID) به شناسه فرآیند لینوکس فرآیندی که تراکنش را ارسال می‌کند اشاره دارد. شناسه کاربر (UI) به شناسه کاربر لینوکس اشاره دارد. هنگام دریافت یک فراخوانی یک طرفه، PID فراخوانی‌کننده 0 است. خارج از زمینه تراکنش binder، این توابع PID و UID فرآیند فعلی را برمی‌گردانند.

در بک‌اند جاوا:

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

در پشت صحنه CPP:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

در بک‌اند NDK:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

در backend مربوط به Rust، هنگام پیاده‌سازی رابط کاربری، موارد زیر را مشخص کنید (به جای اینکه آن را به صورت پیش‌فرض تنظیم کنید):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

گزارش اشکال و اشکال‌زدایی API برای سرویس‌ها

وقتی گزارش‌های باگ اجرا می‌شوند (برای مثال، با adb bugreport )، اطلاعات را از سراسر سیستم جمع‌آوری می‌کنند تا به اشکال‌زدایی مشکلات مختلف کمک کنند. برای سرویس‌های AIDL، گزارش‌های باگ از dumpsys باینری روی تمام سرویس‌های ثبت‌شده در service manager برای ریختن اطلاعات خود در گزارش باگ استفاده می‌کنند. همچنین می‌توانید از dumpsys در خط فرمان برای دریافت اطلاعات از یک سرویس با dumpsys SERVICE [ARGS] استفاده کنید. در بک‌اندهای C++ و Java، می‌توانید ترتیب تخلیه سرویس‌ها را با استفاده از آرگومان‌های اضافی برای addService کنترل کنید. همچنین می‌توانید از dumpsys --pid SERVICE برای دریافت PID یک سرویس هنگام اشکال‌زدایی استفاده کنید.

برای افزودن خروجی سفارشی به سرویس خود، متد dump را در شیء سرور خود مانند هر متد IPC دیگری که در یک فایل AIDL تعریف شده است، بازنویسی کنید. هنگام انجام این کار، dumping را به مجوز برنامه android.permission.DUMP یا dumping را به 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;

در backend مربوط به Rust، هنگام پیاده‌سازی رابط کاربری، موارد زیر را مشخص کنید (به جای اینکه آن را به صورت پیش‌فرض تنظیم کنید):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

از نکات ضعف استفاده کنید

شما می‌توانید یک ارجاع ضعیف به یک شیء binder داشته باشید.

اگرچه جاوا از WeakReference پشتیبانی می‌کند، اما از ارجاعات ضعیف binder در لایه native پشتیبانی نمی‌کند.

در بک‌اند CPP، نوع ضعیف wp<IFoo> است.

در backend 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();

دریافت پویای توصیفگر رابط

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

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

    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 می‌تواند بپذیرد، عملیات تکرار (iterate) انجام دهید. به دلیل ملاحظات مربوط به حجم کد، این قابلیت در جاوا پشتیبانی نمی‌شود.

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

در پشت صحنه CPP:

    ::android::enum_range<MyEnum>()

در بک‌اند NDK:

   ::ndk::enum_range<MyEnum>()

در بک‌اند Rust:

    MyEnum::enum_values()

مدیریت نخ

هر نمونه از libbinder در یک فرآیند، یک threadpool را نگهداری می‌کند. در بیشتر موارد استفاده، این باید دقیقاً یک threadpool باشد که در تمام backendها به اشتراک گذاشته می‌شود. تنها استثنا زمانی است که کد فروشنده، یک کپی دیگر از libbinder برای ارتباط با /dev/vndbinder بارگذاری کند. این در یک گره binder جداگانه است، بنابراین threadpool به اشتراک گذاشته نمی‌شود.

برای بک‌اند جاوا، اندازه‌ی threadpool فقط می‌تواند افزایش یابد (زیرا از قبل شروع شده است):

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

با استفاده از backend ناهمزمان Rust، به دو threadpool نیاز دارید: binder و Tokio. این بدان معناست که برنامه‌هایی که از async Rust استفاده می‌کنند، به ملاحظات خاصی نیاز دارند، به خصوص در مورد استفاده از join_thread_pool . برای اطلاعات بیشتر در این مورد به بخش ثبت سرویس‌ها مراجعه کنید.

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

سی‌پلاس‌پلاس، جاوا و راست برخی از نام‌ها را به عنوان کلمات کلیدی یا برای استفاده خاص زبان رزرو می‌کنند. در حالی که AIDL محدودیت‌هایی را بر اساس قوانین زبان اعمال نمی‌کند، استفاده از نام‌های فیلد یا نوع که با یک نام رزرو شده مطابقت دارند، می‌تواند منجر به شکست کامپایل برای سی‌پلاس‌پلاس یا جاوا شود. برای راست، فیلد یا نوع با استفاده از سینتکس شناسه خام تغییر نام داده می‌شود که با استفاده از پیشوند r# قابل دسترسی است.

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

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

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

،

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

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

AIDL دارای backend های زیر است:

بک‌اند زبان سطح API ساخت سیستم‌ها
جاوا جاوا SDK یا SystemApi (پایدار*) همه
ان دی کی سی++ libbinder_ndk (پایدار*) aidl_interface
سی پی پی سی++ libbinder (ناپایدار) همه
زنگ زدگی زنگ زدگی libbinder_rs (پایدار*) aidl_interface
  • این سطوح API پایدار هستند، اما بسیاری از APIها، مانند APIهای مربوط به مدیریت سرویس، برای استفاده داخلی پلتفرم رزرو شده‌اند و در دسترس برنامه‌ها نیستند. برای اطلاعات بیشتر در مورد نحوه استفاده از AIDL در برنامه‌ها، به زبان تعریف رابط اندروید (AIDL) مراجعه کنید.
  • بک‌اند Rust در اندروید ۱۲ معرفی شد؛ بک‌اند NDK از اندروید ۱۰ در دسترس قرار گرفته است.
  • جعبه Rust بر روی libbinder_ndk ساخته شده است که به آن امکان پایداری و قابلیت حمل می‌دهد. APEXها از جعبه binder به روش استاندارد در سمت سیستم استفاده می‌کنند. بخش Rust در یک APEX قرار گرفته و درون آن ارسال می‌شود. این بخش به libbinder_ndk.so در پارتیشن سیستم بستگی دارد.

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

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

سیستم ساخت هسته

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

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

رابط کاربری aidl

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

انواع

کامپایلر aidl را به عنوان یک پیاده‌سازی مرجع برای انواع در نظر بگیرید. وقتی یک رابط ایجاد می‌کنید، aidl --lang=<backend> ... را برای دیدن فایل رابط حاصل فراخوانی کنید. وقتی از ماژول aidl_interface استفاده می‌کنید، می‌توانید خروجی را در out/soong/.intermediates/ <path to module> / مشاهده کنید.

نوع جاوا یا 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 9
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] 4
خروجی: Vec<T>
FileDescriptor android::base::unique_fd ناموجود ناموجود
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor 9
نوع رابط ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong<dyn T> 9
نوع بسته‌بندی ( T ) T T T ۹
نوع اتحادیه ( T ) 5 T T T
T[N] ۶ std::array<T, N> std::array<T, N> [T; N]

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

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

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

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

۵. انواع Union در اندروید ۱۲ و بالاتر پشتیبانی می‌شوند.

۶. در اندروید ۱۳ یا بالاتر، آرایه‌های با اندازه ثابت پشتیبانی می‌شوند. آرایه‌های با اندازه ثابت می‌توانند چندین بعد داشته باشند (برای مثال، int[3][4] ). در بک‌اند جاوا، آرایه‌های با اندازه ثابت به صورت انواع آرایه نمایش داده می‌شوند.

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

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

۹. در Rust، انواعی که Default پیاده‌سازی نمی‌کنند ( IBinder ، ParcelFileDescriptor ، انواع رابط و parcelableهای بدون ساختار) در صورت نیاز به مقدار پیش‌فرض (مانند فیلدهای parcelable، پارامترهای out و آرایه‌های با اندازه ثابت در آن زمینه‌ها)، حتی بدون حاشیه‌نویسی @nullable ، به Option<T> نگاشت می‌شوند. عدم nullability همچنان در زمان اجرا در طول parceling اعمال می‌شود و اگر مقدار 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 ، به nullable مراجعه کنید.

بسته‌های سفارشی

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

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

    package my.pack.age;
    parcelable Foo;

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

برای اعلان یک بسته‌بندی backend سفارشی 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);
    };

در اندروید ۱۵، برای تعریف یک بسته سفارشی 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);

سپس می‌توانید از این parcelable به عنوان یک نوع در فایل‌های AIDL استفاده کنید، اما توسط AIDL تولید نخواهد شد. برای parcelableهای سفارشی backend CPP و NDK، عملگرهای < و == را ارائه دهید تا از آنها در union استفاده کنید.

مقادیر پیش‌فرض

parcelableهای ساختاریافته می‌توانند مقادیر پیش‌فرض به ازای هر فیلد را برای مقادیر اولیه، فیلدهای String و آرایه‌هایی از این نوع‌ها تعریف کنند.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

در بک‌اند جاوا، وقتی مقادیر پیش‌فرض وجود نداشته باشند، فیلدها برای انواع اولیه با مقادیر صفر و برای انواع غیراولیه null مقداردهی اولیه می‌شوند.

In other backends, fields are initialized with default initialized values when default values aren't defined. For example, in the C++ backend, String fields are initialized as an empty string and List<T> fields are initialized as an empty vector<T> . @nullable fields are initialized as null-value fields.

Unions

AIDL unions are tagged and their features are similar in all backends. They're constructed to the first field's default value and they have a language-specific way to interact with them:

    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++ and NDK example

    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 example

In Rust, unions are implemented as enums and don't have explicit getters and setters.

    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

مدیریت خطا

The Android OS provides built-in error types for services to use when reporting errors. These are used by binders and can be used by any services implementing a binder interface. Their use is well documented in the AIDL definition and they don't require any user-defined status or return type.

Output parameters with errors

When an AIDL function reports an error, the function might not initialize or modify output parameters. Specifically, output parameters might be modified if the error occurs during unparceling, as opposed to happening during the processing of the transaction itself. In general, when getting an error from an AIDL function, all inout and out parameters as well as the return value (which acts like an out parameter in some backends) should be considered to be in an indefinite state.

Which error values to use

Many of the built-in error values can be used in any AIDL interfaces, but some are treated in a special way. For example, EX_UNSUPPORTED_OPERATION and EX_ILLEGAL_ARGUMENT are OK to use when they describe the error condition, but EX_TRANSACTION_FAILED must not be used because it's treated specially by the underlying infrastructure. Check the backend specific definitions for more information on these built-in values.

If the AIDL interface requires additional error values that aren't covered by the built-in error types, they can use the special service-specific built-in error that allows the inclusion of a service-specific error value that's defined by the user. 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.

In Java, errors map to exceptions, such as android.os.RemoteException . For service-specific exceptions, Java uses android.os.ServiceSpecificException along with the user-defined error.

Native code in Android doesn't use exceptions. The CPP backend uses android::binder::Status . The NDK backend uses ndk::ScopedAStatus . Every method generated by AIDL returns one of these, representing the status of the method. The Rust backend uses the same exception code values as the NDK, but converts them into native Rust errors ( StatusCode , ExceptionCode ) before delivering them to the user. For service-specific errors, the returned Status or ScopedAStatus uses EX_SERVICE_SPECIFIC along with the user-defined error.

The built-in error types can be found in the following files:

Backend تعریف
جاوا android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
زنگ زدگی android/binder_status.h

Use various backends

These instructions are specific to Android platform code. These examples use a defined type, my.package.IFoo . For instructions on how to use the Rust backend, see the Rust AIDL example in Android Rust patterns .

Import types

Whether the defined type is an interface, parcelable, or union, you can import it in Java:

import my.package.IFoo;

Or in the CPP backend:

#include <my/package/IFoo.h>

Or in the NDK backend (notice the extra aidl namespace):

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

Or in the Rust backend:

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

Although you can import a nested type in Java, in the CPP and NDK backends you must include the header for its root type. For example, when importing a nested type Bar defined in my/package/IFoo.aidl ( IFoo is the root type of the file) you must include <my/package/IFoo.h> for the CPP backend (or <aidl/my/package/IFoo.h> for the NDK backend).

Implement an interface

To implement an interface, you must inherit from the native stub class. An implementation of an interface is often called a service when it's registered with the service manager or android.app.ActivityManager and called a callback when it's registered by a client of a service. However, a variety of names are used to describe interface implementations depending on the exact usage. The stub class reads commands from the binder driver and executes the methods that you implement. Imagine that you have an AIDL file like this:

    package my.package;
    interface IFoo {
        int doFoo();
    }

In Java, you must extend from the generated Stub class:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Or with 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(())
        }
    }

Register and get services

Services in platform Android are usually registered with the servicemanager process. In addition to the following APIs, some APIs check the service (meaning they return immediately if the service isn't available). Check the corresponding servicemanager interface for exact details. You can perform these operations only when compiling against platform Android.

In Java:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

In the async Rust backend, with a single-threaded runtime:

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
}

One important difference from the other options is that you don't call join_thread_pool when using async Rust and a single-threaded runtime. This is because you need to give Tokio a thread where it can execute spawned tasks. In the following example, the main thread serves that purpose. Any tasks spawned using tokio::spawn execute on the main thread.

In the async Rust backend, with a multithreaded runtime:

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. Therefore, it makes more sense to call join_thread_pool on the main thread so that the main thread isn't idle. You must wrap the call in block_in_place to leave the async context.

You can request to get a notification for when a service hosting a binder dies. This can help to avoid leaking callback proxies or assist in error recovery. Make these calls on binder proxy objects.

  • In Java, use android.os.IBinder::linkToDeath .
  • In the CPP backend, use android::IBinder::linkToDeath .
  • In the NDK backend, use AIBinder_linkToDeath . Always use AIBinder_DeathRecipient_setOnUnlinked to control the lifetime of your death recipient cookie.
  • In the Rust backend, create a DeathRecipient object, then call my_binder.link_to_death(&mut my_death_recipient) . Note that because DeathRecipient owns the callback, you must keep that object alive as long as you want to receive notifications.

Caller information

When receiving a kernel binder call, caller information is available in several APIs. The process ID (PID) refers to the Linux process ID of the process that's sending a transaction. The user ID (UI) refers to the Linux user ID. When receiving a one-way call, the calling PID is 0. Outside of a binder transaction context, these functions return the PID and UID of the current process.

In the Java backend:

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

In the CPP backend:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

In the NDK backend:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Bug reports and debugging API for services

When bug reports run (for example, with adb bugreport ), they collect information from all around the system to aid with debugging various issues. For AIDL services, bug reports use the binary dumpsys on all services registered with the service manager to dump their information into the bug report. You can also use dumpsys on the command line to get information from a service with dumpsys SERVICE [ARGS] . In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService . You can also use dumpsys --pid SERVICE to get the PID of a service while debugging.

To add custom output to your service, override the dump method in your server object like you're implementing any other IPC method defined in an AIDL file. When doing this, restrict dumping to the app permission android.permission.DUMP or restrict dumping to specific UIDs.

In the Java backend:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

In the CPP backend:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

In the NDK backend:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

Use weak pointers

You can hold a weak reference to a binder object.

While Java supports WeakReference , it doesn't support weak binder references at the native layer.

In the CPP backend, the weak type is wp<IFoo> .

In the NDK backend, use ScopedAIBinder_Weak :

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

In the Rust backend, use WpIBinder or Weak<IFoo> :

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Dynamically get interface descriptor

The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.

In Java, you can get the interface descriptor with code such as:

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

In the CPP backend:

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

The NDK and Rust backends don't support this capability.

Statically get interface descriptor

Sometimes (such as when registering @VintfStability services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Enum range

In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java.

For an enum MyEnum defined in AIDL, iteration is provided as follows.

In the CPP backend:

    ::android::enum_range<MyEnum>()

In the NDK backend:

   ::ndk::enum_range<MyEnum>()

In the Rust backend:

    MyEnum::enum_values()

Thread management

Every instance of libbinder in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception is if vendor code loads another copy of libbinder to talk to /dev/vndbinder . This is on a separate binder node, so the threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (because it's already started):

    BinderInternal.setMaxThreads(<new larger value>);

For the CPP backend, the following operations are available:

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

Similarly, in the NDK backend:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

In the Rust backend:

    binder::ProcessState::start_thread_pool();
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    binder::ProcessState::join_thread_pool();

With the async Rust backend, you need two threadpools: binder and Tokio. This means that apps using async Rust need special considerations, especially when it comes to the use of join_thread_pool . See the section on registering services for more information on this.

Reserved names

C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that match a reserved name can result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the raw identifier syntax, accessible using the r# prefix.

We recommend avoiding using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.

If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible. You might need to update your code to continue building, but any already built programs continue to interoperate.

Names to avoid:

،

An AIDL backend is a target for stub code generation. Always use AIDL files in a particular language with a specific runtime. Depending on the context, you should use different AIDL backends.

In the following table, the stability of the API surface refers to the ability to compile code against this API surface in a way that the code can be delivered independently from the system.img libbinder.so binary.

AIDL has the following backends:

Backend زبان API surface Build systems
جاوا جاوا SDK or SystemApi (stable*) همه
NDK C++ libbinder_ndk (stable*) aidl_interface
CPP C++ libbinder (unstable) همه
زنگ زدگی زنگ زدگی libbinder_rs (stable*) aidl_interface
  • These API surfaces are stable, but many of the APIs, such as those for service management, are reserved for internal platform use and aren't available to apps. For more information on how to use AIDL in apps, see Android Interface Definition Language (AIDL) .
  • The Rust backend was introduced in Android 12; the NDK backend has been available as of Android 10.
  • The Rust crate is built on top of libbinder_ndk , which lets it be stable and portable. APEXes use the binder crate in the standard way on the system side. The Rust portion is bundled into an APEX and shipped inside it. This portion depends on the libbinder_ndk.so on the system partition.

Build systems

Depending on the backend, there are two ways to compile AIDL into stub code. For more details on the build systems, see Soong Modules Reference .

Core build system

In any cc_ or java_ Android.bp module (or in their Android.mk equivalents), you can specify AIDL ( .aidl ) files as source files. In this case, the Java or CPP backends of AIDL are used (not the NDK backend), and the classes to use the corresponding AIDL files are added to the module automatically. You can specify options such as local_include_dirs (which tells the build system the root path to AIDL files in that module) in these modules under an aidl: group.

The Rust backend is only for use with Rust. rust_ modules are handled differently in that AIDL files aren't specified as source files. Instead, the aidl_interface module produces a rustlib called aidl_interface_name -rust , which can be linked against. For details, see the Rust AIDL example .

aidl_interface

Types used with the aidl_interface build system must be structured. In order to be structured, parcelables must contain fields directly and not be declarations of types defined directly in target languages. For how structured AIDL fits in with stable AIDL, see Structured versus stable AIDL .

انواع

Consider the aidl compiler as a reference implementation for types. When you create an interface, invoke aidl --lang=<backend> ... to see the resulting interface file. When you use the aidl_interface module, you can view the output in out/soong/.intermediates/ <path to module> / .

Java or AIDL type C++ type NDK type Rust type
boolean bool bool bool
byte 8 int8_t int8_t i8
char char16_t char16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string In: &str
Out: String
android.os.Parcelable android::Parcelable ناموجود ناموجود
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder 9
T[] std::vector<T> std::vector<T> In: &[T]
Out: Vec<T>
byte[] std::vector std::vector ۱ In: &[u8]
Out: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 In: In: &[T] 4
Out: Vec<T>
FileDescriptor android::base::unique_fd ناموجود ناموجود
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor 9
Interface type ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong<dyn T> 9
Parcelable type ( T ) T T T 9
Union type ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. In Android 12 or higher, byte arrays use uint8_t instead of int8_t for compatibility reasons.

2. The C++ backend supports List<T> where T is one of String , IBinder , ParcelFileDescriptor or parcelable. In Android 13 or higher, T can be any nonprimitive type (including interface types) except arrays. AOSP recommends using array types like T[] , because they work in all backends.

3. The NDK backend supports List<T> where T is one of String , ParcelFileDescriptor or parcelable. In Android 13 or higher, T can be any nonprimitive type except arrays.

4. Types are passed differently for Rust code depending on whether they are input (an argument), or an output (a returned value).

5. Union types are supported in Android 12 and higher.

6. In Android 13 or higher, fixed-size arrays are supported. Fixed-size arrays can have multiple dimensions (for example, int[3][4] ). In the Java backend, fixed-size arrays are represented as array types.

7. To instantiate a binder SharedRefBase object, use SharedRefBase::make\<My\>(... args ...) . This function creates a std::shared_ptr\<T\> object, which is also managed internally, in case the binder is owned by another process. Creating the object other ways causes double ownership.

8. See also Java or AIDL type byte[] .

9. In Rust, types that don't implement Default ( IBinder , ParcelFileDescriptor , interface types, and unstructured parcelables) map to Option<T> when a default value is required (such as in parcelable fields, out parameters, and fixed-size arrays in those contexts), even without the @nullable annotation. Non-nullability is still enforced at runtime during parceling, returning StatusCode::UNEXPECTED_NULL if the value is None . For method return values, in parameters (passed as &T ), and inout parameters (passed as &mut T ), these types aren't wrapped in Option<T> unless annotated with @nullable .

Directionality (in, out, and inout)

When specifying the types of the arguments to functions, you can specify them as in , out , or inout . This controls the direction that information is passed for an IPC call.

  • The in argument specifier indicates data is passed from the caller to the callee. The in specifier is the default direction, but if data types can also be out , then you must specify the direction.

  • The out argument specifier means that data is passed from the callee to the caller.

  • The inout argument specifier is the combination of both of these. However, we recommend avoiding using the argument specifier inout . If you use inout with a versioned interface and an older callee, the additional fields that are present only in the caller get reset to their default values. With respect to Rust, a normal inout type receives &mut T , and a list inout type receives &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 and UTF-16

With the CPP backend, you can choose whether strings are UTF-8 or UTF-16. Declare strings as @utf8InCpp String in AIDL to automatically convert them to UTF-8. The NDK and Rust backends always use UTF-8 strings. For more information about the utf8InCpp annotation, see utf8InCpp .

Nullability

You can annotate types that can be null with @nullable . For more information about the nullable annotation, see nullable .

Custom parcelables

A custom parcelable is a parcelable that's implemented manually in a target backend. Use custom parcelables only when you're trying to add support to other languages for an existing custom parcelable which can't be changed.

Here's an example of an AIDL parcelable declaration:

    package my.pack.age;
    parcelable Foo;

By default, this declares a Java parcelable where my.pack.age.Foo is a Java class implementing the Parcelable interface.

For a declaration of a custom CPP backend parcelable in AIDL, use cpp_header :

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

The C++ implementation in my/pack/age/Foo.h looks like this:

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

For a declaration of a custom NDK parcelable in AIDL, use ndk_header :

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

The NDK implementation in android/pack/age/Foo.h looks like this:

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

In Android 15, for declaration of a custom Rust parcelable in AIDL, use rust_type :

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

The Rust implementation in rust_crate/src/lib.rs looks like this:

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

Then you can use this parcelable as a type in AIDL files, but it won't be generated by AIDL. Provide < and == operators for CPP and NDK backend custom parcelables to use them in union .

Default values

Structured parcelables can declare per-field default values for primitives, String fields, and arrays of these types.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

In the Java backend, when default values are missing, fields are initialized as zero values for primitive types and null for nonprimitive types.

In other backends, fields are initialized with default initialized values when default values aren't defined. For example, in the C++ backend, String fields are initialized as an empty string and List<T> fields are initialized as an empty vector<T> . @nullable fields are initialized as null-value fields.

Unions

AIDL unions are tagged and their features are similar in all backends. They're constructed to the first field's default value and they have a language-specific way to interact with them:

    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++ and NDK example

    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 example

In Rust, unions are implemented as enums and don't have explicit getters and setters.

    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

مدیریت خطا

The Android OS provides built-in error types for services to use when reporting errors. These are used by binders and can be used by any services implementing a binder interface. Their use is well documented in the AIDL definition and they don't require any user-defined status or return type.

Output parameters with errors

When an AIDL function reports an error, the function might not initialize or modify output parameters. Specifically, output parameters might be modified if the error occurs during unparceling, as opposed to happening during the processing of the transaction itself. In general, when getting an error from an AIDL function, all inout and out parameters as well as the return value (which acts like an out parameter in some backends) should be considered to be in an indefinite state.

Which error values to use

Many of the built-in error values can be used in any AIDL interfaces, but some are treated in a special way. For example, EX_UNSUPPORTED_OPERATION and EX_ILLEGAL_ARGUMENT are OK to use when they describe the error condition, but EX_TRANSACTION_FAILED must not be used because it's treated specially by the underlying infrastructure. Check the backend specific definitions for more information on these built-in values.

If the AIDL interface requires additional error values that aren't covered by the built-in error types, they can use the special service-specific built-in error that allows the inclusion of a service-specific error value that's defined by the user. 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.

In Java, errors map to exceptions, such as android.os.RemoteException . For service-specific exceptions, Java uses android.os.ServiceSpecificException along with the user-defined error.

Native code in Android doesn't use exceptions. The CPP backend uses android::binder::Status . The NDK backend uses ndk::ScopedAStatus . Every method generated by AIDL returns one of these, representing the status of the method. The Rust backend uses the same exception code values as the NDK, but converts them into native Rust errors ( StatusCode , ExceptionCode ) before delivering them to the user. For service-specific errors, the returned Status or ScopedAStatus uses EX_SERVICE_SPECIFIC along with the user-defined error.

The built-in error types can be found in the following files:

Backend تعریف
جاوا android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
زنگ زدگی android/binder_status.h

Use various backends

These instructions are specific to Android platform code. These examples use a defined type, my.package.IFoo . For instructions on how to use the Rust backend, see the Rust AIDL example in Android Rust patterns .

Import types

Whether the defined type is an interface, parcelable, or union, you can import it in Java:

import my.package.IFoo;

Or in the CPP backend:

#include <my/package/IFoo.h>

Or in the NDK backend (notice the extra aidl namespace):

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

Or in the Rust backend:

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

Although you can import a nested type in Java, in the CPP and NDK backends you must include the header for its root type. For example, when importing a nested type Bar defined in my/package/IFoo.aidl ( IFoo is the root type of the file) you must include <my/package/IFoo.h> for the CPP backend (or <aidl/my/package/IFoo.h> for the NDK backend).

Implement an interface

To implement an interface, you must inherit from the native stub class. An implementation of an interface is often called a service when it's registered with the service manager or android.app.ActivityManager and called a callback when it's registered by a client of a service. However, a variety of names are used to describe interface implementations depending on the exact usage. The stub class reads commands from the binder driver and executes the methods that you implement. Imagine that you have an AIDL file like this:

    package my.package;
    interface IFoo {
        int doFoo();
    }

In Java, you must extend from the generated Stub class:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Or with 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(())
        }
    }

Register and get services

Services in platform Android are usually registered with the servicemanager process. In addition to the following APIs, some APIs check the service (meaning they return immediately if the service isn't available). Check the corresponding servicemanager interface for exact details. You can perform these operations only when compiling against platform Android.

In Java:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

In the async Rust backend, with a single-threaded runtime:

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
}

One important difference from the other options is that you don't call join_thread_pool when using async Rust and a single-threaded runtime. This is because you need to give Tokio a thread where it can execute spawned tasks. In the following example, the main thread serves that purpose. Any tasks spawned using tokio::spawn execute on the main thread.

In the async Rust backend, with a multithreaded runtime:

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. Therefore, it makes more sense to call join_thread_pool on the main thread so that the main thread isn't idle. You must wrap the call in block_in_place to leave the async context.

You can request to get a notification for when a service hosting a binder dies. This can help to avoid leaking callback proxies or assist in error recovery. Make these calls on binder proxy objects.

  • In Java, use android.os.IBinder::linkToDeath .
  • In the CPP backend, use android::IBinder::linkToDeath .
  • In the NDK backend, use AIBinder_linkToDeath . Always use AIBinder_DeathRecipient_setOnUnlinked to control the lifetime of your death recipient cookie.
  • In the Rust backend, create a DeathRecipient object, then call my_binder.link_to_death(&mut my_death_recipient) . Note that because DeathRecipient owns the callback, you must keep that object alive as long as you want to receive notifications.

Caller information

When receiving a kernel binder call, caller information is available in several APIs. The process ID (PID) refers to the Linux process ID of the process that's sending a transaction. The user ID (UI) refers to the Linux user ID. When receiving a one-way call, the calling PID is 0. Outside of a binder transaction context, these functions return the PID and UID of the current process.

In the Java backend:

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

In the CPP backend:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

In the NDK backend:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Bug reports and debugging API for services

When bug reports run (for example, with adb bugreport ), they collect information from all around the system to aid with debugging various issues. For AIDL services, bug reports use the binary dumpsys on all services registered with the service manager to dump their information into the bug report. You can also use dumpsys on the command line to get information from a service with dumpsys SERVICE [ARGS] . In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService . You can also use dumpsys --pid SERVICE to get the PID of a service while debugging.

To add custom output to your service, override the dump method in your server object like you're implementing any other IPC method defined in an AIDL file. When doing this, restrict dumping to the app permission android.permission.DUMP or restrict dumping to specific UIDs.

In the Java backend:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

In the CPP backend:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

In the NDK backend:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

Use weak pointers

You can hold a weak reference to a binder object.

While Java supports WeakReference , it doesn't support weak binder references at the native layer.

In the CPP backend, the weak type is wp<IFoo> .

In the NDK backend, use ScopedAIBinder_Weak :

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

In the Rust backend, use WpIBinder or Weak<IFoo> :

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Dynamically get interface descriptor

The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.

In Java, you can get the interface descriptor with code such as:

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

In the CPP backend:

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

The NDK and Rust backends don't support this capability.

Statically get interface descriptor

Sometimes (such as when registering @VintfStability services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Enum range

In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java.

For an enum MyEnum defined in AIDL, iteration is provided as follows.

In the CPP backend:

    ::android::enum_range<MyEnum>()

In the NDK backend:

   ::ndk::enum_range<MyEnum>()

In the Rust backend:

    MyEnum::enum_values()

Thread management

Every instance of libbinder in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception is if vendor code loads another copy of libbinder to talk to /dev/vndbinder . This is on a separate binder node, so the threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (because it's already started):

    BinderInternal.setMaxThreads(<new larger value>);

For the CPP backend, the following operations are available:

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

Similarly, in the NDK backend:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

In the Rust backend:

    binder::ProcessState::start_thread_pool();
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    binder::ProcessState::join_thread_pool();

With the async Rust backend, you need two threadpools: binder and Tokio. This means that apps using async Rust need special considerations, especially when it comes to the use of join_thread_pool . See the section on registering services for more information on this.

Reserved names

C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that match a reserved name can result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the raw identifier syntax, accessible using the r# prefix.

We recommend avoiding using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.

If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible. You might need to update your code to continue building, but any already built programs continue to interoperate.

Names to avoid: