AIDL cho HAL

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

HAL (Lớp trừu tượng phần cứng) sử dụng AIDL để giao tiếp giữa các thành phần khung, chẳng hạn như 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ừ một HAL sang một giao thức khác, không có hạn chế nào về cơ chế IPC có thể 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ư như giữa các thành phần khung Android hoặc trong ứng dụng. Giờ đây, AIDL đã có tính ổn định , bạn có thể triển khai toàn bộ ngăn xếp bằng một thời gian chạy IPC. 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 nghĩa là bạn chỉ cần tìm hiểu, gỡ lỗi tối ưu hoá 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 trường vào Parceable. Điều này có nghĩa là sẽ dễ dàng tạo mã phiên bản hơn trong những năm và cả năm chi phí so với cùng kỳ năm trước nhỏ hơn (có thể sửa đổi loại tại chỗ và không có thư viện bổ sung cho mỗi phiên bản giao diện).
    • Giao diện tiện ích có thể được đính kèm trong thời gian chạy thay vì theo loại hệ thống của mình, vì vậy, bạn không cần đặt lại tiện ích hạ nguồn cho các tiện ích mới hơn các phiên bản giao diện.
  • Giao diện AIDL hiện có có thể được sử dụng trực tiếp khi chủ sở hữu giao diện đó chọn ổn định thiết bị đó. Trước đây, toàn bộ bản sao giao diện sẽ phải được tạo trong HIDL.

Tạo dựa trên thời gian chạy AIDL

AIDL có 3 phần phụ trợ: Java, NDK, CPP. Để sử dụng AIDL chính thức, 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à trò chuyện vào ngày /dev/binder. Đối với mã trên ảnh nhà cung cấp, điều này có nghĩa là libbinder Không thể sử dụng (từ VNDK): thư viện này có API C++ không ổn định và bên trong không ổn định. Thay vào đó, mã của nhà cung cấp gốc phải sử dụng phần phụ trợ NDK của AIDL, liên kết với libbinder_ndk (được hệ thống libbinder.so hỗ trợ), và liên kết dựa trên các thư viện NDK do các mục nhập aidl_interface tạo. Cho tên mô-đun chính xác, xem quy tắc đặt tên mô-đun.

Viết giao diện AIDL HAL

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

  • Mọi định nghĩa kiểu đều phải được chú thích bằng @VintfStability.
  • Nội dung 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 ở trong Tệp kê khai VINTF để có thể hoạt động. Kiểm tra điều này (và các liên quan theo yêu cầu, chẳng hạn như xác minh rằng giao diện đã phát hành bị treo) bằng cách sử dụng Xét nghiệm VTS vts_treble_vintf_vendor_test. Bạn có thể dùng @VintfStability không có 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 một đối tượng liên kết trước khi đối tượng đó được gửi đến một quy trình khác. Hạ cấp dịch vụ đối với độ ổ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 một hệ thống ngữ cảnh.

Ngoài ra, để tối đa hoá khả năng di chuyển mã và tránh những vấn đề tiềm ẩn như như các thư viện bổ sung không cần thiết, hãy vô hiệu hoá phần phụ trợ CPP.

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

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

Tìm giao diện AIDL HAL

Giao diện AIDL ổn định của AOSP (Dự án nguồn mở Android) cho HAL (Lớp trừu tượng phần cứng) nằm trong cùng thư mục cơ sở như Giao diện HIDL, trong 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 giao diện tiện ích vào hardware/interfaces khác các thư mục con trong vendor hoặc hardware.

Giao diện tiện ích

Android có một bộ giao diện AOSP chính thức với mỗi bản phát hành. Khi Android các đối tác muốn thêm chức năng vào các giao diện này thì họ không nên thay đổi trực tiếp 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 môi trường thời gian chạy Android của AOSP (Dự án nguồn mở Android). Đối với thiết bị GMS, tránh thay đổi những giao diện này cũng là yếu tố đảm bảo hình ảnh GSI có thể tiếp tục hoạt động.

Các tiện ích có thể đăng ký theo hai cách:

Tuy nhiên, tiện ích được đăng ký, khi tiện ích dành riêng cho nhà cung cấp (có nghĩa là không phải là một phần của các thành phần AOSP ngược dòng (upstream) sử dụng giao diện, không thể hợp nhất xung đột. Tuy nhiên, khi sửa đổi hạ nguồn đối với các thành phần AOSP ngược dòng (upstream) có thể xảy ra, 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 phần bổ sung giao diện có thể được tải lên AOSP trong bản phát hành tiếp theo
  • bổ sung giao diện cho phép linh hoạt hơn mà không có xung đột hợp nhất, có thể được cải tiến trong bản phát hành tiếp theo

ParcelableHolder của phần mở rộng

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à giúp Parcelable có thể mở rộng. Ví dụ: hình ảnh mà người triển khai thiết bị muốn có thể mở rộng Parcelable, AospDefinedParcelable do AOSP xác định, để bao gồm giá trị gia tăng của chúng các tính năng AI mới.

Trước đây nếu không có ParcelableHolder, trình 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ẽ có lỗi nếu thêm giao diện khác trường:

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 mã trước, phương pháp này bị lỗi vì các trường do trình triển khai thiết bị thêm vào có thể có xung đột khi Parcelable được được sửa đổi trong bản phát hành Android tiếp theo.

Bằng cách sử dụng ParcelableHolder, chủ sở hữu của một gói có thể xác định một tiện ích điểm trong Parcelable.

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

Sau đó, trình triển khai thiết bị có thể xác định Parcelable riêng cho tiện ích.

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

Cuối cùng, bạn có thể đính kèm Parcelable mới vào Parcelable gốc bằng 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>();

Tên thực thể máy chủ AIDL HAL

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

Viết máy chủ HAL AIDL

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

    <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 như bình thường. Khi chạy VTS thường thì tất cả các lớp trừu tượng phần cứng (HAL) AIDL đã khai báo đều có sẵn.

Viết một ứng dụng AIDL

Ứng dụng 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

Dùng công cụ hidl2aidl để chuyển đổi giao diện HIDL thành AIDL.

Tính năng của hidl2aidl:

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

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

  1. Tạo công cụ nằm trong system/tools/hidl/hidl2aidl.

    Việc tạo công cụ này từ nguồn mới nhất sẽ cung cấp của bạn. Bạn có thể dùng phiên bản mới nhất để chuyển đổi giao diện trên nhánh từ bản phát hành trước.

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

    Bạn có thể dùng đối số -l để thêm nội dung của tệp giấy phép mới (không bắt buộc) lên đầu tất cả các tệp đã tạo. Hãy nhớ 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 các tệp đã tạo và khắc phục mọi vấn đề về lượt chuyển đổi.

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

    • Tắt những phần phụ trợ sẽ không dùng đến. Ưu tiên phần phụ trợ của NDK hơn CPP phần phụ trợ, hãy xem phần chọn thời gian chạy.
    • Xoá các thư viện dịch hoặc bất kỳ mã nào đã tạo trong đó mà sẽ không dùng đến.
  5. Xem Những điểm khác biệt chính về AIDL/HIDL.

    • Việc sử dụng Status được 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à không cần phải có một loại trạng thái giao diện cụ thể khác.
    • Đối số giao diện AIDL trong các phương thức không phải là @nullable theo mặc định giống như ở HIDL.

SEPolicy cho HAL AIDL

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. Nếu không, cấu hình sepolicy sẽ giống nhau 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 dành cho HAL). Ở đây là định nghĩa mẫu về ngữ 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ụ có thông tin chính xác loại đã đượ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 khung hỗ trợ nhiều tên thực thể, bạn phải thêm tên thực thể bổ sung vào service_contexts dành riêng cho thiết bị.

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

Bạn phải thêm các thuộc tính HAL khi tạo một loại HAL mới. Một HAL cụ thể có thể được liên kết với nhiều loại dịch vụ (mỗi loại dịch vụ có thể có nhiều trường hợp 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 cụ thể, hal_client_domain và Macro hal_server_domain liên kết miền với một thuộc tính HAL nhất định. Cho ví dụ: máy chủ hệ thống là ứng dụng khách của HAL này tương ứng với chính sách hal_client_domain(system_server, hal_foo). Máy chủ HAL còn có chức năng tương tự hal_server_domain(my_hal_domain, hal_foo). Thông thường, đối với một HAL (Lớp trừu tượng phần cứng) nhất định , chúng ta cũng sẽ tạo một miền như hal_foo_default để tham chiếu hoặc ví dụ về HAL (Lớp trừu tượng phần cứng). 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 chúng. Việc phân biệt giữa các tên miền cho nhiều máy chủ chỉ quan trọng nếu chúng ta có nhiều máy chủ phân phát cùng một giao diện và cần quyền khác đặt trong quá trình triển khai. Trong tất cả các macro này, hal_foo không thực sự đối tượng sepolicy. Thay vào đó, mã thông báo này được các macro này sử dụng để tham chiếu đến nhóm thuộc tính được liên kết với một cặp máy chủ ứng dụng.

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)). Có liên kết với thuộc tính HAL với các dịch vụ HAL AIDL sử dụng macro hal_attribute_service (sử dụng HAL HIDL macro hal_attribute_hwservice). Ví dụ: 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ể tuân thủ HAL và hal_foo_server có thể đăng ký HAL. Việc thực thi các quy tắc đăng ký này do trình quản lý ngữ cảnh (servicemanager) thực hiện. Lưu ý rằng tên dịch vụ có thể không phải lúc nào cũng tương ứng với thuộc tính HAL. Ví dụ: chúng ta có thể thấy hal_attribute_service(hal_foo, hal_foo2_service). Mặc dù vậy, nhìn chung, vì điều này ngụ ý rằng các dịch vụ luôn được sử dụng cùng nhau, chúng ta có thể xem xét việc xoá hal_foo2_service và sử dụng hal_foo_service cho tất cả các dịch vụ của chúng tôi ngữ cảnh. Hầu hết các HAL (Lớp trừu tượng phần cứng) đặt nhiều hal_attribute_service là vì tên thuộc tính HAL (Lớp trừu tượng phần cứng) ban đầu không đủ khái quát và không thể thay đổi được.

Kết hợp tất cả lại với nhau, HAL (Lớp trừu tượng phần cứng) ví dụ sẽ có dạng như sau:

    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 tiện ích đi kèm

Có thể đính kèm một tiện ích vào bất kỳ giao diện liên kết 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ụ hoặc đó là một giao diện phụ. Khi tải tiện ích, bạn phải xác nhận loại tiện ích là như dự kiến. Bạn chỉ có thể thiết lập tiện ích trong quá trình phân phát một liên kết.

Bạn nên sử dụng các tiện ích đính kèm mỗi khi một tiện ích 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, không cần sử dụng cơ chế này, đồng thời bạn có thể sử dụng giao diện mở rộng đã đăng ký trực tiếp với nhà quản lý dịch vụ. Giao diện tiện ích đi kèm hợp lý nhất khi chúng được đính kèm vào các giao diện phụ, vì hệ thống phân cấp có thể sâu hoặc nhiều phiên bản. Sử dụng tiện ích chung để phản chiếu hệ thống phân cấp giao diện liên kết của một dịch vụ khác sẽ đòi hỏi rất nhiều công việc kế toán để cung cấp chức năng tương đương cho các tiện ích được đính kèm trực tiếp.

Để thiết lập một tiện ích trên liên kết, hãy sử dụng các API sau:

  • Trong phần phụ trợ của NDK: AIBinder_setExtension
  • Trong phần phụ trợ Java: android.os.Binder.setExtension
  • Trong phần phụ trợ CPP: android::Binder::setExtension
  • Trong phần phụ trợ Rust: binder::Binder::set_extension

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

  • Trong phần phụ trợ của NDK: AIBinder_getExtension
  • Trong phần phụ trợ Java: android.os.IBinder.getExtension
  • Trong phần phụ trợ CPP: android::IBinder::getExtension
  • Trong phần 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. Ví dụ về cách sử dụng có thể tìm thấy tiện ích trong phần cứng/giao diện/kiểm thử/tiện ích/bộ rung.

Những điểm khác biệt chính giữa AIDL và HIDL

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

  • Cú pháp của ngôn ngữ AIDL gần giống với Java hơn. Cú pháp HIDL tương tự như C++.
  • Tất cả giao diện AIDL đều tích hợp sẵn trạng thái lỗi. Thay vì tạo nhóm quảng cáo tuỳ chỉnh loại trạng thái, tạo số nguyên 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 Lỗi Xử lý.
  • AIDL không tự động khởi động nhóm luồng khi các đối tượng liên kết được gửi. Bạn phải bắt đầu các quy trình này theo cách thủ công (xem chuỗi quản lý chiến dịch).
  • AIDL không huỷ khi lỗi truyền tải không được kiểm tra (HIDL Return huỷ bật lỗi chưa đánh dấu).
  • 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/ra ngoài cùng với dữ liệu đầu ra (không có "lệnh gọi lại đồng bộ").
  • AIDL sử dụng fd làm loại nguyên gốc thay vì tên người dùng.
  • HIDL dùng phiên bản lớn cho những thay đổi không tương thích và phiên bản nhỏ cho các thay đổi tương thích. Trong AIDL, những thay đổi có khả năng tương thích ngược sẽ được thực hiện. AIDL không có khái niệm rõ ràng về các phiên bản lớn; thay vào đó, đây là được tích hợp vào tên gói. Ví dụ: AIDL có thể sử dụng tên gói bluetooth2.
  • Theo mặc định, AIDL không kế thừa mức độ ưu tiên theo thời gian thực. setInheritRt hàm phải được sử dụng trên mỗi liên kết để bật tính năng kế thừa ưu tiên theo thời gian thực.