AIDL cho HAL

Android 11 giới thiệu khả năng sử dụng AIDL cho HAL trong Android. Điều này giúp triển khai các phần của Android mà không cần HIDL. Chuyển đổi HAL sang sử dụng riêng AIDL nếu có thể (khi HAL ngược dòng sử dụng HIDL, thì HIDL phải được sử dụng).

HAL sử dụng AIDL để giao tiếp giữa các thành phần khung, chẳng hạn như các thành phần trong system.img và các thành phần phần cứng, chẳng hạn như các thành phần trong vendor.img , phải sử dụng AIDL ổn định. Tuy nhiên, để giao tiếp trong một phân vùng, ví dụ từ HAL này sang HAL khác, không có giới hạn nào đối với cơ chế IPC để sử dụng.

Động lực

AIDL đã tồn tại lâu hơn HIDL và được sử dụng ở nhiều nơi khác, chẳng hạn như giữa các thành phần khung Android hoặc trong ứng dụng. Giờ đây, AIDL có hỗ trợ ổn định, có thể triển khai toàn bộ ngăn xếp với một thời gian chạy IPC duy nhất. AIDL cũng có hệ thống tạo phiên bản tốt hơn HIDL.

  • Sử dụng một ngôn ngữ IPC duy nhất có nghĩa là chỉ có một thứ để tìm hiểu, gỡ lỗi, tối ưu hóa và bảo mật.
  • AIDL hỗ trợ tạo phiên bản tại chỗ cho chủ sở hữu giao diện:
    • Chủ sở hữu có thể thêm các phương thức vào cuối giao diện hoặc các trường vào các gói có thể phân phối. Điều này có nghĩa là mã phiên bản dễ dàng hơn qua nhiều năm và chi phí hàng năm cũng nhỏ hơn (các loại có thể được sửa đổi tại chỗ và không cần thêm thư viện cho mỗi phiên bản giao diện).
    • Các giao diện tiện ích mở rộng có thể được đính kèm trong thời gian chạy thay vì trong hệ thống loại, do đó không cần phải khởi động lại các tiện ích mở rộng xuôi dòng trên các phiên bản giao diện mới hơn.
  • Giao diện AIDL hiện có có thể được sử dụng trực tiếp khi chủ sở hữu của nó chọn ổn định nó. Trước đây, toàn bộ bản sao của giao diện sẽ phải được tạo trong HIDL.

Viết giao diện AIDL HAL

Để giao diện AIDL được sử dụng giữa hệ thống và nhà cung cấp, giao diện cần có hai thay đổi:

  • Mọi định nghĩa loại phải được chú thích bằng @VintfStability .
  • Khai báo aidl_interface cần bao gồm stability: "vintf", .

Chỉ chủ sở hữu của giao diện mới có thể thực hiện những thay đổi này.

Khi bạn thực hiện những thay đổi này, giao diện phải có trong bảng kê khai VINTF để hoạt động. Kiểm tra điều này (và các yêu cầu liên quan, chẳng hạn như xác minh rằng các giao diện đã phát hành bị đóng băng) bằng cách sử dụng kiểm tra VTS vts_treble_vintf_vendor_test . Bạn có thể sử dụng giao diện @VintfStability mà không cần các yêu cầu này bằng cách gọi AIBinder_forceDowngradeToLocalStability trong phần phụ trợ NDK, android::Stability::forceDowngradeToLocalStability trong phần phụ trợ C++ hoặc android.os.Binder#forceDowngradeToSystemStability trong phần phụ trợ Java trên đối tượng chất kết dính trước khi gửi sang một tiến trình khác. Việc hạ cấp dịch vụ xuống độ ổn định của nhà cung cấp không được hỗ trợ trong Java vì tất cả các ứng dụng đều chạy trong ngữ cảnh hệ thống.

Ngoài ra, để có tính di động tối đa của mã và để tránh các sự cố tiềm ẩn như thư viện bổ sung không cần thiết, hãy tắt chương trình phụ trợ CPP.

Lưu ý rằng việc sử dụng backends trong ví dụ mã bên dưới là chính xác, vì có ba chương trình phụ trợ (Java, NDK và CPP). Đoạn mã dưới đây cho biết cách chọn phụ trợ CPP cụ thể để vô hiệu hóa nó.

    aidl_interface: {
        ...
        backends: {
            cpp: {
                enabled: false,
            },
        },
    }

Tìm giao diện AIDL HAL

AOSP Các giao diện AIDL ổn định cho HAL nằm trong cùng các thư mục cơ sở với các giao diện HIDL, trong các thư mục aidl .

  • phần cứng/giao diện
  • khung/phần cứng/giao diện
  • hệ thống/phần cứng/giao diện

Bạn nên đặt các giao diện mở rộng vào các thư mục con hardware/interfaces khác trong vendor hoặc hardware .

Giao diện mở rộng

Android có một bộ giao diện AOSP chính thức với mọi bản phát hành. Khi các đối tác Android muốn thêm chức năng vào các giao diện này, họ không nên trực tiếp thay đổi các giao diện này vì điều này có nghĩa là thời gian chạy Android của họ không tương thích với thời gian chạy Android AOSP. Đối với các thiết bị GMS, việc tránh thay đổi các giao diện này cũng là điều đảm bảo hình ảnh GSI có thể tiếp tục hoạt động.

Tiện ích mở rộng có thể đăng ký theo hai cách khác nhau:

Tuy nhiên, một tiện ích mở rộng đã được đăng ký, khi các thành phần dành riêng cho nhà cung cấp (nghĩa là không phải là một phần của AOSP ngược dòng) sử dụng giao diện, sẽ không có khả năng xảy ra xung đột hợp nhất. Tuy nhiên, khi các sửa đổi xuôi dòng đối với các thành phần AOSP ngược dòng được thực hiện, xung đột hợp nhất có thể xảy ra và các chiến lược sau đây được khuyến nghị:

  • các bổ sung giao diện có thể được ngược dòng lên AOSP trong phiên bản tiếp theo
  • bổ sung giao diện cho phép linh hoạt hơn nữa, không có xung đột hợp nhất, có thể được ngược dòng trong bản phát hành tiếp theo

Parcelables mở rộng: ParcelableHolder

ParcelableHolder là một Parcelable có thể chứa một Parcelable khác. Trường hợp sử dụng chính của ParcelableHolder là làm cho một Parcelable có thể mở rộng được. Ví dụ: hình ảnh mà những người triển khai thiết bị mong muốn có thể mở rộng Parcelable do AOSP xác định, AospDefinedParcelable , để bao gồm các tính năng giá trị gia tăng của chúng.

Trước đây không có ParcelableHolder , những người triển khai thiết bị không thể sửa đổi giao diện AIDL ổn định do AOSP xác định vì sẽ xảy ra lỗi khi thêm nhiều trường hơn:

parcelable AospDefinedParcelable {
  int a;
  String b;
  String x; // ERROR: added by a device implementer
  int[] y; // added by a device implementer
}

Như đã thấy trong đoạn mã trước, phương pháp này bị hỏng vì các trường được thêm bởi trình triển khai thiết bị có thể có xung đột khi Parcelable được sửa đổi trong các bản phát hành tiếp theo của Android.

Sử dụng ParcelableHolder , chủ sở hữu của một bưu kiện có thể xác định một điểm mở rộng trong Parcelable .

parcelable AospDefinedParcelable {
  int a;
  String b;
  ParcelableHolder extension;
}

Sau đó, những người triển khai thiết bị có thể xác định Parcelable của riêng họ cho tiện ích mở rộng của họ.

parcelable OemDefinedParcelable {
  String x;
  int[] y;
}

Cuối cùng, Parcelable mới có thể được đính kèm với Parcelable ban đầu thông qua trường ParcelableHolder .


// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;

ap.extension.setParcelable(op);

...

OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);

// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();

ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);

...

std::shared_ptr<OemDefinedParcelable> op_ptr;

ap.extension.getParcelable(&op_ptr);

// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);

...

std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);

// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });

ap.extension.set_parcelable(Rc::clone(&op));

...

let op = ap.extension.get_parcelable::<OemDefinedParcelable>();

Xây dựng dựa trên thời gian chạy AIDL

AIDL có ba phụ trợ khác nhau: Java, NDK, CPP. Để sử dụng AIDL ổn định, bạn phải luôn sử dụng bản sao hệ thống của libbinder tại system/lib*/libbinder.so và thảo luận về /dev/binder . Đối với mã trên hình ảnh nhà cung cấp, điều này có nghĩa là không thể sử dụng libbinder (từ VNDK): thư viện này có API C++ không ổn định và nội bộ không ổn định. Thay vào đó, mã nhà cung cấp gốc phải sử dụng phụ trợ NDK của AIDL, liên kết với libbinder_ndk (được hỗ trợ bởi hệ thống libbinder.so ) và liên kết với các thư viện -ndk_platform được tạo bởi các mục nhập aidl_interface .

Tên phiên bản máy chủ AIDL HAL

Theo quy ước, các dịch vụ AIDL HAL có tên phiên bản có định dạng $package.$type/$instance . Ví dụ: một phiên bản của bộ rung HAL được đăng ký là android.hardware.vibrator.IVibrator/default .

Viết máy chủ AIDL HAL

@VintfStability Các máy chủ AIDL phải được khai báo trong tệp kê khai VINTF, ví dụ như thế này:

    <hal format="aidl">
        <name>android.hardware.vibrator</name>
        <version>1</version>
        <fqname>IVibrator/default</fqname>
    </hal>

Nếu không, họ nên đăng ký dịch vụ AIDL bình thường. Khi chạy thử nghiệm VTS, tất cả các AIDL HAL đã khai báo đều khả dụng.

Viết ứng dụng khách AIDL

Máy khách AIDL phải tự khai báo trong ma trận tương thích, ví dụ như sau:

    <hal format="aidl" optional="true">
        <name>android.hardware.vibrator</name>
        <version>1-2</version>
        <interface>
            <name>IVibrator</name>
            <instance>default</instance>
        </interface>
    </hal>

Chuyển đổi HAL hiện có từ HIDL sang AIDL

Sử dụng công cụ hidl2aidl để chuyển đổi giao diện HIDL sang AIDL.

Các tính năng hidl2aidl :

  • Tạo tệp .aidl dựa trên tệp .hal cho gói đã cho
  • Tạo quy tắc xây dựng cho gói AIDL mới được tạo với tất cả các phụ trợ được bật
  • Tạo các phương thức dịch trong các chương trình phụ trợ Java, CPP và NDK để dịch từ các loại HIDL sang các loại AIDL
  • Tạo quy tắc xây dựng cho thư viện dịch với các phụ thuộc bắt buộc
  • Tạo các xác nhận tĩnh để đảm bảo rằng các điều tra viên HIDL và AIDL có cùng giá trị trong phần phụ trợ CPP và NDK

Làm theo các bước sau để chuyển đổi gói tệp .hal thành tệp .aidl:

  1. Xây dựng công cụ nằm trong system/tools/hidl/hidl2aidl .

    Xây dựng công cụ này từ nguồn mới nhất cung cấp trải nghiệm đầy đủ nhất. Bạn có thể sử dụng phiên bản mới nhất để chuyển đổi giao diện trên các nhánh cũ hơn từ các bản phát hành trước.

    m hidl2aidl
    
  2. Thực thi công cụ với một thư mục đầu ra theo sau là gói sẽ được chuyển đổi.

    Tùy chọn, sử dụng đối -l để thêm nội dung của tệp giấy phép mới vào đầu tất cả các tệp được tạo. Đảm bảo sử dụng đúng giấy phép và ngày tháng.

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    Ví dụ:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. Đọc qua các tệp đã tạo và khắc phục mọi sự cố với quá trình chuyển đổi.

    • conversion.log chứa bất kỳ sự cố nào chưa được xử lý cần khắc phục trước.
    • Các tệp .aidl đã tạo có thể có các cảnh báo và đề xuất cần hành động. Những nhận xét này bắt đầu bằng // .
    • Tận dụng cơ hội để dọn dẹp và cải thiện gói.
    • Kiểm tra chú thích @JavaDerive để biết các tính năng có thể cần thiết, chẳng hạn như toString hoặc equals .
  4. Chỉ xây dựng các mục tiêu bạn cần.

    • Vô hiệu hóa phụ trợ sẽ không được sử dụng. Thích chương trình phụ trợ NDK hơn chương trình phụ trợ CPP, xem phần chọn thời gian chạy .
    • Xóa các thư viện dịch hoặc bất kỳ mã nào được tạo của chúng sẽ không được sử dụng.
  5. Xem Những khác biệt chính về AIDL/HIDL .

    • Việc sử dụng Status tích hợp sẵn của AIDL và các ngoại lệ thường cải thiện giao diện và loại bỏ nhu cầu về loại trạng thái dành riêng cho giao diện khác.
    • Các đối số giao diện AIDL trong các phương thức không @nullable theo mặc định giống như trong HIDL.

Chính sách bảo mật cho AIDL HAL

Loại dịch vụ AIDL hiển thị với mã nhà cung cấp phải có thuộc tính hal_service_type . Mặt khác, cấu hình sepolicy giống như bất kỳ dịch vụ AIDL nào khác (mặc dù có các thuộc tính đặc biệt cho HAL). Dưới đây là một định nghĩa ví dụ về bối cảnh dịch vụ HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Đối với hầu hết các dịch vụ do nền tảng xác định, ngữ cảnh dịch vụ với loại chính xác đã được thêm vào (ví dụ: android.hardware.foo.IFoo/default sẽ được đánh dấu là hal_foo_service ). Tuy nhiên, nếu ứng dụng khách khung hỗ trợ nhiều tên phiên bản, thì các tên phiên bản bổ sung phải được thêm vào tệp service_contexts dành riêng cho thiết bị.

    android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0

Các thuộc tính HAL phải được thêm vào khi chúng tôi tạo một loại HAL mới. Một thuộc tính HAL cụ thể có thể được liên kết với nhiều loại dịch vụ (mỗi loại có thể có nhiều phiên bản như chúng ta vừa thảo luận). Đối với HAL, foo , chúng tôi có hal_attribute(foo) . Macro này xác định các thuộc tính hal_foo_clienthal_foo_server . Đối với một miền nhất định, macro hal_client_domainhal_server_domain liên kết miền với một thuộc tính HAL nhất định. Chẳng hạn, máy chủ hệ thống là máy khách của HAL này tương ứng với chính sách hal_client_domain(system_server, hal_foo) . Một máy chủ HAL tương tự bao gồm hal_server_domain(my_hal_domain, hal_foo) . Thông thường, đối với một thuộc tính HAL nhất định, chúng tôi cũng tạo một miền như hal_foo_default để tham khảo hoặc HAL mẫu. Tuy nhiên, một số thiết bị sử dụng các miền này cho máy chủ của riêng họ. Việc phân biệt giữa các miền cho nhiều máy chủ chỉ quan trọng nếu chúng ta có nhiều máy chủ phục vụ cùng một giao diện và cần một quyền khác được đặt trong quá trình triển khai của chúng. Trong tất cả các macro này, hal_foo thực sự không phải là một đối tượng riêng tư. Thay vào đó, mã thông báo này được sử dụng bởi các macro này để tham chiếu đến nhóm thuộc tính được liên kết với một cặp máy chủ máy khách.

Tuy nhiên, cho đến nay, chúng tôi chưa liên kết hal_foo_servicehal_foo (cặp thuộc tính từ hal_attribute(foo) ). Thuộc tính HAL được liên kết với các dịch vụ AIDL HAL bằng cách sử dụng macro hal_attribute_service (HIDL HALs sử dụng macro hal_attribute_hwservice ). Chẳng hạn, hal_attribute_service(hal_foo, hal_foo_service) . Điều này có nghĩa là các quy trình hal_foo_client có thể nhận được HAL và các quy trình hal_foo_server có thể đăng ký HAL. Việc thực thi các quy tắc đăng ký này được thực hiện bởi trình quản lý ngữ cảnh ( servicemanager ). Lưu ý, tên dịch vụ có thể không phải lúc nào cũng tương ứng với các thuộc tính HAL. Chẳng hạn, chúng ta có thể thấy hal_attribute_service(hal_foo, hal_foo2_service) . Tuy nhiên, nói chung, vì điều này có nghĩa là các dịch vụ luôn được sử dụng cùng nhau, nên chúng tôi có thể xem xét xóa hal_foo2_service và sử dụng hal_foo_service cho tất cả ngữ cảnh dịch vụ của mình. Hầu hết các HAL đặt nhiều hal_attribute_service là do tên thuộc tính HAL ban đầu không đủ chung chung và không thể thay đổi.

Đặt tất cả những thứ này lại với nhau, một ví dụ HAL trông như thế này:

    public/attributes:
    // define hal_foo, hal_foo_client, hal_foo_server
    hal_attribute(foo)

    public/service.te
    // define hal_foo_service
    type hal_foo_service, hal_service_type, protected_service, service_manager_type

    public/hal_foo.te:
    // allow binder connection from client to server
    binder_call(hal_foo_client, hal_foo_server)
    // allow client to find the service, allow server to register the service
    hal_attribute_service(hal_foo, hal_foo_service)
    // allow binder communication from server to service_manager
    binder_use(hal_foo_server)

    private/service_contexts:
    // bind an AIDL service name to the selinux type
    android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0

    private/<some_domain>.te:
    // let this domain use the hal service
    binder_use(some_domain)
    hal_client_domain(some_domain, hal_foo)

    vendor/<some_hal_server_domain>.te
    // let this domain serve the hal service
    hal_server_domain(some_hal_server_domain, hal_foo)

Giao diện mở rộng kèm theo

Tiện ích mở rộng có thể được gắn vào bất kỳ giao diện kết dính nào, cho dù đó là giao diện cấp cao nhất được đăng ký trực tiếp với trình quản lý dịch vụ hay đó là giao diện phụ. Khi nhận tiện ích mở rộng, bạn phải xác nhận loại tiện ích mở rộng như mong đợi. Tiện ích mở rộng chỉ có thể được đặt từ quy trình cung cấp chất kết dính.

Tiện ích mở rộng đính kèm nên được sử dụng bất cứ khi nào tiện ích mở rộng sửa đổi chức năng của HAL hiện có. Khi cần chức năng hoàn toàn mới, cơ chế này không cần được sử dụng và giao diện mở rộng có thể được đăng ký trực tiếp với người quản lý dịch vụ. Các giao diện tiện ích mở rộng được đính kèm có ý nghĩa nhất khi chúng được gắn vào các giao diện con, bởi vì các cấu trúc phân cấp này có thể sâu hoặc đa phiên bản. Việc sử dụng tiện ích mở rộng toàn cầu để phản ánh phân cấp giao diện liên kết của một dịch vụ khác sẽ yêu cầu sổ sách kế toán mở rộng để cung cấp chức năng tương đương cho các tiện ích mở rộng được đính kèm trực tiếp.

Để đặt tiện ích mở rộng trên chất kết dính, hãy sử dụng các API sau:

  • Trong phụ trợ NDK: AIBinder_setExtension
  • Trong chương trình phụ trợ Java: android.os.Binder.setExtension
  • Trong chương trình phụ trợ CPP: android::Binder::setExtension
  • Trong phụ trợ Rust: binder::Binder::set_extension

Để có tiện ích mở rộng trên bìa sách, hãy sử dụng các API sau:

  • Trong phụ trợ NDK: AIBinder_getExtension
  • Trong chương trình phụ trợ Java: android.os.IBinder.getExtension
  • Trong chương trình phụ trợ CPP: android::IBinder::getExtension
  • Trong phụ trợ Rust: binder::Binder::get_extension

Bạn có thể tìm thêm thông tin về các API này trong tài liệu về hàm getExtension trong phần phụ trợ tương ứng. Bạn có thể tìm thấy ví dụ về cách sử dụng tiện ích mở rộng trong phần cứng/giao diện/kiểm tra/tiện ích mở rộng/bộ rung .

Sự khác biệt chính giữa AIDL/HIDL

Khi sử dụng AIDL HAL hoặc sử dụng giao diện AIDL HAL, hãy lưu ý đến sự khác biệt so với cách viết HIDL HAL.

  • Cú pháp của ngôn ngữ AIDL gần với Java hơn. Cú pháp HIDL tương tự như C++.
  • Tất cả các giao diện AIDL đều có trạng thái lỗi tích hợp. Thay vì tạo các loại trạng thái tùy chỉnh, hãy tạo các int trạng thái không đổi trong tệp giao diện và sử dụng EX_SERVICE_SPECIFIC trong phần phụ trợ CPP/NDK và ServiceSpecificException trong phần phụ trợ Java. Xem Xử lý lỗi .
  • AIDL không tự động bắt đầu nhóm luồng khi các đối tượng kết dính được gửi. Chúng phải được bắt đầu thủ công (xem quản lý luồng ).
  • AIDL không hủy bỏ các lỗi vận chuyển không được kiểm tra (HIDL Return hủy bỏ các lỗi không được kiểm tra).
  • AIDL chỉ có thể khai báo một loại cho mỗi tệp.
  • Các đối số AIDL có thể được chỉ định là vào/ra/vào ngoài tham số đầu ra (không có "gọi lại đồng bộ").
  • AIDL sử dụng fd làm kiểu nguyên thủy thay vì xử lý.
  • HIDL sử dụng các phiên bản chính cho các thay đổi không tương thích và các phiên bản phụ cho các thay đổi tương thích. Trong AIDL, các thay đổi tương thích ngược được thực hiện tại chỗ. AIDL không có khái niệm rõ ràng về các phiên bản chính; thay vào đó, điều này được tích hợp vào tên gói. Chẳng hạn, AIDL có thể sử dụng tên gói bluetooth2 .
  • AIDL không kế thừa ưu tiên thời gian thực theo mặc định. Hàm setInheritRt phải được sử dụng cho mỗi trình kết nối để cho phép kế thừa mức độ ưu tiên theo thời gian thực.