AIDL Backends

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

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

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

ساخت سیستم ها

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

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

در هر ماژول cc_ یا java_ Android.bp (یا در معادل های Android.mk آنها)، فایل های .aidl را می توان به عنوان فایل منبع مشخص کرد. در این مورد، از پشتیبان‌های Java/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 ساختار یافته با AIDL پایدار مطابقت دارد، به AIDL ساختاریافته در مقابل AIDL پایدار مراجعه کنید.

انواع

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

نوع جاوا/AIDL نوع C++ نوع NDK نوع زنگ
بولی بوول بوول بوول
بایت int8_t int8_t i8
کاراکتر char16_t char16_t u16
بین المللی int32_t int32_t i32
طولانی int64_t int64_t i64
شناور شناور شناور f32
دو برابر دو برابر دو برابر f64
رشته اندروید::String16 std::string رشته
android.os.Parcelable اندروید::قابل تقسیم N/A N/A
آی بایندر اندروید::IBinder ndk::SpAIBinder کلاسور::SpIBinder
T[] std::vector<T> std::vector<T> در: &[T]
خروجی: Vec<T>
بایت[] std::vector<uint8_t> std::vector<int8_t> 1 در: &[u8]
خروجی: Vec<u8>
لیست<T> std::vector<T> 2 std::vector<T> 3 در: &[T] 4
خروجی: Vec<T>
FileDescriptor android::base::unique_fd N/A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
نوع رابط (T) android::sp<T> std::shared_ptr<T> کلاسور::قوی
نوع قابل حمل (T) تی تی تی
نوع اتحادیه (T) 5 تی تی تی
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] ). در باطن جاوا، آرایه های با اندازه ثابت به عنوان انواع آرایه نمایش داده می شوند.

جهت دهی (داخل/خروج/خروج)

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

UTF8/UTF16

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

پوچ پذیری

می‌توانید انواعی را که می‌توانند در باطن جاوا تهی باشند با @nullable حاشیه‌نویسی کنید تا مقادیر null را در پشتیبان‌های CPP و NDK نشان دهید. در باطن Rust این انواع @nullable به صورت Option<T> در معرض دید قرار می گیرند. سرورهای بومی مقادیر تهی را به طور پیش فرض رد می کنند. تنها استثناهای این مورد، انواع interface و IBinder هستند که همیشه می‌توانند برای خواندن NDK و نوشتن CPP/NDK تهی باشند. برای اطلاعات بیشتر در مورد حاشیه نویسی nullable ، به یادداشت ها در AIDL مراجعه کنید.

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

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

برای اینکه بتوان یک بسته بندی سفارشی را اعلام کرد تا AIDL از آن مطلع شود، اعلان بسته بندی 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 (AOSP آزمایشی)، برای اعلام یک Rust قابل سفارشی در AIDL، از rust_type استفاده کنید:

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

پیاده سازی Rust در rust_crate/src/lib.rs به ​​شکل زیر است:

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

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

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

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

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

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

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

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

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

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

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

رسیدگی به خطا

سیستم عامل 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

استفاده از Backend های مختلف

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

انواع وارداتی

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

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> وارد کنید. <aidl/my/package/IFoo.h> برای باطن NDK).

خدمات پیاده سازی

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

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

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

    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 will 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 (یا Process ID) به شناسه فرآیند لینوکس فرآیندی که در حال ارسال تراکنش است، اشاره دارد. UID (یا User ID) به شناسه کاربری لینوکس اشاره دارد. هنگام دریافت تماس یک طرفه، 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<()>

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

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

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

    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 بارگیری کند. از آنجایی که این در یک گره binder جداگانه است، threadpool مشترک نیست.

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

    BinderInternal.setMaxThreads(<new larger value>);

برای باطن CPP، عملیات زیر در دسترس است:

    // set max threadpool count (default is 15)
    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
    // create threadpool
    ProcessState::self()->startThreadPool();
    // add current thread to threadpool (adds thread to max thread count)
    IPCThreadState::self()->joinThreadPool();

به طور مشابه، در باطن NDK:

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

در باطن Rust:

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

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

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

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

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

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

نام هایی که باید اجتناب کنید: * کلمات کلیدی C++ * کلمات کلیدی جاوا * کلمات کلیدی زنگ زده