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 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 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 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 như từ HAL này sang HAL khác, không có hạn chế 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 các ứ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ợ lập phiên bản tại chỗ cho chủ sở hữu của 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. Điều này có nghĩa là nó dễ dàng hơn để phiên bản mã qua nhiều năm và cũng như 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 mở rộng có thể được đính kèm trong thời gian chạy thay vì trong hệ thống kiểu, vì vậy không cần phải căn cứ lại các phần mở rộng hạ lưu 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

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

  • Mọi định nghĩa kiểu 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 ở 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 đã 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ó 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ước khi nó được gửi trên một đối tượng binder sang một quy 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ả các ứng dụng đều chạy trong ngữ cảnh hệ thống.

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

Lưu ý rằng việc sử dụng phần backends trong ví dụ mã bên dưới là đúng, vì có ba 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 để 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 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 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ì đ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 AOSP 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à điều đảm bảo hình ảnh GSI có thể tiếp tục hoạt động.

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

Tuy nhiên, một phần mở rộng đã được đăng ký, khi các thành phần dành riêng cho nhà cung cấp (có 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, không có khả năng xảy ra xung đột hợp nhất. Tuy nhiên, khi các sửa đổi hạ nguồn đối với các thành phần AOSP ngược dòng được thực hiện, có thể dẫn đến 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 ngược dòng 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 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

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

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ẽ có lỗi nếu 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 đoạn mã trước, phương pháp này bị hỏng vì các trường do người triển khai thiết bị thêm vào có thể có xung đột khi Parcelable được phiên bản lạ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 phần 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ừ VNDK): thư viện này có API C ++ không ổn định và nội dung 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ỗ 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ủa đị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

Máy chủ AIDL @VintfStability phải được khai báo 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ọ sẽ đăng ký dịch vụ AIDL một cách bình thường. Khi chạy các bài kiểm tra VTS, dự kiến ​​rằng tất cả các AIDL HAL đã được khai báo đều có sẵn.

Viết ứng dụng AIDL

Các ứng dụng 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 thành 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 phần 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 để dịch thư viện với các phụ thuộc bắt buộc
  • Tạo xác nhận tĩnh để đảm bảo rằng các trình kê khai 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 một 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 được chuyển đổi.

    hidl2aidl -o <output directory> <package>
    

    Ví dụ:

    hidl2aidl -o . android.hardware.nfc@1.2
    
  3. Đọc qua các tệp đã tạo và khắc phục bất kỳ sự cố nào với việc chuyển đổi.

    • conversion.log chứa bất kỳ vấn đề chưa được khắc phục nào cần khắc phục trước tiên.
    • Các tệp .aidl được 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 tiến gói.
  4. Chỉ xây dựng các mục tiêu bạn cần.

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

    • 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ề một loại trạng thái giao diện cụ thể khác.

Riêng biệt cho AIDL HALs

Loại dịch vụ AIDL hiển thị với mã nhà cung cấp phải có vendor_service tính nhà cung cấp dịch vụ. Nếu không, cấu hình riêng biệt giống với 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à định nghĩa ví dụ về ngữ cảnh dịch vụ HAL:

    type hal_foo_service, service_manager_type, vendor_service;

Đố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 đã được đánh dấu là hal_foo_service ). Tuy nhiên, nếu một ứ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 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

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 trường hợp 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 một thuộc tính HAL nhất định. Ví dụ: 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ộ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 chiếu hoặc ví dụ về HAL. Tuy nhiên, một số thiết bị sử dụng các miền này cho các 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ỉ quan trọng nếu chúng tôi có nhiều máy chủ phục vụ cùng một giao diện và cần một bộ quyền khác trong việc triển khai chúng. Trong tất cả các macro này, hal_foo không thực sự 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 để tham chiếu đến nhóm thuộc tính được liên kết với một cặp máy chủ khách hàng.

Tuy nhiên, cho đến nay, chúng tôi vẫn 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 HAL 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ể nắm giữ 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. Ví dụ: chúng ta có thể thấy hal_attribute_service(hal_foo, hal_foo2_service) . Mặc dù vậy, 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, chúng tôi có thể xem xét loại bỏ hal_foo2_service và sử dụng hal_foo_service cho tất cả các ngữ cảnh dịch vụ của chúng tôi. 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 được.

Kết hợp tất cả những điều 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, vendor_service, 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_call(hal_foo_server, servicemanager)

    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
    binder_use(some_hal_server_domain)
    hal_server_domain(some_hal_server_domain, hal_foo)

Các giao diện tiện ích mở rộng đính kèm

Một phần mở rộng có thể được đính kèm vào bất kỳ giao diện chất 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 được 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. Các tiện ích mở rộng chỉ có thể được đặt từ quy trình cung cấp chất kết dính.

Các tiện ích mở rộng được đí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 có 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 phụ, bởi vì các cấu trúc phân cấp này có thể sâu hoặc nhiều 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 kế toán mở rộng để cung cấp chức năng tương đương với 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

Để có được một tiện ích mở rộng trên một 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

Bạn có thể tìm thêm thông tin cho các API này trong tài liệu của hàm getExtension trong phần phụ trợ tương ứng. 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 / thử nghiệm / 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 ý những điểm 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 sẵn. 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 phân luồng khi các đối tượng chất kết dính được gửi đi. Chúng phải được khởi động theo cách thủ công (xem phần quản lý luồng ).
  • AIDL không hủy bỏ đối với các lỗi vận chuyển không được kiểm tra (HIDL Return hủy bỏ đối với các lỗi chưa được kiểm tra).
  • AIDL chỉ có thể khai báo một kiểu cho mỗi tệp.
  • Các đối số AIDL có thể được chỉ định là in / out / inout 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ì 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 nhỏ 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 kết hợp vào tên gói. Ví dụ: AIDL có thể sử dụng tên gói bluetooth2 .