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 có thể triển khai các phần của Android mà không cần HIDL. Chuyển đổi HAL sang sử dụng AIDL riêng nếu có thể (khi HAL ngược dòng sử dụng HIDL thì phải sử dụng HIDL).

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, chẳng hạn từ HAL này sang HAL khác, không có hạn chế nào đối với việc sử dụng cơ chế IPC.

Độ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 đã hỗ trợ tính ổn định, nên 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 phần có thể phân chia được. Điều này có nghĩa là việc tạo mã phiên bản qua nhiều năm sẽ dễ dàng hơn và chi phí qua từ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 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 lê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

Để sử dụng giao diện AIDL 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 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. 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 được phát hành đã bị đóng băng) bằng cách sử dụng thử nghiệm VTS vts_treble_vintf_vendor_test . Bạn có thể sử dụng giao diện @VintfStability mà không cần những 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 nó được gửi sang tiến trình khác. Việc hạ cấp dịch vụ xuống mức ổn định của nhà cung cấp không được hỗ trợ trong Java vì tất cả ứng dụng đều chạy trong ngữ cảnh hệ thống.

Ngoài ra, để có khả năng di chuyển mã tối đa 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à đúng vì có ba phần phụ trợ (Java, NDK và CPP). Mã bên dưới cho biết cách chọn cụ thể phần phụ trợ CPP để vô hiệu hóa nó.

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

Tìm giao diện AIDL HAL

AOSP Giao diện AIDL ổn định cho HAL nằm trong cùng thư mục cơ sở với 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 đố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 những 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, phần 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 thì sẽ không có khả năng xung đột hợp nhất. Tuy nhiên, khi thực hiện 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ể xảy ra xung đột hợp nhất và các chiến lược sau được khuyến nghị:

  • các bổ sung giao diện có thể được cập nhật lên AOSP trong phiên bản tiếp theo
  • các 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 cập nhật trong phiên bản 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à tạo ra một Parcelable có thể mở rộng được. Ví dụ: hình ảnh mà người triển khai thiết bị mong đợi 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 họ.

Trước đây nếu không có ParcelableHolder , 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ẽ là lỗi nếu 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ị lỗi do các trường được người 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 tiếp theo của Android.

Bằng cách 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 một Parcelable .

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

Sau đó, 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 gắn vào 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ần 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à nói chuyện trên /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ừ VNĐK): 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ần phụ trợ NDK của AIDL, liên kết với libbinder_ndk (được hệ thống hỗ trợ 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 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 một cách bình thường. Khi chạy thử nghiệm VTS, dự kiến ​​tất cả AIDL HAL đã khai báo đều khả dụng.

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

Các 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 chương trình phụ trợ được 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 xây dựng cho thư viện dịch 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 bộ liệt kê HIDL và AIDL có cùng giá trị 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. 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 mang lại 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 phiên bản trước.

    m hidl2aidl
    
  2. Thực thi công cụ với thư mục đầu ra, theo sau là gói cần 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. Hãy chắc chắn 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 được tạo và khắc phục mọi sự cố với quá trình chuyển đổi.

    • conversion.log chứa mọi vấn đề chưa được xử lý cần khắc phục trước tiên.
    • Các tệp .aidl được tạo có thể chứa các cảnh báo và đề xuất cần thực hiện. Những bình luận này bắt đầu bằng // .
    • Tận dụng cơ hội để dọn dẹp và cải tiế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 các chương trình phụ trợ không được sử dụng. Ưu tiên phần phụ trợ NDK hơn phần phụ trợ CPP, xem cách chọn thời gian chạy .
    • Xóa thư viện dịch hoặc bất kỳ mã nào được tạo mà 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 và ngoại lệ tích hợp sẵn của AIDL 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 theo mặc định không @nullable giống như trong HIDL.

Chính sách riêng biệ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 chính sách cũng 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 dành 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ụ có loại đúng đã đượ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 máy khách khung hỗ trợ nhiều tên phiên bản thì 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

Thuộc tính HAL phải được thêm vào khi chúng ta 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 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 miền với 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) . Tương tự, máy chủ HAL 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 làm mẫu HAL. 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 đặt quyền khác nhau khi triển khai 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 biệt. Thay vào đó, mã thông báo này được các macro này sử dụng để chỉ nhóm thuộc tính được liên kết với cặp máy chủ 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 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ể 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ý bối 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 thuộc tính HAL. Ví dụ: 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 ngụ ý rằng 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 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)

Các giao diện mở rộng kèm theo

Tiện ích mở rộng có thể được đính kèm 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 người 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. Chỉ có thể đặt tiện ích mở rộng từ quy trình cung cấp chất kết dính.

Nên sử dụng các tiện ích mở rộng đính kèm 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, không cần sử dụng cơ chế này và có thể đăng ký trực tiếp giao diện mở rộng với người quản lý dịch vụ. Các giao diện 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 phụ, vì các hệ thống 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 hệ thống 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 ghi sổ kế toán rộng rãi để 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ần phụ trợ 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

Để có 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ần phụ trợ 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ể 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 về 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 việc 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 số nguyên 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. Xem Xử lý lỗi .
  • AIDL không tự động khởi động threadpool khi các đối tượng liên kết được gửi. Chúng phải được khởi động thủ công (xem quản lý luồng ).
  • AIDL không hủy bỏ các lỗi truyền tải 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.
  • Đối số AIDL có thể được chỉ định là vào/ra/vào ngoài tham số đầu ra (không có "lệnh gọi lại đồng bộ").
  • AIDL sử dụng fd làm kiểu nguyên thủy thay vì kiểu xử lý.
  • HIDL sử dụng các phiên bản chính cho những thay đổi không tương thích và các phiên bản nhỏ cho những 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 kết 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 mức độ ưu tiên thời gian thực theo mặc định. Hàm setInheritRt phải được sử dụng cho mỗi chất kết dính để cho phép kế thừa ưu tiên theo thời gian thực.