Google cam kết thúc đẩy công bằng chủng tộc cho Cộng đồng người da đen. Xem cách thực hiện.

AIDL phụ trợ

Một chương trình 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 phụ trợ AIDL khác nhau.

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

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ả các
NDK C ++ libbinder_ndk (ổn định *) aidl_interface
CPP C ++ libbinder (không ổn định) tất cả các
Rỉ sét Rỉ sét libbinder_rs (không ổn định) aidl_interface
  • Các bề mặt API này ổ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 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 trên libbinder_ndk . Các APEX sử dụng thùng chất kết dính theo cách giống như bất kỳ ai khác trong hệ thống. Phần Rust được đóng gói thành một APEX và được 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 phần phụ trợ, có hai cách để biên dịch AIDL thành mã sơ khai. Để biết thêm chi tiết về các hệ thống xây dựng, hãy xem 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 trong các mô-đun Android.mk tương đương 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, các chương trình phụ trợ Java / CPP của AIDL được sử dụng (không phải chương trình phụ trợ NDK) và các lớp để sử dụng các tệp AIDL tương ứng đượ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 đến các tệp AIDL trong mô-đun đó có thể được chỉ định trong các mô-đun này dưới một nhóm aidl: Lưu ý rằng phần phụ trợ Rust chỉ để sử dụng với Rust. mô-đun rust_ được xử lý khác nhau ở 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 là <aidl_interface name>-rust có thể được liên kết chống lại. Để biết thêm chi tiết, hãy xem ví dụ Rust AIDL .

aidl_interface

Xem AIDL ổn định . 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; nghĩa là, được thể hiện trực tiếp bằng AIDL. Điều này có nghĩa là không thể sử dụng các gói tùy chỉnh.

Các loại

Bạn có thể coi trình biên dịch aidl như một triển khai tham chiếu cho các loại. 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 bạn sử dụng mô-đun 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
char 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 :: string Sợi dây
android.os.Parcelable android :: Có thể gửi được N / A N / A
IBinder android :: IBinder ndk :: SpAIBinder chất kết dính :: SpIBinder
T [] std :: vector <T> std :: vector <T> Trong: & T
Hết: Vec <T>
byte [] std :: vector <uint8_t> std :: vector <int8_t> 1 Trong: & [u8]
Hết: Vec <u8>
Danh sách <T> std :: vector <T> 2 std :: vector <T> 3 Trong: & [T] 4
Hết: Vec <T>
FileDescriptor android :: base :: unique_fd N / A binder :: parcel :: ParcelFileDescriptor
ParcelFileDescriptor 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 các String , IBinder , ParcelFileDescriptor hoặc parcelable. Trong Android T (thử nghiệm AOSP) trở lên, T có thể là bất kỳ kiểu không nguyên thủy nào (bao gồm cả các kiểu giao diện) ngoại trừ mảng. AOSP khuyên bạn nên sử dụng các kiểu mảng như T[] , vì chúng hoạt động trong tất cả các phần phụ trợ.

3. NDK backend hỗ trợ List<T> trong đó T là một trong các String , ParcelFileDescriptor hoặc parcelable. Trong Android T (thử nghiệm AOSP) trở lên, T có thể là bất kỳ kiểu không nguyên thủy nào ngoại trừ mảng.

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

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

6. Trong Android T (thử nghiệm AOSP) trở lên, các mảng có kích thước cố định được hỗ trợ. Mảng kích thước cố định có thể có nhiều kích thước (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 các kiểu mảng.

Định hướng (vào / ra / inout)

Khi chỉ định kiểu của các đối số cho các hàm, bạn có thể chỉ định chúng là in , out hoặc inout . Điều này kiểm soát thông tin hướng được chuyển cho một 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 bộ nhớ. out nghĩa là dữ liệu được truyền từ callee đến caller. 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 inout trình xác định đối số. Nếu bạn sử dụng inout với giao diện được tạo 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, kiểu inout bình thường nhận &mut Vec<T> và kiểu inout danh sách nhận &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 dưới dạng @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 .

Vô hiệu

Bạn có thể chú thích các loại có thể là null trong phần phụ trợ Java với @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. Các ngoại lệ duy nhất cho điều này là các loại interfaceIBinder , luôn có thể là rỗng đối với các lần đọc NDK và ghi CPP / NDK. Để biết thêm thông tin về nullable thích vô hiệu, hãy xem Chú thích trong AIDL .

Các bưu kiện tùy chỉnh

Trong phần phụ trợ C ++ và Java trong hệ thống xây dựng cốt lõi, bạn có thể khai báo một gói được triển khai thủ công trong phần phụ trợ đích (trong C ++ hoặc trong Java).

    package my.package;
    parcelable Foo;

hoặc với khai báo tiêu đề C ++:

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

Sau đó, bạn có thể sử dụng bưu kiện này như một loại trong tệp AIDL, nhưng nó sẽ không được tạo bởi AIDL.

Rust không hỗ trợ các bưu kiện tùy chỉnh.

Giá trị mặc định

Các gói 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 giá trị nguyên thủy, String và mảng thuộc các kiểu này.

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

Trong chương trình phụ trợ Java khi các giá trị mặc định bị thiếu, 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ị được khởi tạo mặc định khi các 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 rỗng và các trường List<T> được khởi tạo dưới dạng một vector<T> . @nullable được khởi tạo dưới dạng trường giá trị null.

Xử lý lỗi

Hệ điều hành Android cung cấp các loại lỗi cài sẵn cho các 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 đầy đủ 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ả lại nào do người dùng xác định.

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

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 đặc biệt dành riêng cho dịch vụ 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 định nghĩa trong giao diện AIDL dưới dạng const int hoặc int -backed enum và không được phân tích cú pháp bởi chất kết dính.

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 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 được tạo bởi AIDL đều trả về một trong những phương thức này, đại diện cho trạng thái của phương thức. Rust backend sử dụng các giá trị mã ngoại lệ giống như NDK, nhưng chuyển đổi chúng thành các lỗi Rust nguyên bản ( StatusCode , ExceptionCode ) trước khi phân phố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ó thể tìm thấy các loại lỗi tích hợp trong các tệp sau:

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 phụ trợ khác nhau

Các 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 kiểu đã xác định, 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ề Rust AIDL trên trang Android Rust Patterns .

Nhập các loại

Cho dù kiểu được xác định là giao diện, có thể phân lô hay liên hợp, bạn có thể nhập nó vào Java:

    import my.package.IFoo;

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

    #include <my/package/IFoo.h>

Hoặc trong chương trình phụ trợ aidl (lưu ý không gian tên bổ sung hỗ trợ):

    #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 một Bar kiểu lồng nhau đượ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 chương trình phụ trợ CPP (hoặc <aidl/my/package/IFoo.h> cho chương trình phụ trợ NDK).

Thực hiện các dịch vụ

Để triển khai một dịch vụ, bạn phải kế thừa từ lớp gốc gốc. Lớp này đọc các lệnh từ trình điều khiển chất kết dính 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ợ aidl (lưu ý vùng tên hỗ trợ 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ợ 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(())
        }
    }

Đă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 servicemanager trình quản lý dịch vụ. Ngoài các API bên dưới, một số API kiểm tra dịch vụ (nghĩa là chúng quay lại ngay lập tức nếu dịch vụ không khả dụng). Kiểm tra giao diện quản lý dịch 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 dựa trên nền tảng Android.

Trong Java:

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

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

Trong phần phụ trợ aidl (lưu ý vùng tên hỗ trợ bổ sung):

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

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

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 chết. Đ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 kết dính.

  • Trong Java, sử dụng android.os.IBinder::linkToDeath .
  • Trong phần phụ trợ CPP, 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, 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, bạn phải giữ cho đối tượng đó tồn tại miễn là bạn muốn nhận thông báo.

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

Khi báo cáo lỗi chạy (ví dụ: với báo cáo adb bugreport ), chúng thu thập thông tin từ khắp nơi trên 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 kết xuất nhị dumpsys trên tất cả các dịch vụ đã đăng ký với người quản lý dịch vụ để kết xuất thông tin của họ 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 phần phụ trợ C ++ và Java, bạn có thể kiểm soát thứ tự mà 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 một dịch vụ trong khi gỡ lỗi.

Để thêm đầu ra tùy chỉnh vào dịch vụ của mình, bạn có thể ghi đè phương thức dump xuất 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ế kết xuất đối với quyền ứng dụng android.permission.DUMP hoặc hạn chế kết xuất đối với 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ó ở chế độ mặc định):

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

Tự động nhận bộ 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ó 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 với 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ụ trợ NDK và Rust không hỗ trợ chức năng này.

Tĩnh nhận bộ 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ợ aidl (lưu ý vùng tên hỗ trợ bổ sung):

    #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 Enum

Trong phần phụ trợ gốc, bạn có thể lặp lại các giá trị có thể mà một 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 một enum MyEnum được định nghĩa trong AIDL, sự lặp lại đượ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_range()

Quản lý luồng

Mỗi phiên bản libbinder trong một quy trình đều duy trì một luồng. Đối với hầu hết các trường hợp sử dụng, đây phải là một luồng chính xác, được chia sẻ trên tất cả các phần phụ trợ. Ngoại lệ duy nhất cho điều này là khi mã nhà cung cấp có thể tải một bản sao khác của libbinder để 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 chương trình phụ trợ Java, threadpool chỉ có thể tăng kích thước (vì nó đã được khởi động):

    BinderInternal.setMaxThreads(<new larger value>);

Đối với chương trình phụ trợ CPP, các thao tác sau có sẵn:

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