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. Nhờ vậy, bạn 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 độc quyền nếu 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ư các thành phần trong system.img) và các thành phần phần cứng (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 (Lớp trừu tượng phần cứng) khác, sẽ 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 vị trí khác, chẳng hạn như giữa các thành phần khung Android hoặc trong ứng dụng. Vì AIDL đã hỗ trợ độ ổn định, nên bạn có thể triển khai toàn bộ ngăn xếp bằng 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.

  • Việc sử dụng một ngôn ngữ IPC duy nhất có nghĩa là bạn chỉ có một thứ để 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. Tức là bạn sẽ dễ dàng tạo mã phiên bản qua nhiều năm và chi phí cũng giảm so với cùng kỳ năm trước (có thể sửa đổi các loại tại chỗ và không cần thêm thư viện cho từng phiên bản giao diện).
    • Bạn có thể đính kèm giao diện mở rộng trong thời gian chạy thay vì trong hệ thống kiểu. Do đó, bạn không cần phải thiết lập lại cơ sở dữ liệu cho các tiện ích hạ nguồn lên các phiên bản giao diện mới hơn.
  • Bạn có thể sử dụng trực tiếp giao diện AIDL hiện có khi chủ sở hữu chọn ổn định giao diện đó. Trước đây, toàn bộ một bản sao của 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à trao đổi trên /dev/binder. Đối với mã trên hình ảnh nhà cung cấp, bạn không thể sử dụng libbinder (từ VNDK): thư viện này có API C++ không ổn định và các thành phần bên trong không ổn định. Thay vào đó, mã 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 với các thư viện NDK do các mục nhập aidl_interface tạo. Để biết tên chính xác của mô-đun, hãy 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 2 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 nằm trong tệp kê khai VINTF để hoạt động. Hãy kiểm thử đ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ị treo) bằng cách sử dụng quy trình kiểm thử VTS vts_treble_vintf_vendor_test. Bạn có thể sử dụng giao diện @VintfStability mà không cần đế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 liên kết trước khi đối tượng này được gửi đến một quy trình khác. Java không hỗ trợ hạ cấp một dịch vụ xuống độ ổn định của nhà cung cấp vì tất cả các ứng dụng đều chạy trong ngữ cảnh hệ thống.

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

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

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

Tìm giao diện AIDL HAL

Các giao diện AIDL ổn định của AOSP (Dự án nguồn mở Android) cho HAL đều nằm trong các thư mục cơ sở giống như 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 giao diện tiện ích vào các thư mục con hardware/interfaces khác 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 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 thay đổi trực tiếp các giao diện này vì như vậy có nghĩa là môi trường thời gian chạy Android của họ không tương thích với thời gian chạy Android của AOSP (Dự án nguồn mở Android). Đố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à 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, một tiện ích đượ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 thực hiện sửa đổi hạ nguồn cho các thành phần AOSP (Dự án nguồn mở Android) ngược dòng, xung đột hợp nhất có thể xảy ra và bạn nên áp dụng các chiến lược sau:

  • 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
  • các 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ập nhật 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à trình triển khai thiết bị mong muốn có thể mở rộng Parcelable, AospDefinedParcelable do AOSP xác định, để bao gồm các tính năng thêm giá trị.

Trước đây nếu không có ParcelableHolder, trình triển khai thiết bị sẽ 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 các trường khác:

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 sửa đổi trong các bản phát hành Android tiếp theo.

Khi sử dụng ParcelableHolder, chủ sở hữu của một gói có thể xác định một điểm mở rộng 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 của mình.

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ể theo định dạng $package.$type/$instance. Ví dụ: một thực thể của bộ rung HAL được đăng ký là android.hardware.vibrator.IVibrator/default.

Viết máy chủ HAL AIDL

Bạn phải khai báo @VintfStability máy chủ AIDL trong tệp kê khai VINTF, 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 kiểm thử VTS, dự kiến phải có tất cả các HAL AIDL đã khai báo.

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, chẳng hạn 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 khi tất cả phần phụ trợ đang bật
  • Tạo các phương thức dịch trong phần phụ trợ Java, CPP và NDK để dịch từ các kiểu HIDL sang các kiểu 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 các liệt kê HIDL và AIDL có cùng giá trị trong các 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ẽ mang lại trải nghiệm hoàn chỉnh 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ũ từ bản phát hành trước.

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

    Bạn có thể sử dụng đối số -l để thêm nội dung của tệp giấy phép mới vào đầu tất cả tệp được tạo (không bắt buộc). 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ần thực hiệ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 chú giải @JavaDerive cho 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ợ NDK thay vì phần phụ trợ CPP, 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 tích hợp sẵn của AIDL và các ngoại lệ thường giúp cải thiện giao diện và không cần phải sử dụng một loại trạng thái cụ thể cho giao diện khác.
    • Theo mặc định, đối số giao diện AIDL trong các phương thức không phải là @nullable giống như các đối số trong 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 cũng giống như mọi dịch vụ AIDL khác (mặc dù có các thuộc tính đặc biệt cho HAL). Dưới đây là định nghĩa mẫu cho ngữ cảnh dịch vụ HAL:

    type hal_foo_service, service_manager_type, hal_service_type;

Đối với hầu hết dịch vụ do nền tảng xác định, ngữ cảnh dịch vụ với đúng loại đã được thêm vào (ví dụ: android.hardware.foo.IFoo/default đã được đánh dấu là hal_foo_service). Tuy nhiên, nếu một ứng dụng khung hỗ trợ nhiều tên thực thể, thì bạn phải thêm tên thực thể bổ sung trong các 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

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 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 dịch vụ có thể có nhiều thực thể như chúng ta vừa thảo luận). Đối với HAL, foo, chúng ta 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 một miền với một thuộc tính HAL nhất định. Ví dụ: máy chủ hệ thống là một ứng dụng 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ũng bao gồm hal_server_domain(my_hal_domain, hal_foo) theo cách tương tự. 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 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 miền cho nhiều máy chủ chỉ có ý nghĩa 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 có tập hợp quyền khác nhau khi triển khai các máy chủ đó. Trong tất cả các macro này, hal_foo thực sự không phải là đối tượng sepolicy. Thay vào đó, các macro này dùng mã thông báo này để tham chiếu đến nhóm thuộc tính 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)). Thuộc tính HAL được liên kết với các dịch vụ HAL AIDL bằng cách sử dụng macro hal_attribute_service (HAL HIDL sử dụng 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ể sử dụng HAL và các quy trình hal_foo_server có thể đăng ký HAL. Trình quản lý ngữ cảnh (servicemanager) sẽ thực thi các quy tắc đăng ký này. Lưu ý: không phải lúc nào tên dịch vụ cũng tương ứng với các thuộc tính HAL. Ví dụ như chúng ta có thể thấy hal_attribute_service(hal_foo, hal_foo2_service). 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, nên chúng ta có thể cân nhắc việc xoá hal_foo2_service và sử dụng hal_foo_service cho mọi ngữ cảnh dịch vụ. Hầu hết HAL (Lớp trừu tượng phần cứng) đặt nhiều hal_attribute_service là do 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

Bạn 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ụ hay là một giao diện phụ. Khi tải một tiện ích, bạn phải xác nhận loại tiện ích là 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 có 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, bạn không cần sử dụng cơ chế này và bạn có thể đăng ký giao diện tiện ích trực tiếp với trình quản lý dịch vụ. Các giao diện tiện ích đính kèm sẽ hợp lý nhất khi chúng được đính kèm với các giao diện phụ, vì các hệ phân cấp này có thể sâu hoặc có nhiều phiên bản. Việc sử dụng tiện ích chung để phản ánh hệ 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 đượ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. Bạn có thể xem ví dụ về cách sử dụng tiện ích trong phần 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 HAL AIDL hoặc sử dụng giao diện AIDL HAL, hãy lưu ý những điểm 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 các loại trạng thái tuỳ chỉnh, hãy tạo int trạng thái không đổi trong các 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. Hãy xem phần Xử lý lỗi.
  • 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 quá trình này theo cách thủ công (xem phần quản lý luồng).
  • AIDL không huỷ khi các lỗi truyền tải chưa được đánh dấu (HIDL Return sẽ huỷ khi có các lỗi chưa được đánh dấu).
  • AIDL chỉ có thể khai báo một loại cho mỗi tệp.
  • Ngoài tham số đầu ra, các đối số AIDL có thể được chỉ định là in/out/out (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 sử dụng các 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 đó, định dạng này được đưa 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. Hàm setInheritRt phải được dùng cho mỗi liên kết để bật tính năng kế thừa mức độ ưu tiên theo thời gian thực.