AIDL phụ trợ

Phần phụ trợ AIDL là mục tiêu để tạo mã sơ khai. Khi sử dụng tệp AIDL, bạn luôn sử dụng chúng bằng một ngôn ngữ cụ thể với thời gian chạy cụ thể. Tùy thuộc vào ngữ cảnh, bạn nên sử dụng các chương trình phụ trợ AIDL khác nhau.

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

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

Xây dựng hệ thống

Tùy thuộc vào chương trình phụ trợ, có hai cách để biên dịch AIDL thành mã sơ khai. Để biết thêm chi tiết về hệ thống xây dựng, hãy xem Tài liệu tham khảo mô-đun Soong .

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

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

viện trợ_giao diện

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, các gói có thể phân chia phải chứa các trường trực tiếp và không phải là các khai báo kiểu được xác định trực tiếp trong ngôn ngữ đích. Để biết AIDL có cấu trúc phù hợp với AIDL ổn định như thế nào, hãy xem AIDL có cấu trúc so với AIDL ổn định .

Các loại

Bạn có thể coi trình biên dịch aidl như một cách 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 kết quả. Khi sử dụng mô- aidl_interface , bạn có thể xem kết quả đầu ra trong out/soong/.intermediates/<path to module>/ .

Loại Java/AIDL Loại C++ Loại NDK Loại rỉ sét
boolean bool bool bool
byte int8_t int8_t i8
ký tự char16_t char16_t u16
int int32_t int32_t i32
dài int64_t int64_t i64
trôi nổi trôi nổi trôi nổi f32
gấp đôi gấp đôi gấp đôi f64
Sợi dây android::String16 std::chuỗi Sợi dây
android.os.Parcelable android::Có thể gửi được không áp dụng không áp dụng
IBinder android::IBinder ndk::SpAIBinder chất kết dính::SpIBinder
T[] 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>
Bộ mô tả tệp android::base::unique_fd không áp dụng binder::parcel::ParcelFileDescriptor
Bộ mô tả tập tin bưu kiện android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
loại giao diện (T) android::sp<T> std::shared_ptr<T> chất kết dính::Mạnh mẽ
loại có thể gửi được (T) T T T
loại công đoàn (T) 5 T T T
T[N] 6 std::mảng<T, N> std::mảng<T, N> [T; N]

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

2. Phần phụ trợ C++ hỗ trợ List<T> trong đó T là một trong String , IBinder , ParcelFileDescriptor hoặc có thể phân chia được. Trong Android 13 trở lên, T có thể là bất kỳ loại không nguyên thủy nào (bao gồm cả loại giao diện) ngoại trừ mảng. AOSP khuyên bạn nên sử dụng các loại mảng như T[] , vì chúng hoạt động trong tất cả các chương trình phụ trợ.

3. Phần phụ trợ NDK hỗ trợ List<T> trong đó T là một trong String , ParcelFileDescriptor hoặc có thể phân chia được. Trong Android 13 trở lên, T có thể là bất kỳ loại không nguyên thủy nào ngoại trừ mảng.

4. Các loại được truyền khác nhau cho mã Rust tùy thuộc vào việc chúng là đầu vào (đối số) hay đầu ra (giá trị được trả về).

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

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

Tính định hướng (vào/ra/vào)

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

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 là @utf8InCpp String trong AIDL để tự động chuyển đổi chúng thành utf-8. Phần phụ trợ NDK và Rust luôn sử dụng chuỗi utf-8. Để biết thêm thông tin về chú thích utf8InCpp , hãy xem Chú thích trong AIDL .

Tính vô hiệu

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

Bưu kiện tùy chỉnh

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

Để khai báo một gói có thể gửi tùy chỉnh để AIDL biết về nó, phần khai báo có thể gửi theo AIDL trông như thế này:

    package my.pack.age;
    parcelable Foo;

Theo mặc định, điều này khai báo một Java có thể phân chia được 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 tùy chỉnh có thể gửi được trong AIDL, hãy sử dụng cpp_header :

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

Việc triển khai C++ trong my/pack/age/Foo.h trông như thế này:

    #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 NDK tùy chỉnh có thể gửi được trong AIDL, hãy sử dụng ndk_header :

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

Việc triển khai NDK trong android/pack/age/Foo.h trông như thế này:

    #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 Rust tùy chỉnh có thể gửi được trong AIDL, hãy sử dụng rust_type :

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

Việc triển khai Rust trong rust_crate/src/lib.rs trông như thế này:

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 loại có thể phân chia này làm loại trong tệp AIDL, nhưng nó sẽ không được AIDL tạo ra. Cung cấp các toán tử <== cho các gói tùy chỉnh phụ trợ CPP/NDK để sử dụng chúng trong union .

Giá trị mặc định

Các bưu kiện có cấu trúc có thể khai báo các giá trị mặc định trên mỗi trường cho các kiểu nguyên thủy, String và các 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 các giá trị mặc định, các trường được khởi tạo dưới dạng giá trị 0 cho các kiểu nguyên thủy và null cho các kiểu không nguyên thủy.

Trong các chương trình phụ trợ khác, các trường được khởi tạo với các giá trị khởi tạo 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 tạo dưới dạng một chuỗi trống và các trường List<T> được khởi tạo 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ị null.

Xử lý lỗi

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

Thông số đầu ra có lỗi

Khi hàm AIDL báo lỗi, hàm đó có thể không khởi tạo 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 giải nén thay vì xảy ra trong quá trình xử lý giao dịch. Nói chung, khi gặp lỗi từ hàm AIDL, tất cả các tham số inoutout cũng như giá trị trả về (hoạt động giống như tham số out trong một số chương trình phụ trợ) phải được coi là ở trạng thái không xác định.

Những giá trị lỗi nào sẽ được sử dụng

Nhiều giá trị lỗi tích hợp 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ụ: EX_UNSUPPORTED_OPERATIONEX_ILLEGAL_ARGUMENT có thể sử dụng khi chúng mô tả tình trạng lỗi, nhưng không được sử dụng EX_TRANSACTION_FAILED vì nó được cơ sở hạ tầng cơ bản xử lý đặc biệt. Kiểm tra các định nghĩa cụ thể về phần phụ trợ để biết thêm thông tin về các giá trị tích hợp này.

Nếu giao diện AIDL yêu cầu các giá trị lỗi bổ sung không nằm trong các loại lỗi tích hợp thì chúng có thể sử dụng lỗi tích hợp dành riêng cho dịch vụ đặc biệt cho phép bao gồm 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 const int hoặc int - và không được phân tích cú pháp bằng chất kết dính.

Trong Java, lỗi ánh xạ tới các ngoại lệ, chẳng hạn như android.os.RemoteException . Đối với các ngoại lệ dành riêng cho 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 ngoại lệ. Phần phụ trợ CPP sử dụng android::binder::Status . Phần phụ trợ NDK sử dụng ndk::ScopedAStatus . Mọi phương thức do AIDL tạo ra đều 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 chúng thành lỗi Rust gốc ( StatusCode , ExceptionCode ) trước khi gửi chúng 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ử dụng EX_SERVICE_SPECIFIC cùng với lỗi do người dùng xác định.

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

Phần phụ trợ Sự định nghĩa
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
rỉ sét android/binder_status.h

Sử dụng các chương trình phụ trợ khác nhau

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

Nhập các loại

Cho dù loại được xác định là giao diện, có thể chia nhỏ hay kết hợp, bạn đều có thể nhập loại đó vào 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 việ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ù bạn có thể nhập một kiểu lồng nhau trong Java, nhưng trong phần phụ trợ CPP/NDK, bạn phải bao gồm tiêu đề cho kiểu gốc của nó. Ví dụ: khi nhập loại lồng nhau Bar được xác định trong my/package/IFoo.aidl ( IFoo là loại gốc của tệp), bạn phải bao gồm <my/package/IFoo.h> cho phần phụ trợ CPP (hoặc <aidl/my/package/IFoo.h> cho phần phụ trợ NDK).

Dịch vụ triển khai

Để triển khai một dịch vụ, bạn phải kế thừa từ lớp sơ khai gốc. Lớp này đọ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. Hãy tưởng tượng rằng bạn có một tệp AIDL như thế này:

    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 (chú ý không gian tên viện aidl ):

    #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ợ 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ý với quy trình servicemanager . Ngoài các API bên dưới, một số API kiểm tra dịch vụ (có nghĩa là chúng sẽ quay lại ngay lập tức nếu dịch vụ không có sẵn). Kiểm tra giao diện servicemanager tương ứng để biết chi tiết chính xác. Các thao tác này chỉ có thể được thực hiện 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 (chú ý không gian tên viện 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")));

Trong phần phụ trợ 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 một 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 tùy chọn khác là chúng tôi không gọi join_thread_pool khi sử dụng async Rust và 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 để nó có thể thực thi các tác vụ được tạo ra. Trong ví dụ này, luồng chính sẽ phục vụ mục đích đó. Bất kỳ tác vụ nào đượ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 thời gian chạy Tokio đa luồng, các tác vụ được tạo ra không thực thi trên luồng chính. Do đó, sẽ hợp lý hơn nếu gọi join_thread_pool trên luồng chính để luồng chính không ở trạng thái rảnh. Bạn phải kết thúc cuộc 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 dịch vụ lưu trữ chất kết dính ngừng hoạt động. Điều này có thể giúp tránh rò rỉ proxy gọi lại hoặc hỗ trợ khắc phục lỗi. Thực hiện các cuộc gọi này trên các đối tượng proxy liên kết.

  • Trong Java, 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) . Lưu ý rằng vì DeathRecipient sở hữu lệnh gọi lại nên bạn phải giữ đối tượng đó tồn tại miễn là bạn muốn nhận được thông báo.

Thông tin người gọi

Khi nhận được lệnh gọi kernel binder, thông tin người gọi sẽ có sẵn trong một số API. PID (hoặc ID tiến trình) đề cập đến ID tiến trình Linux của quá trình đang gửi giao dịch. UID (hoặc ID người dùng) đề cập đến ID người dùng Linux. Khi nhận cuộc gọi một chiều, PID cuộc gọi là 0. Khi ở ngoài ngữ cảnh giao dịch liên kết, các hàm này 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ợ NDK:

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

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

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

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

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

Để thêm đầu ra tùy chỉnh vào dịch vụ của bạn, bạn có thể ghi đè phương thức dump trong đối tượng máy chủ của mình giống như bạn đang triển khai bất kỳ phương thức IPC nào 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ế chuyển sang quyền ứng dụng android.permission.DUMP hoặc hạn chế chuyển sang 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ợ 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ững điều sau (thay vì cho phép nó mặc định):

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

Tự động nhận mô tả giao diện

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

Trong Java, bạn có thể lấy bộ mô tả giao diện bằng 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();

Phần phụ trợ NDK và Rust không hỗ trợ chức năng này.

Tĩnh nhận mô tả giao diện

Đôi khi (chẳng hạn như khi đăng ký dịch vụ @VintfStability ), bạn cần biết bộ mô tả giao diện tĩnh là gì. Trong Java, bạn có thể lấy bộ 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 (chú ý không gian tên viện aidl ):

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

Trong phần phụ trợ Rust:

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

Phạm vi liệt kê

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

Đối với enum MyEnum đượ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ợ NDK:

   ::ndk::enum_range<MyEnum>()

Trong phần phụ trợ Rust:

    MyEnum::enum_values()

Quản lý chủ đề

Mỗi phiên bản của libbinder trong một tiến 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 chính xác là một nhóm luồng, được chia sẻ trên tất cả các chương trình phụ trợ. Ngoại lệ duy nhất cho trường hợp này là khi mã của nhà cung cấp có thể tải một bản sao libbinder khác để nói chuyện với /dev/vndbinder . Vì đây là một nút liên kết riêng biệt nên luồng luồng không được chia sẻ.

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

    BinderInternal.setMaxThreads(<new larger value>);

Đối với chương trình phụ trợ CPP, có sẵ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ợ NDK:

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

Trong phần phụ trợ 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 hai nhóm luồng: binder và Tokio. Điều này có nghĩa là các ứng dụng sử dụng async Rust cần được cân nhắc đặc biệt, đặc biệt là khi sử dụng join_thread_pool . Xem phần đăng ký dịch vụ để biết thêm thông tin về điều này.

Tên dành riêng

C++, Java và Rust dành một số tên làm từ khóa hoặc cho mục đích sử dụng dành riêng cho ngôn ngữ. Mặc dù AIDL không thực thi các hạn chế dựa trên quy tắc ngôn 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 cho C++ hoặc Java. Đối với Rust, trường hoặc loại được đổi tên bằng cú pháp "mã định danh thô", có thể truy cập được bằng tiền tố r# .

Chúng tôi khuyên bạn nên tránh sử dụng tên dành riêng trong định nghĩa AIDL của mình nếu có thể để tránh các ràng buộc không phù hợp hoặc lỗi biên dịch hoàn toàn.

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

Những tên cần tránh: * Từ khóa C++ * Từ khóa Java * Từ khóa Rust