Phần phụ trợ AIDL

Phần phụ trợ AIDL là một mục tiêu để tạo mã mã giả lập. Khi dùng tệp AIDL, bạn luôn dùng các tệp đó bằng một ngôn ngữ cụ thể trong thời gian chạy cụ thể. Tuỳ thuộc vào ngữ cảnh, bạn nên sử dụng các phần phụ trợ AIDL khác nhau.

Trong bảng sau, độ ổn định của nền tảng API đề cập đến khả năng biên dịch mã dựa trên nền tảng API này theo cách có thể phân phối mã độc lập từ tệp nhị phân system.img libbinder.so.

AIDL có các phần phụ trợ sau:

Phần phụ trợ Ngôn ngữ Nền tảng API Hệ thống xây dựng
Java Java SDK/SystemApi (ổn định*) tất cả
NDK C++ libbinder_ndk (ổn định*) giao diện aidl
CPP C++ libbinder (không ổn định) tất cả
Rust Rust libbinder_rs (ổn định*) giao diện aidl
  • Các nền tảng API này là ổn định, nhưng nhiều API, chẳng hạn như các API để quản lý dịch vụ, được dành riêng để sử dụng nền tảng nội bộ và không được cung cấp cho ứng dụng. Để biết thêm thông tin về cách sử dụng AIDL trong các ứng dụng, hãy xem tài liệu dành cho nhà phát triển.
  • Phần phụ trợ Rust đã ra mắt trong Android 12; phần phụ trợ NDK đã có kể từ Android 10.
  • Thùng rác được xây dựng dựa trên libbinder_ndk, cho phép nó ổn định và di động. Các APEX sử dụng ngăn liên kết giống như bất kỳ ai khác ở phía hệ thống. Phần Rust được nhóm vào một APEX và vận chuyển vào bên trong đó. Điều này phụ thuộc vào libbinder_ndk.so trên phân vùng hệ thống.

Hệ thống xây dựng

Tuỳ thuộc vào phần phụ trợ, có 2 cách để biên dịch AIDL thành mã giả lập. Để biết thêm thông tin chi tiết về các hệ thống xây dựng, hãy xem Tài liệu tham khảo về mô-đun Soong.

Hệ thống xây dựng cốt lõi

Trong bất kỳ mô-đun Android.bp cc_ hoặc java_ nào (hoặc trong các mô-đun tương đương Android.mk của chúng), bạn có thể chỉ định tệp .aidl làm tệp nguồn. Trong trường hợp này, hệ thống phụ trợ Java/CPP của AIDL được sử dụng (không phải phần phụ trợ NDK) và các lớp để sử dụng tệp AIDL tương ứng sẽ tự động được thêm vào mô-đun. Các tuỳ chọn như local_include_dirs cho hệ thống xây dựng biết đường dẫn gốc đến các tệp AIDL trong mô-đun đó có thể được chỉ định trong các mô-đun này thuộc nhóm aidl:. Xin lưu ý rằng phần phụ trợ của Rust chỉ dùng được với Rust. Các mô-đun rust_ được xử lý theo cách khác, trong đó tệp AIDL không được chỉ định là tệp nguồn. Thay vào đó, mô-đun aidl_interface tạo ra một rustlib có tên là <aidl_interface name>-rust. Mô-đun này có thể được liên kết dựa vào đó. Để biết thêm thông tin chi tiết, hãy xem ví dụ về Raust AIDL.

giao diện aidl

Các loại được sử dụng với hệ thống xây dựng này phải được cấu trúc. Để được cấu trúc, gói phải chứa các trường trực tiếp và không được khai báo các kiểu được xác định trực tiếp bằng ngôn ngữ đích. Để biết AIDL có cấu trúc phù hợp với AIDL ổn định, hãy xem bài viết AIDL có cấu trúc và ổn định.

Loại

Bạn có thể coi trình biên dịch aidl là phương thức triển khai tham chiếu cho các kiểu. Khi bạn tạo một giao diện, hãy gọi aidl --lang=<backend> ... để xem tệp giao diện thu được. Khi sử dụng mô-đun aidl_interface, bạn có thể xem kết quả trong out/soong/.intermediates/<path to module>/.

Loại Java/AIDL Loại C++ Loại NDK Loại gỉ
boolean bool bool bool
byte int8_t int8_t i8
ký tự ký tự 16_t ký tự 16_t u16
int int32_t int32_t i32
long int64_t int64_t i64
số thực dấu phẩy động số thực dấu phẩy động số thực dấu phẩy động F32
gấp đôi gấp đôi gấp đôi F64
Chuỗi android::String16 std::chuỗi Chuỗi
android.os.Theo gói android::Theo gói Không áp dụng Không áp dụng
IBinder android::IBinder ndk::SpAIBinder liên kết::SpIBinder
Đ[] std::vector<T> std::vector<T> Trong: &[T]
Ra: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 Trong: &[u8]
Ra: Vec<u8>
Danh sách<T> std::vector<T>2 std::vector<T>3 Trong: &[T]4
Ra: Vec<T>
Mô tả tệp android::base::: duy nhất_fd Không áp dụng liên kết::Parce::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor liên kết::Parce::ParcelFileDescriptor
loại giao diện (T) android::sp<T> std::shared_ptr<T>7 liên kết::Mạnh
loại theo gói (T) T5 T5 T5
loại liên kết (T)5 T5 T5 T5
T[N] 6 std::array<T, N> std::array<T, N> [T; B]

1. Trên Android 12 trở lên, vì lý do tương thích, các mảng byte sử dụng uint8_t thay vì int8_t.

2. Phần phụ trợ C++ hỗ trợ List<T>, trong đó T là một trong các giá trị String, IBinder, ParcelFileDescriptor hoặc có thể đóng gói. Trong Android 13 trở lên, T có thể là bất kỳ kiểu dữ liệu gốc nào (bao gồm cả kiểu giao diện), ngoại trừ mảng. AOSP đề xuất bạn sử dụng các loại mảng như T[] vì các kiểu này hoạt động trong mọi phần phụ trợ.

3. Phần phụ trợ NDK hỗ trợ List<T>, trong đó T là một trong các giá trị String, ParcelFileDescriptor hoặc theo gói. Trong Android 13 trở lên, T có thể là bất kỳ kiểu dữ liệu gốc nào, ngoại trừ mảng.

4. Các kiểu được truyền theo cách khác nhau cho mã Rust, tuỳ thuộc vào việc đó là dữ liệu đầu vào (đối số) hay đầu ra (giá trị được trả về).

5. Các loại liên kết được hỗ trợ trong Android 12 trở lên.

6. Trong Android 13 trở lên, chúng tôi hỗ trợ các mảng có kích thước cố định. Mảng có kích thước cố định có thể có nhiều chiều (ví dụ: int[3][4]). Trong phần phụ trợ Java, các mảng có kích thước cố định được biểu thị dưới dạng loại mảng.

7. Để tạo thực thể cho đối tượng SharedRefBase liên kết, hãy sử dụng SharedRefBase::make\<My\>(... args ...). Hàm này tạo một đối tượng std::shared_ptr\<T\> cũng được quản lý nội bộ, trong trường hợp liên kết thuộc sở hữu của một quy trình khác. Việc tạo đối tượng theo cách khác sẽ dẫn đến tình trạng sở hữu hai lần.

Hướng (vào/ra/ra)

Khi chỉ định loại đối số cho hàm, bạn có thể chỉ định các đối số đó là in, out hoặc inout. Chính sách này kiểm soát việc truyền thông tin về hướng nào cho lệnh gọi IPC. in là hướng mặc định và cho biết dữ liệu được truyền từ phương thức gọi đến phương thức gọi. out có nghĩa là dữ liệu được truyền từ phương thức gọi đến phương thức gọi. inout là sự kết hợp của cả hai. Tuy nhiên, nhóm Android khuyên bạn nên tránh sử dụng công cụ xác định đối số inout. Nếu bạn sử dụng inout với giao diện được tạo phiên bản và một hàm được gọi cũ hơn, thì các trường bổ sung chỉ có trong phương thức gọi sẽ được đặt lại về giá trị mặc định. Đối với Rust, loại inout thông thường sẽ nhận được &mut Vec<T> và loại inout của danh sách sẽ nhận được &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);
}

UTF8/UTF16

Với phần phụ trợ CPP, bạn có thể chọn chuỗi là utf-8 hay utf-16. Khai báo các chuỗi dưới dạng @utf8InCpp String trong AIDL để tự động chuyển đổi các chuỗi đó thành utf-8. Các phần phụ trợ của NDK và Rust luôn sử dụng các chuỗi utf-8. Để biết thêm thông tin về chú thích utf8InCpp, hãy xem phần Chú thích trong AIDL.

Tính chất rỗng

Bạn có thể chú thích các loại có thể rỗng trong phần phụ trợ Java bằng @nullable để hiển thị giá trị rỗng cho các phần phụ trợ CPP và NDK. Trong phần phụ trợ của Rust, các loại @nullable này được biểu thị dưới dạng Option<T>. Theo mặc định, máy chủ gốc từ chối giá trị rỗng. Ngoại lệ duy nhất là các kiểu interfaceIBinder. Hai loại này luôn có thể rỗng đối với hoạt động đọc NDK và ghi CPP/NDK. Để biết thêm thông tin về chú thích nullable, hãy xem bài viết Chú thích trong AIDL.

Gói hàng tuỳ chỉnh

Gói tuỳ chỉnh là một gói được triển khai theo cách thủ công trong phần phụ trợ mục tiêu. Chỉ sử dụng các gói có thể tuỳ chỉnh khi bạn đang cố gắng thêm tính năng hỗ trợ vào các ngôn ngữ khác cho một gói tuỳ chỉnh hiện có mà không thay đổi được.

Để khai báo một gói theo gói tuỳ chỉnh sao cho AIDL biết về gói đó, nội dung khai báo theo gói AIDL sẽ có dạng như sau:

    package my.pack.age;
    parcelable Foo;

Theo mặc định, thao tác này sẽ khai báo một gói Java, trong đó my.pack.age.Foo là một lớp Java triển khai giao diện Parcelable.

Để khai báo phần phụ trợ CPP tuỳ chỉnh theo gói trong AIDL, hãy sử dụng cpp_header:

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

Quy trình triển khai C++ trong my/pack/age/Foo.h sẽ có dạng như sau:

    #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);
    };

Để khai báo về một gói NDK tuỳ chỉnh trong AIDL, hãy sử dụng ndk_header:

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

Quy trình triển khai NDK trong android/pack/age/Foo.h sẽ có dạng như sau:

    #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);
    };

Trong Android 15 (thử nghiệm AOSP), để khai báo một gói Rust tuỳ chỉnh trong AIDL, hãy sử dụng rust_type:

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

Cách triển khai Rust trong rust_crate/src/lib.rs sẽ có dạng như sau:

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

Sau đó, bạn có thể sử dụng gói có thể đóng gói này làm một loại trong tệp AIDL, nhưng AIDL sẽ không tạo nó. Cung cấp các toán tử <== cho các gói tuỳ chỉnh phụ trợ CPP/NDK để sử dụng chúng trong union.

Giá trị mặc định

Các gói có cấu trúc có thể khai báo giá trị mặc định theo từng trường cho dữ liệu gốc, String và mảng thuộc các loại này.

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

Trong phần phụ trợ Java khi thiếu giá trị mặc định, các trường sẽ được khởi chạy dưới dạng giá trị 0 cho các loại nguyên gốc và null cho các loại không phải là nguyên gốc.

Trong các phần phụ trợ khác, các trường được khởi tạo bằng giá trị khởi động mặc định khi giá trị mặc định không được xác định. Ví dụ: trong phần phụ trợ C++, các trường String được khởi động dưới dạng một chuỗi trống và các trường List<T> được khởi động dưới dạng vector<T> trống. Các trường @nullable được khởi tạo dưới dạng trường có giá trị rỗng.

Xử lý lỗi

Hệ điều hành Android cung cấp các loại lỗi tích hợp sẵn để các dịch vụ sử dụng khi báo cáo lỗi. Các API này được trình liên kết sử dụng và có thể dùng cho bất kỳ dịch vụ nào triển khai giao diện liên kết. Việc sử dụng chúng được ghi nhận rõ ràng trong định nghĩa AIDL và không yêu cầu trạng thái hoặc loại dữ liệu trả về do người dùng xác định.

Tham số đầu ra có lỗi

Khi một hàm AIDL báo cáo lỗi, hàm này không thể khởi chạy hoặc sửa đổi các tham số đầu ra. Cụ thể, các tham số đầu ra có thể được sửa đổi nếu lỗi xảy ra trong quá trình huỷ gói thay vì xảy ra trong quá trình xử lý giao dịch. Nhìn chung, khi gặp lỗi từ một hàm AIDL, tất cả tham số inoutout cũng như giá trị trả về (hoạt động như một tham số out trong một số phần phụ trợ) đều phải được coi là ở trạng thái vô thời hạn.

Giá trị lỗi nên sử dụng

Nhiều giá trị lỗi tích hợp sẵn có thể được sử dụng trong bất kỳ giao diện AIDL nào, nhưng một số giá trị được xử lý theo cách đặc biệt. Ví dụ: bạn có thể sử dụng EX_UNSUPPORTED_OPERATIONEX_ILLEGAL_ARGUMENT khi mô tả điều kiện lỗi, nhưng không được sử dụng EX_TRANSACTION_FAILED vì cơ sở hạ tầng cơ sở coi phương thức này là đặc biệt. Hãy kiểm tra các định nghĩa cụ thể trong phần phụ trợ để biết thêm thông tin về các giá trị tích hợp sẵn này.

Nếu giao diện AIDL yêu cầu các giá trị lỗi bổ sung không thuộc loại lỗi tích hợp sẵn, thì chúng có thể sử dụng lỗi tích hợp đặc biệt dành riêng cho dịch vụ cho phép đưa vào một giá trị lỗi dành riêng cho dịch vụ do người dùng xác định. Các lỗi dành riêng cho dịch vụ này thường được xác định trong giao diện AIDL dưới dạng enum được const int hoặc int hỗ trợ và không được phân tích cú pháp bằng liên kết.

Trong Java, lỗi ánh xạ đến các ngoại lệ, chẳng hạn như android.os.RemoteException. Đối với các trường hợp ngoại lệ dành riêng cho từng dịch vụ, Java sử dụng android.os.ServiceSpecificException cùng với lỗi do người dùng xác định.

Mã gốc trong Android không sử dụng các ngoại lệ. Phần phụ trợ CPP sử dụng android::binder::Status. Phần phụ trợ của NDK sử dụng ndk::ScopedAStatus. Mỗi phương thức do AIDL tạo sẽ trả về một trong các phương thức này, thể hiện trạng thái của phương thức đó. Phần phụ trợ Rust sử dụng các giá trị mã ngoại lệ giống như NDK, nhưng chuyển đổi các giá trị đó thành các lỗi Rust gốc (StatusCode, ExceptionCode) trước khi phân phối các giá trị đó cho người dùng. Đối với các lỗi dành riêng cho dịch vụ, Status hoặc ScopedAStatus được trả về sẽ sử dụng EX_SERVICE_SPECIFIC cùng với lỗi do người dùng xác định.

Bạn có thể tìm thấy các loại lỗi tích hợp trong các tệp sau:

Phần phụ trợ Định nghĩa
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

Sử dụng nhiều phần phụ trợ

Những hướng dẫn này dành riêng cho mã nền tảng Android. Các ví dụ này sử dụng một loại đã xác định là my.package.IFoo. Để biết hướng dẫn về cách sử dụng phần phụ trợ Rust, hãy xem ví dụ về AIDL của Rust trên trang Mẫu Rust cho Android.

Kiểu nhập

Cho dù loại đã xác định là giao diện, theo gói hay hợp nhất, bạn đều có thể nhập loại đó trong Java:

import my.package.IFoo;

Hoặc trong phần phụ trợ CPP:

#include <my/package/IFoo.h>

Hoặc trong phần phụ trợ NDK (chú ý không gian tên aidl bổ sung):

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

Hoặc trong phần phụ trợ Rust:

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

Mặc dù có thể nhập một kiểu được lồng trong Java, nhưng trong phần phụ trợ CPP/NDK, bạn phải đưa tiêu đề vào kiểu gốc của nó. Ví dụ: khi nhập loại lồng ghép Bar được xác định trong my/package/IFoo.aidl (IFoo là loại gốc của tệp), bạn phải thêm <my/package/IFoo.h> cho phần phụ trợ CPP (hoặc <aidl/my/package/IFoo.h> cho phần phụ trợ NDK).

Triển khai dịch vụ

Để triển khai một dịch vụ, bạn phải kế thừa từ lớp mã giả lập gốc. Lớp này sẽ đọc các lệnh từ trình điều khiển liên kết và thực thi các phương thức mà bạn triển khai. Tưởng tượng rằng bạn có một tệp AIDL như sau:

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

Trong Java, bạn phải mở rộng từ lớp này:

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

Trong phần phụ trợ CPP:

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

Trong phần phụ trợ NDK (để ý không gian tên aidl bổ sung):

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

Trong phần phụ trợ của 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(())
        }
    }

Hoặc với Rust không đồng bộ:

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

Đăng ký và nhận dịch vụ

Các dịch vụ trong nền tảng Android thường được đăng ký bằng quy trình servicemanager. Ngoài các API bên dưới, một số API còn kiểm tra dịch vụ (nghĩa là API sẽ trả về ngay lập tức nếu dịch vụ không có sẵn). Hãy kiểm tra giao diện servicemanager tương ứng để biết thông tin chi tiết chính xác. Bạn chỉ có thể thực hiện các thao tác này khi biên dịch trên nền tảng Android.

Trong Java:

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

Trong phần phụ trợ 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"));

Trong phần phụ trợ NDK (để ý không gian tên aidl bổ sung):

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

Trong phần phụ trợ của 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()
}

Trong phần phụ trợ Rust không đồng bộ, với thời gian chạy đơn luồng:

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
}

Một điểm khác biệt quan trọng so với các tuỳ chọn khác là chúng tôi không gọi join_thread_pool khi sử dụng Rust không đồng bộ và môi trường thời gian chạy đơn luồng. Điều này là do bạn cần cung cấp cho Tokio một luồng để Tokio có thể thực thi các tác vụ được tạo. Trong ví dụ này, luồng chính sẽ phục vụ mục đích đó. Mọi tác vụ được tạo bằng tokio::spawn sẽ thực thi trên luồng chính.

Trong phần phụ trợ Rust không đồng bộ, với thời gian chạy đa luồng:

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

Với môi trường thời gian chạy đa luồng Tokio, các tác vụ được tạo sẽ không thực thi trên luồng chính. Do đó, bạn nên gọi join_thread_pool trên luồng chính để luồng chính không chỉ ở trạng thái rảnh. Bạn phải gói lệnh gọi trong block_in_place để rời khỏi ngữ cảnh không đồng bộ.

Bạn có thể yêu cầu nhận thông báo khi một dịch vụ lưu trữ một liên kết bị vô hiệu hoá. Điều này có thể giúp tránh làm rò rỉ proxy gọi lại hoặc hỗ trợ khôi phục lỗi. Thực hiện các lệnh gọi này trên các đối tượng proxy liên kết.

  • Trong Java, hãy sử dụng android.os.IBinder::linkToDeath.
  • Trong phần phụ trợ CPP, hãy sử dụng android::IBinder::linkToDeath.
  • Trong phần phụ trợ NDK, hãy sử dụng AIBinder_linkToDeath.
  • Trong phần phụ trợ Rust, hãy tạo một đối tượng DeathRecipient, sau đó gọi my_binder.link_to_death(&mut my_death_recipient). Xin lưu ý rằng vì DeathRecipient sở hữu lệnh gọi lại, nên bạn phải duy trì đối tượng đó trong khoảng thời gian bạn muốn nhận thông báo.

Thông tin người gọi

Khi nhận được lệnh gọi liên kết nhân, thông tin phương thức gọi sẽ có trong một số API. PID (hay Mã quy trình) là mã nhận dạng quy trình Linux của quy trình đang gửi giao dịch. UID (hoặc Mã nhận dạng người dùng) đề cập đến mã nhận dạng người dùng Linux. Khi nhận cuộc gọi một chiều, PID gọi là 0. Khi ở bên ngoài ngữ cảnh giao dịch liên kết, các hàm này sẽ trả về PID và UID của quy trình hiện tại.

Trong phần phụ trợ Java:

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

Trong phần phụ trợ CPP:

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

Trong phần phụ trợ của NDK:

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

Trong phần phụ trợ Rust, khi triển khai giao diện, hãy chỉ định như sau (thay vì cho phép giao diện mặc định):

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

Báo cáo lỗi và API gỡ lỗi cho dịch vụ

Khi chạy báo cáo lỗi (ví dụ: với adb bugreport), các báo cáo này sẽ thu thập thông tin từ khắp nơi trong hệ thống để hỗ trợ gỡ lỗi nhiều vấn đề. Đối với các dịch vụ AIDL, báo cáo lỗi sẽ sử dụng dumpsys nhị phân trên tất cả các dịch vụ đã đăng ký với trình quản lý dịch vụ để kết xuất thông tin vào báo cáo lỗi. Bạn cũng có thể sử dụng dumpsys trên dòng lệnh để nhận thông tin từ một dịch vụ có dumpsys SERVICE [ARGS]. Trong phần phụ trợ C++ và Java, bạn có thể kiểm soát thứ tự kết xuất các dịch vụ bằng cách sử dụng các đối số bổ sung vào addService. Bạn cũng có thể sử dụng dumpsys --pid SERVICE để lấy PID của một dịch vụ trong khi gỡ lỗi.

Để thêm đầu ra tuỳ chỉnh vào dịch vụ, bạn có thể ghi đè phương thức dump trong đối tượng máy chủ như khi triển khai mọi phương thức IPC khác được xác định trong tệp AIDL. Khi thực hiện việc này, bạn nên hạn chế kết xuất cho quyền của ứng dụng android.permission.DUMP hoặc hạn chế kết xuất ở các UID cụ thể.

Trong phần phụ trợ Java:

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

Trong phần phụ trợ CPP:

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

Trong phần phụ trợ của NDK:

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

Trong phần phụ trợ Rust, khi triển khai giao diện, hãy chỉ định như sau (thay vì cho phép giao diện mặc định):

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

Tự động lấy chỉ số mô tả giao diện

Bộ mô tả giao diện xác định loại giao diện. Điều này rất hữu ích khi gỡ lỗi hoặc khi bạn có một liên kết không xác định.

Trong Java, bạn có thể lấy chỉ số mô tả giao diện bằng các mã như:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

Trong phần phụ trợ CPP:

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

Các phần phụ trợ của NDK và Rust không hỗ trợ tính năng này.

Lấy chỉ số mô tả giao diện không đổi

Đôi khi (chẳng hạn như khi đăng ký các dịch vụ @VintfStability), bạn cần biết chỉ số mô tả giao diện là gì ở dạng tĩnh. Trong Java, bạn có thể lấy chỉ số mô tả bằng cách thêm mã như:

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

Trong phần phụ trợ CPP:

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

Trong phần phụ trợ NDK (để ý không gian tên aidl bổ sung):

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

Trong phần phụ trợ của Rust:

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

Dải ô enum

Trong các phần phụ trợ gốc, bạn có thể lặp lại các giá trị mà một enum có thể đảm nhận. Do những cân nhắc về kích thước mã, kích thước này không được hỗ trợ trong Java.

Đối với một MyEnum enum được xác định trong AIDL, phép lặp được cung cấp như sau.

Trong phần phụ trợ CPP:

    ::android::enum_range<MyEnum>()

Trong phần phụ trợ của NDK:

   ::ndk::enum_range<MyEnum>()

Trong phần phụ trợ của Rust:

    MyEnum::enum_values()

Quản lý luồng

Mọi thực thể của libbinder trong một quy trình đều duy trì một nhóm luồng. Đối với hầu hết các trường hợp sử dụng, đây phải là chính xác một nhóm luồng, dùng chung trên tất cả các phần phụ trợ. Trường hợp ngoại lệ duy nhất là khi mã nhà cung cấp có thể tải một bản sao khác của libbinder để trao đổi với /dev/vndbinder. Vì nằm trên một nút liên kết riêng biệt, nên nhóm luồng không được dùng chung.

Đối với phần phụ trợ Java, nhóm luồng chỉ có thể tăng kích thước (vì đã bắt đầu):

    BinderInternal.setMaxThreads(<new larger value>);

Đối với phần phụ trợ CPP, bạn có thể thực hiện các thao tác sau:

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

Tương tự, trong phần phụ trợ của NDK:

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

Trong phần phụ trợ của Rust:

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

Với phần phụ trợ Rust không đồng bộ, bạn cần có 2 nhóm luồng: liên kết và Tokio. Điều này có nghĩa là các ứng dụng sử dụng Rust không đồng bộ cần được cân nhắc đặc biệt, đặc biệt là khi dùng join_thread_pool. Hãy xem phần về việc đăng ký dịch vụ để biết thêm thông tin về vấn đề này.

Tên dành riêng

C++, Java và Rust đặt trước một số tên làm từ khoá hoặc để sử dụng theo ngôn ngữ cụ thể. Mặc dù AIDL không thực thi các quy tắc hạn chế dựa trên quy tắc về ngôn ngữ, nhưng việc sử dụng tên trường hoặc tên loại khớp với tên dành riêng có thể dẫn đến lỗi biên dịch đối với C++ hoặc Java. Đối với Rust, trường hoặc kiểu này được đổi tên bằng cú pháp "giá trị nhận dạng thô", có thể truy cập được bằng tiền tố r#.

Bạn nên tránh sử dụng tên dành riêng trong định nghĩa AIDL nếu có thể để tránh các liên kết không hợp lý hoặc lỗi biên dịch hoàn toàn.

Nếu đã có tên dành riêng trong định nghĩa AIDL, bạn có thể đổi tên các trường một cách an toàn mà vẫn tương thích với giao thức; có thể bạn cần cập nhật mã để tiếp tục xây dựng, nhưng mọi chương trình đã tạo sẽ tiếp tục tương tác.

Tên cần tránh: