پشتیبان 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 خود رزرو کردهاید، میتوانید با خیال راحت نام فیلدها را تغییر دهید در حالی که با پروتکل سازگار هستند. ممکن است برای ادامه ساخت نیاز به به روز رسانی کد خود داشته باشید، اما هر برنامه ای که قبلا ساخته شده است به کار خود ادامه می دهد.
نام هایی که باید از آنها اجتناب کنید: