حمایت های AIDL

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

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

AIDL دارای پشتوانه های زیر است:

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

ساخت سیستم ها

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

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

در هر 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 مراجعه کنید.

idl_interface

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

انواع

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

نوع جاوا یا AIDL نوع C++ نوع NDK نوع زنگ
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 در: &str
خارج: String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> در: &[T]
خروجی: Vec<T>
byte[] std::vector std::vector 1 در: &[u8]
خروجی: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 در: In: &[T] 4
خروجی: Vec<T>
FileDescriptor android::base::unique_fd N/A N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
نوع رابط ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
نوع قابل بسته بندی ( T ) T T T
نوع اتحادیه ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

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

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

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

5. انواع Union در اندروید 12 و بالاتر پشتیبانی می شود.

6. در اندروید 13 یا بالاتر، آرایه‌های با اندازه ثابت پشتیبانی می‌شوند. آرایه‌های با اندازه ثابت می‌توانند چندین بعد داشته باشند (به عنوان مثال، int[3][4] ). در باطن جاوا، آرایه های با اندازه ثابت به عنوان انواع آرایه نمایش داده می شوند.

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

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

جهت دهی (داخل، بیرون و بیرون)

هنگام تعیین انواع آرگومان ها برای توابع، می توانید آنها را به صورت in ، out یا inout مشخص کنید. این جهتی را که اطلاعات برای تماس IPC ارسال می شود، کنترل می کند.

  • مشخص کننده in نشان می دهد که داده ها از تماس گیرنده به تماس گیرنده ارسال می شود. مشخص کننده in جهت پیش فرض است، اما اگر انواع داده ها نیز می توانند out باشند، باید جهت را مشخص کنید.

  • مشخص کننده آرگومان out به این معنی است که داده ها از فراخوان به تماس گیرنده ارسال می شود.

  • مشخص کننده آرگومان inout ترکیبی از هر دوی اینها است. با این حال، توصیه می کنیم از استفاده از مشخص کننده آرگومان inout اجتناب کنید. اگر inout با یک رابط نسخه شده و یک تماس گیرنده قدیمی استفاده می کنید، فیلدهای اضافی که فقط در تماس گیرنده وجود دارند به مقادیر پیش فرض خود بازنشانی می شوند. با توجه به Rust، یک نوع inout معمولی &mut T را دریافت می کند، و یک نوع inout لیستی &mut Vec<T> را دریافت می کند.

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 و UTF-16

با پشتوانه CPP، می توانید انتخاب کنید که رشته ها UTF-8 یا UTF-16 باشند. رشته ها را به عنوان @utf8InCpp String در AIDL اعلام کنید تا به طور خودکار آنها را به UTF-8 تبدیل کنید. پشتیبان‌های NDK و Rust همیشه از رشته‌های UTF-8 استفاده می‌کنند. برای اطلاعات بیشتر در مورد حاشیه نویسی utf8InCpp ، به utf8InCpp مراجعه کنید.

پوچ پذیری

می‌توانید انواعی را که می‌توانند null باشند با @nullable حاشیه‌نویسی کنید. برای اطلاعات بیشتر در مورد حاشیه نویسی nullable ، به nullable مراجعه کنید.

بسته بندی های سفارشی

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

در اینجا نمونه ای از یک اعلامیه بسته بندی 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، برای اعلام یک Rust سفارشی parcelable در 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 ارائه دهید تا از آنها در union استفاده کنید.

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

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

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

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

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

اتحادیه ها

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

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

مثال جاوا

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setSringField("abc");                // setter

مثال C++ و NDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

نمونه زنگ زدگی

در Rust، اتحادیه ها به صورت enum اجرا می شوند و گیرنده و تنظیم کننده صریح ندارند.

    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 و همچنین مقدار بازگشتی (که در برخی از backendها مانند یک پارامتر out عمل می کند) باید در حالت نامحدود در نظر گرفته شوند.

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

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

اگر رابط AIDL به مقادیر خطای اضافی نیاز داشته باشد که توسط انواع خطای داخلی پوشش داده نمی‌شود، می‌توانند از خطای داخلی خاص سرویس استفاده کنند که اجازه می‌دهد یک مقدار خطای خاص سرویس را که توسط کاربر تعریف شده است درج کند. این خطاهای خاص سرویس معمولاً در رابط AIDL به عنوان یک enum با پشتوانه const int یا int تعریف می شوند و توسط 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 همراه با خطای تعریف شده توسط کاربر استفاده می کند.

انواع خطاهای داخلی را می توان در فایل های زیر یافت:

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

از باطن های مختلف استفاده کنید

این دستورالعمل ها مختص کد پلتفرم اندروید هستند. این نمونه ها از نوع تعریف شده my.package.IFoo استفاده می کنند. برای دستورالعمل‌های نحوه استفاده از Rust Backend، به مثال Rust AIDL در الگوهای Android Rust مراجعه کنید.

انواع واردات

چه نوع تعریف شده یک رابط، بسته پذیر یا اتحادیه باشد، می توانید آن را در جاوا وارد کنید:

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 بومی ارث بری کنید. پیاده‌سازی یک رابط معمولاً هنگامی که در مدیر سرویس یا android.app.ActivityManager ثبت می‌شود، سرویس نامیده می‌شود و زمانی که توسط مشتری یک سرویس ثبت می‌شود، یک callback نامیده می‌شود. با این حال، بسته به کاربرد دقیق، از نام‌های مختلفی برای توصیف پیاده‌سازی رابط استفاده می‌شود. کلاس stub دستورات را از درایور بایندر می خواند و متدهایی را که شما پیاده سازی می کنید اجرا می کند. تصور کنید که یک فایل 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 async:

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

در باطن Rust async، با زمان اجرا تک رشته ای:

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 async و زمان اجرا تک رشته ای، join_thread_pool فراخوانی نمی کنید . این به این دلیل است که باید به توکیو رشته ای بدهید تا بتواند وظایف ایجاد شده را اجرا کند. در مثال زیر، موضوع اصلی این هدف را دنبال می کند. هر وظیفه ای که با استفاده از tokio::spawn ایجاد می شود در رشته اصلی اجرا می شود.

در باطن Rust async، با زمان اجرا چند رشته ای:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

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

    // Sleep forever.
    tokio::task::block_in_place(|| {
        binder::ProcessState::join_thread_pool();
    });
}

با زمان اجرا چند رشته ای توکیو، وظایف ایجاد شده در رشته اصلی اجرا نمی شوند. بنابراین، فراخوانی join_thread_pool در موضوع اصلی منطقی تر است تا رشته اصلی بیکار نباشد. شما باید تماس را در block_in_place بپیچید تا از زمینه ناهمگام خارج شوید.

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

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

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

هنگام دریافت تماس بایندر هسته، اطلاعات تماس گیرنده در چندین API موجود است. شناسه فرآیند (PID) به شناسه فرآیند لینوکس فرآیندی که تراکنش را ارسال می کند، اشاره دارد. شناسه کاربری (UI) به شناسه کاربری لینوکس اشاره دارد. هنگام دریافت تماس یک طرفه، PID فراخوانی 0 است. خارج از زمینه تراکنش بایندر، این توابع 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++ و جاوا، می‌توانید ترتیب حذف سرویس‌ها را با استفاده از آرگومان‌های اضافی برای addService کنترل کنید. همچنین می‌توانید از dumpsys --pid SERVICE برای دریافت PID یک سرویس در حین اشکال‌زدایی استفاده کنید.

برای افزودن خروجی سفارشی به سرویس خود، روش dump را در شی سرور خود نادیده بگیرید، مانند اینکه در حال اجرای هر روش IPC دیگری هستید که در یک فایل AIDL تعریف شده است. هنگام انجام این کار، تخلیه را به مجوز برنامه android.permission.DUMP محدود کنید یا تخلیه را به UIDهای خاص محدود کنید.

در باطن جاوا:

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

در باطن CPP:

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

در باطن NDK:

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

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

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

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

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

در حالی که جاوا از WeakReference پشتیبانی می کند، از مراجع ضعیف بایندر در لایه اصلی پشتیبانی نمی کند.

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

در باطن NDK، از ScopedAIBinder_Weak استفاده کنید:

#include <android/binder_auto_utils.h>

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

در باطن 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

در backend های بومی، می توانید مقادیر احتمالی را که enum می تواند دریافت کند، تکرار کنید. به دلیل ملاحظات اندازه کد، این در جاوا پشتیبانی نمی شود.

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

در باطن CPP:

    ::android::enum_range<MyEnum>()

در باطن NDK:

   ::ndk::enum_range<MyEnum>()

در باطن Rust:

    MyEnum::enum_values()

مدیریت موضوع

هر نمونه از libbinder در یک فرآیند یک Threadpool را حفظ می کند. برای اکثر موارد استفاده، این باید دقیقاً یک Threadpool باشد که در تمام backendها به اشتراک گذاشته شود. تنها استثنا این است که کد فروشنده یک کپی دیگر از libbinder برای صحبت با /dev/vndbinder بارگیری کند. این در یک گره کلاسور جداگانه است، بنابراین 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();

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

اسامی رزرو شده

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

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

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

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

،

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

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

AIDL دارای پشتوانه های زیر است:

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

ساخت سیستم ها

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

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

در هر 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 مراجعه کنید.

idl_interface

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

انواع

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

نوع جاوا یا AIDL نوع C++ نوع NDK نوع زنگ
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 در: &str
خارج: String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> در: &[T]
خروجی: Vec<T>
byte[] std::vector std::vector 1 در: &[u8]
خروجی: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 در: In: &[T] 4
خروجی: Vec<T>
FileDescriptor android::base::unique_fd N/A N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
نوع رابط ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
نوع قابل بسته بندی ( T ) T T T
نوع اتحادیه ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

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

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

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

5. انواع Union در اندروید 12 و بالاتر پشتیبانی می شود.

6. در اندروید 13 یا بالاتر، آرایه‌های با اندازه ثابت پشتیبانی می‌شوند. آرایه‌های با اندازه ثابت می‌توانند چندین بعد داشته باشند (به عنوان مثال، int[3][4] ). در باطن جاوا، آرایه های با اندازه ثابت به عنوان انواع آرایه نمایش داده می شوند.

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

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

جهت دهی (داخل، بیرون و بیرون)

هنگام تعیین انواع آرگومان ها برای توابع، می توانید آنها را به صورت in ، out یا inout مشخص کنید. این جهتی را که اطلاعات برای تماس IPC ارسال می شود، کنترل می کند.

  • مشخص کننده in نشان می دهد که داده ها از تماس گیرنده به تماس گیرنده ارسال می شود. مشخص کننده in جهت پیش فرض است، اما اگر انواع داده ها نیز می توانند out باشند، باید جهت را مشخص کنید.

  • مشخص کننده آرگومان out به این معنی است که داده ها از فراخوان به تماس گیرنده ارسال می شود.

  • مشخص کننده آرگومان inout ترکیبی از هر دوی اینها است. با این حال، توصیه می کنیم از استفاده از مشخص کننده آرگومان inout اجتناب کنید. اگر inout با یک رابط نسخه شده و یک تماس گیرنده قدیمی استفاده می کنید، فیلدهای اضافی که فقط در تماس گیرنده وجود دارند به مقادیر پیش فرض خود بازنشانی می شوند. با توجه به Rust، یک نوع inout معمولی &mut T را دریافت می کند، و یک نوع inout لیستی &mut Vec<T> را دریافت می کند.

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF-8 و UTF-16

با پشتوانه CPP، می توانید انتخاب کنید که رشته ها UTF-8 یا UTF-16 باشند. رشته ها را به عنوان @utf8InCpp String در AIDL اعلام کنید تا به طور خودکار آنها را به UTF-8 تبدیل کنید. پشتیبان‌های NDK و Rust همیشه از رشته‌های UTF-8 استفاده می‌کنند. برای اطلاعات بیشتر در مورد حاشیه نویسی utf8InCpp ، به utf8InCpp مراجعه کنید.

پوچ پذیری

می‌توانید انواعی را که می‌توانند null باشند با @nullable حاشیه‌نویسی کنید. برای اطلاعات بیشتر در مورد حاشیه نویسی nullable ، به nullable مراجعه کنید.

بسته بندی های سفارشی

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

در اینجا نمونه ای از یک اعلامیه بسته بندی 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، برای اعلام یک Rust سفارشی parcelable در 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 ارائه دهید تا از آنها در union استفاده کنید.

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

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

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

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

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

اتحادیه ها

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

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

مثال جاوا

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setSringField("abc");                // setter

مثال C++ و NDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

نمونه زنگ زدگی

در Rust، اتحادیه ها به صورت enum اجرا می شوند و گیرنده و تنظیم کننده صریح ندارند.

    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 و همچنین مقدار بازگشتی (که در برخی از backendها مانند یک پارامتر out عمل می کند) باید در حالت نامحدود در نظر گرفته شوند.

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

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

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

در جاوا ، خطاها به استثنائات ، مانند android.os.RemoteException نقشه می رود. برای استثنائات خاص خدمات ، جاوا از android.os.ServiceSpecificException به همراه خطای تعریف شده توسط کاربر استفاده می کند.

کد بومی در اندروید از استثنائات استفاده نمی کند. Backend CPP از android::binder::Status استفاده می کند. Backend NDK از ndk::ScopedAStatus استفاده می کند. هر روشی که توسط AIDL ایجاد شود ، یکی از این موارد را نشان می دهد و وضعیت روش را نشان می دهد. Backend Rust از همان مقادیر کد استثنائی به عنوان NDK استفاده می کند ، اما قبل از تحویل آنها به کاربر ، آنها را به خطاهای زنگ زدگی بومی ( StatusCode ، ExceptionCode ) تبدیل می کند. برای خطاهای خاص خدمات ، Status برگشتی یا ScopedAStatus از EX_SERVICE_SPECIFIC به همراه خطای تعریف شده توسط کاربر استفاده می کند.

انواع خطای داخلی را می توان در پرونده های زیر یافت:

Backend تعریف
جاوا 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 در الگوهای زنگ زدگی اندرویدی مراجعه کنید.

انواع واردات

این نوع تعریف شده یک رابط ، بسته بندی یا اتحادیه است ، می توانید آن را در جاوا وارد کنید:

import my.package.IFoo;

یا در پس زمینه CPP:

#include <my/package/IFoo.h>

یا در باطن NDK (به فضای aidl اضافی کمک کنید):

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

یا در پس زمینه زنگ زدگی:

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

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

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

برای اجرای یک رابط ، شما باید از کلاس خرد بومی به ارث ببرید. اجرای یک رابط اغلب در هنگام ثبت نام در مدیر سرویس یا android.app.ActivityManager به عنوان یک سرویس نامیده می شود و هنگام ثبت نام توسط مشتری یک سرویس ، یک پاسخ به تماس خوانده می شود. با این حال ، انواع مختلفی از اسامی برای توصیف پیاده سازی های رابط بسته به استفاده دقیق استفاده می شود. کلاس Stub دستورات درایور Binder را می خواند و روشهایی را که اجرا می کنید اجرا می کند. تصور کنید که شما یک پرونده AIDL مانند این دارید:

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

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

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

در Backend CPP:

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

در باطن NDK (به فضای aidl اضافی کمک کنید):

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

در پس زمینه زنگ زدگی:

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

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

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

یا با زنگ زدگی Async:

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

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

    impl Interface for MyFoo {}

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

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

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

در جاوا:

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

در Backend CPP:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

در باطن NDK (به فضای aidl اضافی کمک کنید):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

در پس زمینه زنگ زدگی:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

در پس زمینه زنگ زدگی Async ، با یک زمان اجرا تک رشته ای:

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 در موضوع اصلی انجام می شود.

در پس زمینه زنگ زدگی Async ، با یک زمان اجرا چند رشته ای:

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 Multithreaded ، کارهای تخم ریزی شده روی موضوع اصلی اجرا نمی شوند. بنابراین ، تماس با join_thread_pool در موضوع اصلی بیشتر است تا موضوع اصلی بیکار نباشد. شما باید تماس را در block_in_place بپیچید تا زمینه ASYNC را ترک کنید.

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

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

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

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

در پس زمینه جاوا:

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

در Backend CPP:

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

در باطن NDK:

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

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

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

گزارش های اشکال و اشکال زدایی API برای خدمات

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

برای افزودن خروجی سفارشی به سرویس خود ، مانند اجرای هر روش IPC دیگر که در یک فایل AIDL تعریف شده است ، روش dump را در شیء سرور خود نادیده بگیرید. هنگام انجام این کار ، دامپینگ را به مجوز برنامه android.permission.DUMP یا محدود کردن دامپینگ به UID های خاص محدود کنید.

در پس زمینه جاوا:

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

در Backend CPP:

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

در باطن NDK:

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

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

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

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

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

در حالی که جاوا WeakReference پشتیبانی می کند ، از منابع ضعیف اتصال در لایه بومی پشتیبانی نمی کند.

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

در Backend NDK ، از ScopedAIBinder_Weak استفاده کنید:

#include <android/binder_auto_utils.h>

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

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

در Backend CPP:

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

Backends NDK و Rust از این توانایی پشتیبانی نمی کنند.

از نظر آماری توصیف کننده رابط را دریافت کنید

بعضی اوقات (مانند هنگام ثبت نام خدمات @VintfStability ) ، باید بدانید که توصیف کننده رابط از نظر آماری چیست. در جاوا ، می توانید توصیف کننده را با اضافه کردن کد مانند:

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

در Backend CPP:

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

در باطن NDK (به فضای aidl اضافی کمک کنید):

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

در پس زمینه زنگ زدگی:

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

دامنه

در پس زمینه های بومی ، می توانید از مقادیر احتمالی که یک enum می تواند به دست آورد ، تکرار کنید. با توجه به ملاحظات اندازه کد ، این در جاوا پشتیبانی نمی شود.

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

در Backend CPP:

    ::android::enum_range<MyEnum>()

در باطن NDK:

   ::ndk::enum_range<MyEnum>()

در پس زمینه زنگ زدگی:

    MyEnum::enum_values()

مدیریت موضوع

هر نمونه از libbinder در یک فرآیند یک نخ را حفظ می کند. برای بیشتر موارد استفاده ، این دقیقاً باید یک موضوع باشد که در تمام پشتوانه ها به اشتراک گذاشته شده است. تنها استثنا این است که اگر کد فروشنده نسخه دیگری از libbinder را برای گفتگو با /dev/vndbinder بارگذاری کند. این روی یک گره اتصال جداگانه است ، بنابراین 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();

در پس زمینه زنگ زدگی:

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

با استفاده از پس زمینه زنگ زدگی Async ، به دو موضوع نیاز دارید: Binder و Tokio. این بدان معنی است که برنامه هایی با استفاده از زنگ زدگی Async به ملاحظات ویژه ای نیاز دارند ، به خصوص در مورد استفاده از join_thread_pool . برای اطلاعات بیشتر در مورد این بخش به بخش ثبت خدمات مراجعه کنید.

اسامی رزرو شده

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

ما توصیه می کنیم در صورت امکان از استفاده از نامهای رزرو شده در تعاریف AIDL خودداری کنید تا از اتصالات غیرقانونی یا خرابی کاملاً کامل استفاده کنید.

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

نام هایی برای جلوگیری از:

،

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

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

AIDL باکترهای زیر را دارد:

Backend زبان سطح API ساخت سیستم ها
جاوا جاوا SDK یا SystemApi (پایدار*) همه
NDK C++ libbinder_ndk (پایدار*) aidl_interface
CPP C++ libbinder (ناپایدار) همه
زنگ زدگی زنگ زدگی libbinder_rs (پایدار*) aidl_interface
  • این سطوح API پایدار هستند ، اما بسیاری از API ها ، مانند مواردی برای مدیریت خدمات ، برای استفاده از پلتفرم داخلی محفوظ هستند و در دسترس برنامه ها نیستند. برای کسب اطلاعات بیشتر در مورد نحوه استفاده از AIDL در برنامه ها ، به زبان تعریف رابط Android (AIDL) مراجعه کنید.
  • حمایت از زنگ زدگی در Android 12 معرفی شد. Backend NDK از Android 10 در دسترس است.
  • جعبه زنگ در بالای libbinder_ndk ساخته شده است که به آن اجازه می دهد تا پایدار و قابل حمل باشد. Apexes از جعبه اتصال به روش استاندارد در سمت سیستم استفاده می کند. قسمت زنگ زدگی در یک اوج قرار گرفته و درون آن حمل می شود. این بخش به libbinder_ndk.so در پارتیشن سیستم بستگی دارد.

ساخت سیستم ها

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

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

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

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

AIDL_INTERFACE

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

انواع

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

نوع جاوا یا AIDL نوع C ++ نوع NDK نوع زنگ زدگی
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 در: &str
خارج: String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> در: &[T]
خارج: Vec<T>
byte[] std::vector std::vector 1 در: &[u8]
بیرون: Vec<u8>
List<T> std::vector<T> 2 std::vector<T> 3 در: In: &[T] 4
خارج: Vec<T>
FileDescriptor android::base::unique_fd N/A N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
نوع رابط ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
نوع بسته ( T ) T T T
نوع اتحادیه ( T ) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

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

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

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

4. بسته به اینکه آیا ورودی (یک آرگومان) یا یک خروجی (یک مقدار برگشتی) هستند ، برای کد زنگ متفاوت است.

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

6. در اندروید 13 یا بالاتر ، آرایه های با اندازه ثابت پشتیبانی می شوند. آرایه های اندازه ثابت می توانند ابعاد مختلفی داشته باشند (برای مثال ، int[3][4] ). در پس زمینه جاوا ، آرایه های با اندازه ثابت به عنوان انواع آرایه نشان داده می شوند.

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

8. همچنین به Java یا Aidl Type byte[] مراجعه کنید.

جهت گیری (در ، خارج و inout)

هنگام مشخص کردن انواع آرگومان ها در توابع ، می توانید آنها را in ، out یا inout مشخص کنید. این مسیری را که اطلاعات برای تماس IPC منتقل می شود ، کنترل می کند.

  • مشخص کننده in نشان می دهد که داده ها از تماس گیرنده به کاللی منتقل می شوند. in مشخصات در جهت پیش فرض است ، اما اگر انواع داده ها نیز می توانند out شوند ، باید جهت را مشخص کنید.

  • مشخص کننده out به این معنی است که داده ها از کاللی به تماس گیرنده منتقل می شوند.

  • مشخصات آرگومان inout ترکیبی از هر دوی این موارد است. با این حال ، ما توصیه می کنیم از استفاده از مشخصات آرگومان inout جلوگیری کنید. اگر inout با یک رابط نسخه و یک Callee قدیمی تر استفاده می کنید ، زمینه های اضافی که فقط در تماس گیرنده وجود دارند ، به مقادیر پیش فرض خود بازنشانی می شوند. با توجه به زنگ زدگی ، یک نوع 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 هستند. رشته ها را به عنوان @utf8InCpp String در AIDL اعلام کنید تا به طور خودکار آنها را به UTF-8 تبدیل کنید. Backends NDK و Rust همیشه از رشته های UTF-8 استفاده می کنند. برای کسب اطلاعات بیشتر در مورد حاشیه نویسی utf8InCpp ، به UTF8INCPP مراجعه کنید.

باطل بودن

شما می توانید انواع مختلفی را که می توانند با @nullable تهی کنند ، حاشیه نویسی کنید. برای کسب اطلاعات بیشتر در مورد حاشیه نویسی nullable ، به Nullable مراجعه کنید.

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

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

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

    package my.pack.age;
    parcelable Foo;

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

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

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

اجرای زنگ زدگی در rust_crate/src/lib.rs به ​​نظر می رسد:

use binder::{
    binder_impl::{BorrowedParcel, UnstructuredParcelable},
    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
    StatusCode,
};

#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
    pub bar: String,
}

impl UnstructuredParcelable for Foo {
    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
        parcel.write(&self.bar)?;
        Ok(())
    }

    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
        let bar = parcel.read()?;
        Ok(Self { bar })
    }
}

impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);

سپس می توانید از این بسته بندی به عنوان یک نوع در پرونده های AIDL استفاده کنید ، اما توسط AIDL تولید نمی شود. برای استفاده از آنها در union اپراتورهای < و == را برای CPP و NDK Backend Custom Parcelables تهیه کنید.

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

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

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

در پس زمینه جاوا ، هنگامی که مقادیر پیش فرض از دست می رود ، زمینه ها به عنوان مقادیر صفر برای انواع ابتدایی و 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.setSringField("abc");                // setter

مثال C ++ و NDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

نمونه زنگ زدگی

در زنگ زدگی ، اتحادیه ها به عنوان enums اجرا می شوند و گیرنده ها و تنظیم کننده های صریح ندارند.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

رسیدگی به خطا

سیستم عامل Android انواع خطای داخلی را برای استفاده در هنگام گزارش خطاها فراهم می کند. اینها توسط Binders استفاده می شود و می تواند توسط هر خدمتی که رابط اتصال دهنده را اجرا می کند ، استفاده شود. استفاده از آنها به خوبی در تعریف AIDL ثبت شده است و به وضعیت تعریف شده توسط کاربر یا نوع بازگشت نیاز ندارند.

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

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

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

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

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

در جاوا ، خطاها به استثنائات ، مانند android.os.RemoteException نقشه می رود. برای استثنائات خاص خدمات ، جاوا از android.os.ServiceSpecificException به همراه خطای تعریف شده توسط کاربر استفاده می کند.

کد بومی در اندروید از استثنائات استفاده نمی کند. Backend CPP از android::binder::Status استفاده می کند. Backend NDK از ndk::ScopedAStatus استفاده می کند. هر روشی که توسط AIDL ایجاد شود ، یکی از این موارد را نشان می دهد و وضعیت روش را نشان می دهد. Backend Rust از همان مقادیر کد استثنائی به عنوان NDK استفاده می کند ، اما قبل از تحویل آنها به کاربر ، آنها را به خطاهای زنگ زدگی بومی ( StatusCode ، ExceptionCode ) تبدیل می کند. برای خطاهای خاص خدمات ، Status برگشتی یا ScopedAStatus از EX_SERVICE_SPECIFIC به همراه خطای تعریف شده توسط کاربر استفاده می کند.

انواع خطای داخلی را می توان در پرونده های زیر یافت:

Backend تعریف
جاوا 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 در الگوهای زنگ زدگی اندرویدی مراجعه کنید.

انواع واردات

این نوع تعریف شده یک رابط ، بسته بندی یا اتحادیه است ، می توانید آن را در جاوا وارد کنید:

import my.package.IFoo;

یا در پس زمینه CPP:

#include <my/package/IFoo.h>

یا در باطن NDK (به فضای aidl اضافی کمک کنید):

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

یا در پس زمینه زنگ زدگی:

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

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

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

برای اجرای یک رابط ، شما باید از کلاس خرد بومی به ارث ببرید. اجرای یک رابط اغلب در هنگام ثبت نام در مدیر سرویس یا android.app.ActivityManager به عنوان یک سرویس نامیده می شود و هنگام ثبت نام توسط مشتری یک سرویس ، یک پاسخ به تماس خوانده می شود. با این حال ، انواع مختلفی از اسامی برای توصیف پیاده سازی های رابط بسته به استفاده دقیق استفاده می شود. کلاس Stub دستورات درایور Binder را می خواند و روشهایی را که اجرا می کنید اجرا می کند. تصور کنید که شما یک پرونده AIDL مانند این دارید:

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

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

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

در Backend CPP:

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

در باطن NDK (به فضای aidl اضافی کمک کنید):

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

در پس زمینه زنگ زدگی:

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

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

    impl Interface for MyFoo {}

    impl IFoo for MyFoo {
        fn doFoo(&self) -> binder::Result<()> {
           ...
           Ok(())
        }
    }

یا با زنگ زدگی Async:

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

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

    impl Interface for MyFoo {}

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

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

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

در جاوا:

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

در Backend CPP:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // return if service is started now
    status_t err = checkService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

در باطن NDK (به فضای aidl اضافی کمک کنید):

    #include <android/binder_manager.h>
    // registering
    binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));

در پس زمینه زنگ زدگی:

use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

در پس زمینه زنگ زدگی Async ، با یک زمان اجرا تک رشته ای:

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 در موضوع اصلی انجام می شود.

در پس زمینه زنگ زدگی Async ، با یک زمان اجرا چند رشته ای:

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 Multithreaded ، کارهای تخم ریزی شده روی موضوع اصلی اجرا نمی شوند. بنابراین ، تماس با join_thread_pool در موضوع اصلی بیشتر است تا موضوع اصلی بیکار نباشد. شما باید تماس را در block_in_place بپیچید تا زمینه ASYNC را ترک کنید.

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

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

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

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

در پس زمینه جاوا:

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

در Backend CPP:

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

در باطن NDK:

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

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

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

گزارش های اشکال و اشکال زدایی API برای خدمات

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

برای افزودن خروجی سفارشی به سرویس خود ، مانند اجرای هر روش IPC دیگر که در یک فایل AIDL تعریف شده است ، روش dump را در شیء سرور خود نادیده بگیرید. هنگام انجام این کار ، دامپینگ را به مجوز برنامه android.permission.DUMP یا محدود کردن دامپینگ به UID های خاص محدود کنید.

در پس زمینه جاوا:

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

در Backend CPP:

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

در باطن NDK:

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

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

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

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

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

در حالی که جاوا WeakReference پشتیبانی می کند ، از منابع ضعیف اتصال در لایه بومی پشتیبانی نمی کند.

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

در Backend NDK ، از ScopedAIBinder_Weak استفاده کنید:

#include <android/binder_auto_utils.h>

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

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

در Backend CPP:

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

Backends NDK و Rust از این توانایی پشتیبانی نمی کنند.

از نظر آماری توصیف کننده رابط را دریافت کنید

بعضی اوقات (مانند هنگام ثبت نام خدمات @VintfStability ) ، باید بدانید که توصیف کننده رابط از نظر آماری چیست. در جاوا ، می توانید توصیف کننده را با اضافه کردن کد مانند:

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

در Backend CPP:

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

در باطن NDK (به فضای aidl اضافی کمک کنید):

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

در پس زمینه زنگ زدگی:

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

دامنه

در پس زمینه های بومی ، می توانید از مقادیر احتمالی که یک enum می تواند به دست آورد ، تکرار کنید. با توجه به ملاحظات اندازه کد ، این در جاوا پشتیبانی نمی شود.

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

در Backend CPP:

    ::android::enum_range<MyEnum>()

در باطن NDK:

   ::ndk::enum_range<MyEnum>()

در پس زمینه زنگ زدگی:

    MyEnum::enum_values()

مدیریت موضوع

هر نمونه از libbinder در یک فرآیند یک نخ را حفظ می کند. برای بیشتر موارد استفاده ، این دقیقاً باید یک موضوع باشد که در تمام پشتوانه ها به اشتراک گذاشته شده است. تنها استثنا این است که اگر کد فروشنده نسخه دیگری از libbinder را برای گفتگو با /dev/vndbinder بارگذاری کند. این روی یک گره اتصال جداگانه است ، بنابراین 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();

در پس زمینه زنگ زدگی:

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

با استفاده از پس زمینه زنگ زدگی Async ، به دو موضوع نیاز دارید: Binder و Tokio. این بدان معنی است که برنامه هایی با استفاده از زنگ زدگی Async به ملاحظات ویژه ای نیاز دارند ، به خصوص در مورد استفاده از join_thread_pool . برای اطلاعات بیشتر در مورد این بخش به بخش ثبت خدمات مراجعه کنید.

اسامی رزرو شده

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

ما توصیه می کنیم در صورت امکان از استفاده از نامهای رزرو شده در تعاریف AIDL خودداری کنید تا از اتصالات غیرقانونی یا خرابی کاملاً کامل استفاده کنید.

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

نام هایی برای جلوگیری از:

،

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

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

AIDL باکترهای زیر را دارد:

Backend زبان سطح API ساخت سیستم ها
جاوا جاوا SDK یا SystemApi (پایدار*) همه
NDK C++ libbinder_ndk (پایدار*) aidl_interface
CPP C++ libbinder (ناپایدار) همه
زنگ زدگی زنگ زدگی libbinder_rs (پایدار*) 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.

ساخت سیستم ها

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 N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> In: &[T]
Out: Vec<T>
byte[] std::vector std::vector 1 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 N/A N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
Interface type ( T ) android::sp<T> std::shared_ptr<T> 7 binder::Strong
Parcelable type ( T ) T T T
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[] .

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 .

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

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.

اتحادیه

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.setSringField("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)

نمونه زنگ زدگی

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

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

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.

در جاوا:

    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 .
  • 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.

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

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

مدیریت موضوع

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.

اسامی رزرو شده

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: