پشتیبان 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 --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 یا بالاتر، آرایه های بایت به دلایل سازگاری به جای int8_t از uint8_t استفاده می کنند.
2. باطن C++ از List<T>
پشتیبانی می کند که در آن T
یکی از String
، IBinder
، ParcelFileDescriptor
یا parcelable است. در Android T (AOSP تجربی) یا بالاتر، T
میتواند هر نوع غیر ابتدایی (از جمله انواع رابط) به جز آرایهها باشد. AOSP توصیه می کند که از انواع آرایه مانند T[]
استفاده کنید، زیرا آنها در همه backend ها کار می کنند.
3. Backend NDK از List<T>
پشتیبانی می کند که در آن T
یکی از String
، ParcelFileDescriptor
یا parcelable است. در Android T (AOSP تجربی) یا بالاتر، T
می تواند هر نوع غیر ابتدایی به جز آرایه ها باشد.
4. بسته به اینکه ورودی (یک آرگومان)، یا یک خروجی (مقدار برگشتی) باشند، انواع برای کد Rust به طور متفاوتی ارسال می شوند.
5. انواع Union در اندروید 12 و بالاتر پشتیبانی می شود.
6. در Android T (AOSP تجربی) یا بالاتر، آرایههای با اندازه ثابت پشتیبانی میشوند. آرایههای با اندازه ثابت میتوانند چند بعد داشته باشند (مثلا 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 مراجعه کنید.
پوچ پذیری
میتوانید انواعی را که میتوانند در باطن جاوا null باشند، با @nullable
کنید تا مقادیر null را در پشتوانههای CPP و NDK نشان دهید. در باطن Rust، این انواع @nullable
به صورت Option<T>
در معرض دید قرار می گیرند. سرورهای بومی مقادیر تهی را به طور پیش فرض رد می کنند. تنها استثناهای این مورد، انواع interface
و IBinder
هستند که همیشه میتوانند برای خواندن NDK و نوشتن CPP/NDK تهی باشند. برای اطلاعات بیشتر در مورد حاشیه نویسی nullable
، به یادداشت ها در AIDL مراجعه کنید.
بسته بندی های سفارشی
در پشتیبانهای C++ و جاوا در سیستم ساخت هسته، میتوانید یک بستهبندی را که به صورت دستی در یک باطن هدف (در C++ یا در جاوا) پیادهسازی میشود، اعلام کنید.
package my.package;
parcelable Foo;
یا با اعلان هدر C++:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
سپس می توانید از این بسته بندی به عنوان یک نوع در فایل های AIDL استفاده کنید، اما توسط AIDL تولید نمی شود.
Rust از بستهبندیهای سفارشی پشتیبانی نمیکند.
مقادیر پیش فرض
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 به مقادیر خطای اضافی نیاز داشته باشد که توسط انواع خطای داخلی پوشش داده نمی شود، ممکن است از خطای داخلی خاص سرویس استفاده کنند که اجازه می دهد مقدار خطای خاص سرویس را که توسط کاربر تعریف شده است درج کند. . این خطاهای خاص سرویس معمولاً در رابط 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).
خدمات پیاده سازی
برای پیاده سازی یک سرویس، باید از کلاس stub بومی ارث ببرید. این کلاس دستورات را از درایور بایندر می خواند و متدهایی را که شما پیاده سازی می کنید اجرا می کند. تصور کنید که یک فایل 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(())
}
}
ثبت نام و دریافت خدمات
خدمات در پلتفرم اندروید معمولاً با فرآیند servicemanager
ثبت می شوند. علاوه بر APIهای زیر، برخی از APIها سرویس را بررسی می کنند (به این معنی که اگر سرویس در دسترس نباشد فوراً برمی گردند). رابط servicemanager
مربوطه را برای جزئیات دقیق بررسی کنید. این عملیات فقط هنگام کامپایل در برابر پلتفرم اندروید قابل انجام است.
در جاوا:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// getting
myService = IFoo.Stub.asInterface(ServiceManager.getService("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);
// getting
status_t err = getService<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
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// getting
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("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(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()
}
پیوند دادن به مرگ
میتوانید درخواست دریافت اعلان برای زمانی که سرویسی که یک کلاسور را میزبانی میکند میمیرد، دریافت کنید. این می تواند به جلوگیری از افشای پراکسی های برگشت تماس یا کمک به بازیابی خطا کمک کند. این فراخوانی ها را روی اشیاء پراکسی بایندر انجام دهید.
- در جاوا از
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 اشکال زدایی برای خدمات
هنگامی که گزارشهای اشکال اجرا میشوند (مثلاً با 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_range()
مدیریت موضوع
هر نمونه از 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();