یک بکاند AIDL هدفی برای تولید کد Stub است. همیشه از فایلهای AIDL به یک زبان خاص با زمان اجرای مشخص استفاده کنید. بسته به زمینه، باید از بکاندهای AIDL مختلف استفاده کنید.
در جدول زیر، پایداری سطح API به توانایی کامپایل کد در برابر این سطح API به گونهای اشاره دارد که کد بتواند مستقل از فایل باینری system.img libbinder.so تحویل داده شود.
AIDL دارای backend های زیر است:
| بکاند | زبان | سطح API | ساخت سیستمها |
|---|---|---|---|
| جاوا | جاوا | SDK یا SystemApi (پایدار*) | همه |
| ان دی کی | سی++ | libbinder_ndk (پایدار*) | aidl_interface |
| سی پی پی | سی++ | libbinder (ناپایدار) | همه |
| زنگ زدگی | زنگ زدگی | libbinder_rs (پایدار*) | aidl_interface |
- این سطوح API پایدار هستند، اما بسیاری از APIها، مانند APIهای مربوط به مدیریت سرویس، برای استفاده داخلی پلتفرم رزرو شدهاند و در دسترس برنامهها نیستند. برای اطلاعات بیشتر در مورد نحوه استفاده از AIDL در برنامهها، به زبان تعریف رابط اندروید (AIDL) مراجعه کنید.
- بکاند Rust در اندروید ۱۲ معرفی شد؛ بکاند NDK از اندروید ۱۰ در دسترس قرار گرفته است.
- جعبه Rust بر روی
libbinder_ndkساخته شده است که به آن امکان پایداری و قابلیت حمل میدهد. APEXها از جعبه binder به روش استاندارد در سمت سیستم استفاده میکنند. بخش Rust در یک APEX قرار گرفته و درون آن ارسال میشود. این بخش بهlibbinder_ndk.soدر پارتیشن سیستم بستگی دارد.
ساخت سیستمها
بسته به backend، دو روش برای کامپایل AIDL به کد stub وجود دارد. برای جزئیات بیشتر در مورد سیستمهای ساخت، به Soong Modules Reference مراجعه کنید.
سیستم ساخت هسته
در هر Android.bp module cc_ یا java_ Android.bp (یا در معادلهای Android.mk آنها)، میتوانید فایلهای AIDL ( .aidl ) را به عنوان فایلهای منبع مشخص کنید. در این حالت، از بکاندهای جاوا یا CPP مربوط به AIDL استفاده میشود (نه بکاند NDK) و کلاسهایی که باید از فایلهای AIDL مربوطه استفاده کنند، به طور خودکار به ماژول اضافه میشوند. میتوانید گزینههایی مانند local_include_dirs (که مسیر ریشه فایلهای AIDL در آن ماژول را به سیستم ساخت میگوید) را در این ماژولها تحت یک گروه aidl: مشخص کنید.
بخش پشتی Rust فقط برای استفاده با Rust است. ماژولهای rust_ به طور متفاوتی مدیریت میشوند، به این صورت که فایلهای AIDL به عنوان فایلهای منبع مشخص نمیشوند. در عوض، ماژول aidl_interface یک rustlib به نام aidl_interface_name -rust تولید میکند که میتواند به آن لینک شود. برای جزئیات بیشتر، به مثال Rust AIDL مراجعه کنید.
رابط کاربری aidl
انواع استفاده شده با سیستم ساخت aidl_interface باید ساختار یافته باشند. برای ساختارمند بودن، parcelableها باید مستقیماً شامل فیلدها باشند و نباید اعلان انواعی باشند که مستقیماً در زبانهای مقصد تعریف شدهاند. برای اینکه AIDL ساختار یافته چگونه با AIDL پایدار مطابقت دارد، به بخش AIDL ساختار یافته در مقابل AIDL پایدار مراجعه کنید.
انواع
کامپایلر aidl را به عنوان یک پیادهسازی مرجع برای انواع در نظر بگیرید. وقتی یک رابط ایجاد میکنید، aidl --lang=<backend> ... را برای دیدن فایل رابط حاصل فراخوانی کنید. وقتی از ماژول aidl_interface استفاده میکنید، میتوانید خروجی را در out/soong/.intermediates/ <path to module> / مشاهده کنید.
| نوع جاوا یا AIDL | نوع C++ | نوع NDK | نوع زنگ زدگی |
|---|---|---|---|
boolean | bool | bool | bool |
byte ۸ | int8_t | int8_t | i8 |
char | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | i64 |
float | float | float | f32 |
double | double | double | f64 |
String | android::String16 | std::string | در: &strخروجی: String |
android.os.Parcelable | android::Parcelable | ناموجود | ناموجود |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | در: &[T]خروجی: Vec<T> |
byte[] | std::vector | std::vector ۱ | در: &[u8]خروج: Vec<u8> |
List<T> | std::vector<T> 2 | std::vector<T> 3 | در: In: &[T] 4خروجی: Vec<T> |
FileDescriptor | android::base::unique_fd | ناموجود | ناموجود |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
نوع رابط ( T ) | android::sp<T> | std::shared_ptr<T> 7 | binder::Strong |
نوع بستهبندی ( T ) | T | T | T |
نوع اتحادیه ( T ) 5 | T | T | T |
T[N] ۶ | std::array<T, N> | std::array<T, N> | [T; N] |
۱. در اندروید ۱۲ یا بالاتر، آرایههای بایتی به دلایل سازگاری از uint8_t به جای int8_t استفاده میکنند.
۲. بکاند ++C از List<T> پشتیبانی میکند که در آن T یکی از String ، IBinder ، ParcelFileDescriptor یا parcelable است. در اندروید ۱۳ یا بالاتر، T میتواند هر نوع غیراولیه (از جمله انواع رابط) به جز آرایهها باشد. AOSP استفاده از انواع آرایهای مانند T[] را توصیه میکند، زیرا در همه بکاندها کار میکنند.
۳. بکاند NDK از List<T> پشتیبانی میکند که در آن T یکی از String ، ParcelFileDescriptor یا parcelable است. در اندروید ۱۳ یا بالاتر، T میتواند هر نوع داده غیراولیه به جز آرایهها باشد.
۴. نوعها بسته به اینکه ورودی (یک آرگومان) یا خروجی (یک مقدار برگردانده شده) باشند، برای کد Rust به طور متفاوتی ارسال میشوند.
۵. انواع Union در اندروید ۱۲ و بالاتر پشتیبانی میشوند.
۶. در اندروید ۱۳ یا بالاتر، آرایههای با اندازه ثابت پشتیبانی میشوند. آرایههای با اندازه ثابت میتوانند چندین بعد داشته باشند (برای مثال، int[3][4] ). در بکاند جاوا، آرایههای با اندازه ثابت به صورت انواع آرایه نمایش داده میشوند.
۷. برای نمونهسازی یک شیء SharedRefBase در binder، از SharedRefBase::make\<My\>(... args ...) استفاده کنید. این تابع یک شیء std::shared_ptr\<T\> ایجاد میکند که در صورتی که binder متعلق به فرآیند دیگری باشد، به صورت داخلی نیز مدیریت میشود. ایجاد شیء به روشهای دیگر باعث ایجاد مالکیت دوگانه میشود.
۸. همچنین به نوع byte[] در جاوا یا AIDL مراجعه کنید.
جهتگیری (ورودی، خروجی و ورودی-خروجی)
هنگام مشخص کردن انواع آرگومانها برای توابع، میتوانید آنها را به صورت in ، out یا inout مشخص کنید. این کار جهت انتقال اطلاعات برای فراخوانی IPC را کنترل میکند.
مشخصکنندهی آرگومان
inنشان میدهد که دادهها از فراخواننده به فراخوانیشونده منتقل میشوند. مشخصکنندهیinجهت پیشفرض است، اما اگر انواع داده میتوانندoutنیز باشند، باید جهت را مشخص کنید.مشخص کننده آرگومان
outبه این معنی است که داده از فراخوانی شونده به فراخوانی کننده منتقل میشود.مشخصکننده آرگومان
inoutترکیبی از هر دوی این موارد است. با این حال، توصیه میکنیم از استفاده از مشخصکننده آرگومانinoutخودداری کنید. اگرinoutبا یک رابط نسخهبندی شده و یک فراخوانیشده قدیمیتر استفاده کنید، فیلدهای اضافی که فقط در فراخوانیکننده وجود دارند، به مقادیر پیشفرض خود بازنشانی میشوند. در رابطه با Rust، یک نوعinoutمعمولی مقدار&mut Tو یک نوعinoutلیست مقدار&mut Vec<T>را دریافت میکند.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF-8 و UTF-16
با استفاده از بکاند CPP، میتوانید انتخاب کنید که رشتهها UTF-8 باشند یا UTF-16. رشتهها را در AIDL به صورت @utf8InCpp String تعریف کنید تا به طور خودکار به UTF-8 تبدیل شوند. بکاندهای NDK و Rust همیشه از رشتههای UTF-8 استفاده میکنند. برای اطلاعات بیشتر در مورد حاشیهنویسی utf8InCpp ، به utf8InCpp مراجعه کنید.
قابلیت ابطال
شما میتوانید انواعی را که میتوانند تهی باشند با @nullable حاشیهنویسی کنید. برای اطلاعات بیشتر در مورد حاشیهنویسی nullable ، به nullable مراجعه کنید.
بستههای سفارشی
یک parcelable سفارشی، parcelable ای است که به صورت دستی در backend هدف پیادهسازی شده است. فقط زمانی از parcelable های سفارشی استفاده کنید که میخواهید پشتیبانی از زبانهای دیگر را برای یک parcelable سفارشی موجود که قابل تغییر نیست، اضافه کنید.
در اینجا مثالی از یک اعلان بستهبندی AIDL آورده شده است:
package my.pack.age;
parcelable Foo;
به طور پیشفرض، این یک parcelable جاوا را تعریف میکند که در آن my.pack.age.Foo یک کلاس جاوا است که رابط Parcelable را پیادهسازی میکند.
برای اعلان یک بستهبندی backend سفارشی CPP در AIDL، cpp_header استفاده کنید:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
پیادهسازی C++ در my/pack/age/Foo.h به صورت زیر است:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
برای اعلان یک بستهبندی NDK سفارشی در AIDL، ndk_header استفاده کنید:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
پیادهسازی NDK در android/pack/age/Foo.h به این شکل است:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
در اندروید ۱۵، برای تعریف یک بسته سفارشی Rust در AIDL، rust_type استفاده کنید:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
پیادهسازی Rust در rust_crate/src/lib.rs به این شکل است:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
سپس میتوانید از این parcelable به عنوان یک نوع در فایلهای AIDL استفاده کنید، اما توسط AIDL تولید نخواهد شد. برای parcelableهای سفارشی backend CPP و NDK، عملگرهای < و == را ارائه دهید تا از آنها در union استفاده کنید.
مقادیر پیشفرض
parcelableهای ساختاریافته میتوانند مقادیر پیشفرض به ازای هر فیلد را برای مقادیر اولیه، فیلدهای String و آرایههایی از این نوعها تعریف کنند.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
در بکاند جاوا، وقتی مقادیر پیشفرض وجود نداشته باشند، فیلدها برای انواع اولیه با مقادیر صفر و برای انواع غیراولیه null مقداردهی اولیه میشوند.
در سایر backendها، فیلدها با مقادیر پیشفرض مقداردهی اولیه میشوند، زمانی که مقادیر پیشفرض تعریف نشده باشند. برای مثال، در backend سیپلاسپلاس، فیلدهای String به صورت یک رشته خالی و فیلدهای List<T> به صورت یک vector<T> خالی مقداردهی اولیه میشوند. فیلدهای @nullable به صورت فیلدهای null-value مقداردهی اولیه میشوند.
اتحادیهها
اتحادیههای AIDL برچسبگذاری شدهاند و ویژگیهای آنها در همه backendها مشابه است. آنها با مقدار پیشفرض اولین فیلد ساخته میشوند و روشی مختص به زبان برای تعامل با آنها وجود دارد:
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
مثال جاوا
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setStringField("abc"); // setter
مثال ++C و NDK
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
مثال زنگ زدگی
در زبان Rust، یونیونها به صورت enum پیادهسازی میشوند و getter و setter صریح ندارند.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
مدیریت خطا
سیستم عامل اندروید انواع خطای داخلی را برای سرویسها فراهم میکند تا هنگام گزارش خطاها از آنها استفاده کنند. این نوع خطاها توسط binderها استفاده میشوند و میتوانند توسط هر سرویسی که رابط binder را پیادهسازی میکند، مورد استفاده قرار گیرند. استفاده از آنها به خوبی در تعریف AIDL مستند شده است و نیازی به هیچ وضعیت یا نوع بازگشتی تعریف شده توسط کاربر ندارند.
پارامترهای خروجی با خطا
وقتی یک تابع AIDL خطایی را گزارش میدهد، ممکن است تابع پارامترهای خروجی را مقداردهی اولیه یا تغییر ندهد. به طور خاص، اگر خطا در حین unparceling رخ دهد، ممکن است پارامترهای خروجی تغییر کنند، نه اینکه در حین پردازش خود تراکنش رخ دهد. به طور کلی، هنگام دریافت خطا از یک تابع AIDL، تمام پارامترهای inout و out و همچنین مقدار برگشتی (که در برخی از backendها مانند پارامتر out عمل میکند) باید در حالت نامشخص در نظر گرفته شوند.
از کدام مقادیر خطا استفاده کنیم
بسیاری از مقادیر خطای داخلی میتوانند در هر رابط AIDL استفاده شوند، اما برخی از آنها به روش خاصی مورد بررسی قرار میگیرند. به عنوان مثال، EX_UNSUPPORTED_OPERATION و EX_ILLEGAL_ARGUMENT برای توصیف شرایط خطا مناسب هستند، اما EX_TRANSACTION_FAILED نباید استفاده شود زیرا به طور خاص توسط زیرساختهای زیربنایی مورد بررسی قرار میگیرد. برای اطلاعات بیشتر در مورد این مقادیر داخلی، تعاریف خاص backend را بررسی کنید.
اگر رابط AIDL به مقادیر خطای اضافی نیاز داشته باشد که توسط انواع خطای داخلی پوشش داده نمیشوند، میتوانند از خطای داخلی ویژه مختص سرویس استفاده کنند که امکان درج مقدار خطای مختص سرویس را که توسط کاربر تعریف شده است، فراهم میکند. این خطاهای مختص سرویس معمولاً در رابط AIDL به عنوان یک const int یا int -backed enum تعریف میشوند و توسط binder تجزیه نمیشوند.
در جاوا، خطاها به استثنائاتی مانند android.os.RemoteException نگاشت میشوند. برای استثنائات مربوط به سرویس، جاوا android.os.ServiceSpecificException به همراه خطای تعریف شده توسط کاربر استفاده میکند.
کد بومی در اندروید از استثنائات استفاده نمیکند. بکاند CPP android::binder::Status استفاده میکند. بکاند NDK از ndk::ScopedAStatus استفاده میکند. هر متدی که توسط AIDL تولید میشود، یکی از این موارد را برمیگرداند که نشاندهنده وضعیت متد است. بکاند Rust از همان مقادیر کد استثنای NDK استفاده میکند، اما قبل از تحویل آنها به کاربر، آنها را به خطاهای بومی Rust ( StatusCode ، ExceptionCode ) تبدیل میکند. برای خطاهای خاص سرویس، Status یا ScopedAStatus برگشتی از EX_SERVICE_SPECIFIC به همراه خطای تعریف شده توسط کاربر استفاده میکند.
انواع خطاهای داخلی را میتوان در فایلهای زیر یافت:
| بکاند | تعریف |
|---|---|
| جاوا | android/os/Parcel.java |
| سی پی پی | binder/Status.h |
| ان دی کی | android/binder_status.h |
| زنگ زدگی | android/binder_status.h |
از بکاندهای مختلف استفاده کنید
این دستورالعملها مختص کد پلتفرم اندروید هستند. این مثالها از یک نوع تعریفشده، my.package.IFoo ، استفاده میکنند. برای دستورالعمل نحوه استفاده از backend در Rust، به مثال Rust AIDL در Android Rust patterns مراجعه کنید.
انواع واردات
چه نوع تعریفشده یک رابط، parcelable یا union باشد، میتوانید آن را در جاوا وارد کنید:
import my.package.IFoo;
یا در بکاند CPP:
#include <my/package/IFoo.h>
یا در بکاند NDK (به فضای نام اضافی aidl توجه کنید):
#include <aidl/my/package/IFoo.h>
یا در بکاند Rust:
use my_package::aidl::my::package::IFoo;
اگرچه میتوانید یک نوع داده تودرتو را در جاوا وارد کنید، اما در بکاندهای CPP و NDK باید سرآیند مربوط به نوع ریشه آن را وارد کنید. برای مثال، هنگام وارد کردن یک نوع داده تودرتو Bar که در my/package/IFoo.aidl تعریف شده است ( IFoo نوع ریشه فایل است)، باید <my/package/IFoo.h> را برای بکاند CPP (یا <aidl/my/package/IFoo.h> را برای بکاند NDK) وارد کنید.
پیادهسازی یک رابط
برای پیادهسازی یک رابط، باید از کلاس stub بومی ارثبری کنید. پیادهسازی یک رابط اغلب وقتی در service manager یا android.app.ActivityManager ثبت میشود، سرویس نامیده میشود و وقتی توسط کلاینت یک سرویس ثبت میشود، callback نامیده میشود. با این حال، بسته به کاربرد دقیق، از نامهای مختلفی برای توصیف پیادهسازیهای رابط استفاده میشود. کلاس stub دستورات را از درایور binder میخواند و متدهایی را که شما پیادهسازی میکنید، اجرا میکند. تصور کنید که یک فایل AIDL مانند این دارید:
package my.package;
interface IFoo {
int doFoo();
}
در جاوا، شما باید از کلاس Stub تولید شده ارث بری کنید:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
در پشت صحنه CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
در بکاند NDK (به فضای نام اضافی aidl توجه کنید):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
در بکاند Rust:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
impl IFoo for MyFoo {
fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
یا با ناهمگامسازی Rust:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
ثبت نام و دریافت خدمات
سرویسها در پلتفرم اندروید معمولاً در فرآیند servicemanager ثبت میشوند. علاوه بر APIهای زیر، برخی از APIها سرویس را بررسی میکنند (به این معنی که اگر سرویس در دسترس نباشد، فوراً برمیگردند). برای جزئیات دقیق، رابط servicemanager مربوطه را بررسی کنید. شما میتوانید این عملیات را فقط هنگام کامپایل در پلتفرم اندروید انجام دهید.
در جاوا:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// return if service is started now
myService = IFoo.Stub.asInterface(ServiceManager.checkService("service-name"));
// waiting until service comes up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));
در پشت صحنه CPP:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// return if service is started now
status_t err = checkService<IFoo>(String16("service-name"), &myService);
// waiting until service comes up (new in Android 11)
myService = waitForService<IFoo>(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = waitForDeclaredService<IFoo>(String16("service-name"));
در بکاند NDK (به فضای نام اضافی aidl توجه کنید):
#include <android/binder_manager.h>
// registering
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("service-name")));
// is a service declared in the VINTF manifest
// VINTF services have the type in the interface instance name.
bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
// wait until a service is available (if isDeclared or you know it's available)
myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService("service-name")));
در بکاند Rust:
use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Does not return - spawn or perform any work you mean to do before this call.
binder::ProcessState::join_thread_pool()
}
در backend ناهمزمان Rust، با یک runtime تک رشتهای:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks run on this thread.
std::future::pending().await
}
یک تفاوت مهم با گزینههای دیگر این است که هنگام استفاده از async Rust و یک محیط اجرای تکرشتهای، join_thread_pool را فراخوانی نمیکنید . دلیل این امر این است که شما باید به Tokio یک نخ بدهید که بتواند وظایف ایجاد شده را در آن اجرا کند. در مثال زیر، نخ اصلی این هدف را برآورده میکند. هر وظیفهای که با استفاده از tokio::spawn ایجاد شود، روی نخ اصلی اجرا میشود.
در backend ناهمزمان Rust، با یک زمان اجرای چند رشتهای:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
با زمان اجرای چند رشتهای Tokio، وظایف ایجاد شده روی نخ اصلی اجرا نمیشوند. بنابراین، منطقیتر است که تابع join_thread_pool روی نخ اصلی فراخوانی کنید تا نخ اصلی بیکار نماند. برای اینکه زمینه ناهمزمان باقی بماند، باید فراخوانی را در block_in_place قرار دهید.
پیوند با مرگ
میتوانید درخواست کنید تا در صورت از کار افتادن سرویسی که میزبان یک binder است، اعلانی دریافت کنید. این کار میتواند به جلوگیری از نشت پروکسیهای فراخوانی یا کمک به بازیابی خطا کمک کند. این فراخوانیها را روی اشیاء پروکسی binder انجام دهید.
- در جاوا
android.os.IBinder::linkToDeathاستفاده کنید. - در بخش مدیریت CPP،
android::IBinder::linkToDeathاستفاده کنید. - در backend NDK، از
AIBinder_linkToDeathاستفاده کنید. همیشهAIBinder_DeathRecipient_setOnUnlinkedبرای کنترل طول عمر کوکی گیرنده مرگ خود استفاده کنید. - در backend زبان Rust، یک شیء
DeathRecipientایجاد کنید، سپسmy_binder.link_to_death(&mut my_death_recipient)را فراخوانی کنید. توجه داشته باشید که از آنجایی کهDeathRecipientمالک callback است، باید آن شیء را تا زمانی که میخواهید اعلانها را دریافت کنید، فعال نگه دارید.
اطلاعات تماسگیرنده
هنگام دریافت یک فراخوانی binder هسته، اطلاعات فراخوانیکننده در چندین API موجود است. شناسه فرآیند (PID) به شناسه فرآیند لینوکس فرآیندی که تراکنش را ارسال میکند اشاره دارد. شناسه کاربر (UI) به شناسه کاربر لینوکس اشاره دارد. هنگام دریافت یک فراخوانی یک طرفه، PID فراخوانیکننده 0 است. خارج از زمینه تراکنش binder، این توابع PID و UID فرآیند فعلی را برمیگردانند.
در بکاند جاوا:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
در پشت صحنه CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
در بکاند NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
در backend مربوط به Rust، هنگام پیادهسازی رابط کاربری، موارد زیر را مشخص کنید (به جای اینکه آن را به صورت پیشفرض تنظیم کنید):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
گزارش اشکال و اشکالزدایی API برای سرویسها
وقتی گزارشهای باگ اجرا میشوند (برای مثال، با adb bugreport )، اطلاعات را از سراسر سیستم جمعآوری میکنند تا به اشکالزدایی مشکلات مختلف کمک کنند. برای سرویسهای AIDL، گزارشهای باگ از dumpsys باینری روی تمام سرویسهای ثبتشده در service manager برای ریختن اطلاعات خود در گزارش باگ استفاده میکنند. همچنین میتوانید از dumpsys در خط فرمان برای دریافت اطلاعات از یک سرویس با dumpsys SERVICE [ARGS] استفاده کنید. در بکاندهای C++ و Java، میتوانید ترتیب تخلیه سرویسها را با استفاده از آرگومانهای اضافی برای addService کنترل کنید. همچنین میتوانید از dumpsys --pid SERVICE برای دریافت PID یک سرویس هنگام اشکالزدایی استفاده کنید.
برای افزودن خروجی سفارشی به سرویس خود، متد dump را در شیء سرور خود مانند هر متد IPC دیگری که در یک فایل AIDL تعریف شده است، بازنویسی کنید. هنگام انجام این کار، dumping را به مجوز برنامه android.permission.DUMP یا dumping را به UID های خاص محدود کنید.
در بکاند جاوا:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
در پشت صحنه CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
در بکاند NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
در backend مربوط به Rust، هنگام پیادهسازی رابط کاربری، موارد زیر را مشخص کنید (به جای اینکه آن را به صورت پیشفرض تنظیم کنید):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
از نکات ضعف استفاده کنید
شما میتوانید یک ارجاع ضعیف به یک شیء binder داشته باشید.
اگرچه جاوا از WeakReference پشتیبانی میکند، اما از ارجاعات ضعیف binder در لایه native پشتیبانی نمیکند.
در بکاند CPP، نوع ضعیف wp<IFoo> است.
در backend NDK، ScopedAIBinder_Weak استفاده کنید:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
در پسزمینه Rust، WpIBinder یا Weak<IFoo> استفاده کنید:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
دریافت پویای توصیفگر رابط
توصیفگر رابط، نوع رابط را مشخص میکند. این مورد هنگام اشکالزدایی یا زمانی که یک binder ناشناخته دارید، مفید است.
در جاوا، میتوانید توصیفگر رابط را با کدی مانند زیر دریافت کنید:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
در پشت صحنه CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
بکاندهای NDK و Rust از این قابلیت پشتیبانی نمیکنند.
دریافت ایستا از توصیفگر رابط
گاهی اوقات (مانند هنگام ثبت سرویسهای @VintfStability )، باید بدانید که توصیفگر رابط به صورت ایستا چیست. در جاوا، میتوانید توصیفگر را با اضافه کردن کدی مانند زیر دریافت کنید:
import my.package.IFoo;
... IFoo.DESCRIPTOR
در پشت صحنه CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
در بکاند NDK (به فضای نام اضافی aidl توجه کنید):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
در بکاند Rust:
aidl::my::package::BnFoo::get_descriptor()
محدوده شمارشی
در بکاندهای نیتیو، میتوانید روی مقادیر ممکنی که یک enum میتواند بپذیرد، عملیات تکرار (iterate) انجام دهید. به دلیل ملاحظات مربوط به حجم کد، این قابلیت در جاوا پشتیبانی نمیشود.
برای یک Enum MyEnum که در AIDL تعریف شده است، تکرار به صورت زیر ارائه میشود.
در پشت صحنه CPP:
::android::enum_range<MyEnum>()
در بکاند NDK:
::ndk::enum_range<MyEnum>()
در بکاند Rust:
MyEnum::enum_values()
مدیریت نخ
هر نمونه از libbinder در یک فرآیند، یک threadpool را نگهداری میکند. در بیشتر موارد استفاده، این باید دقیقاً یک threadpool باشد که در تمام backendها به اشتراک گذاشته میشود. تنها استثنا زمانی است که کد فروشنده، یک کپی دیگر از libbinder برای ارتباط با /dev/vndbinder بارگذاری کند. این در یک گره binder جداگانه است، بنابراین threadpool به اشتراک گذاشته نمیشود.
برای بکاند جاوا، اندازهی threadpool فقط میتواند افزایش یابد (زیرا از قبل شروع شده است):
BinderInternal.setMaxThreads(<new larger value>);
برای پشت صحنه CPP، عملیات زیر در دسترس هستند:
// set max threadpool count (default is 15)
status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
// create threadpool
ProcessState::self()->startThreadPool();
// add current thread to threadpool (adds thread to max thread count)
IPCThreadState::self()->joinThreadPool();
به طور مشابه، در بکاند NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
در بکاند Rust:
binder::ProcessState::start_thread_pool();
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
binder::ProcessState::join_thread_pool();
با استفاده از backend ناهمزمان Rust، به دو threadpool نیاز دارید: binder و Tokio. این بدان معناست که برنامههایی که از async Rust استفاده میکنند، به ملاحظات خاصی نیاز دارند، به خصوص در مورد استفاده از join_thread_pool . برای اطلاعات بیشتر در این مورد به بخش ثبت سرویسها مراجعه کنید.
نامهای رزرو شده
سیپلاسپلاس، جاوا و راست برخی از نامها را به عنوان کلمات کلیدی یا برای استفاده خاص زبان رزرو میکنند. در حالی که AIDL محدودیتهایی را بر اساس قوانین زبان اعمال نمیکند، استفاده از نامهای فیلد یا نوع که با یک نام رزرو شده مطابقت دارند، میتواند منجر به شکست کامپایل برای سیپلاسپلاس یا جاوا شود. برای راست، فیلد یا نوع با استفاده از سینتکس شناسه خام تغییر نام داده میشود که با استفاده از پیشوند r# قابل دسترسی است.
توصیه میکنیم تا حد امکان از استفاده از نامهای رزرو شده در تعاریف AIDL خودداری کنید تا از اتصالات غیر ارگونومیک یا شکست کامل کامپایل جلوگیری شود.
اگر از قبل نامهای رزرو شدهای در تعاریف AIDL خود دارید، میتوانید با خیال راحت فیلدها را تغییر نام دهید و در عین حال با پروتکل سازگار بمانید. ممکن است لازم باشد کد خود را برای ادامه ساخت بهروزرسانی کنید، اما هر برنامهای که از قبل ساخته شده است، همچنان به همکاری خود ادامه میدهد.
نامهایی که باید از آنها اجتناب کرد: