AIDL ổn định

Android 10 bổ sung tính năng hỗ trợ Ngôn ngữ định nghĩa giao diện Android (AIDL) ổn định, một cách mới để theo dõi giao diện chương trình ứng dụng (API) và giao diện nhị phân của ứng dụng (ABI) do các giao diện AIDL cung cấp. AIDL ổn định hoạt động giống hệt như AIDL, nhưng hệ thống xây dựng theo dõi khả năng tương thích của giao diện và có những hạn chế đối với những việc bạn có thể làm:

  • Các giao diện được xác định trong hệ thống xây dựng bằng aidl_interfaces.
  • Giao diện chỉ có thể chứa dữ liệu có cấu trúc. Parcelable đại diện cho các loại ưu tiên được tạo tự động dựa trên định nghĩa AIDL của chúng và được tự động chuyển đổi và huỷ chuyển đổi.
  • Bạn có thể khai báo các giao diện là ổn định (tương thích ngược). Khi điều này xảy ra, API của chúng sẽ được theo dõi và phân phiên bản trong một tệp bên cạnh giao diện AIDL.

AIDL có cấu trúc so với AIDL ổn định

AIDL có cấu trúc đề cập đến các loại được xác định hoàn toàn trong AIDL. Ví dụ: khai báo có thể chuyển đổi thành gói (một gói tuỳ chỉnh có thể chuyển đổi) không phải là AIDL có cấu trúc. Parcelable có các trường được xác định trong AIDL được gọi là parcelable có cấu trúc.

AIDL ổn định yêu cầu AIDL có cấu trúc để hệ thống bản dựng và trình biên dịch có thể hiểu liệu các thay đổi đối với các đối tượng có thể đóng gói có tương thích ngược hay không. Tuy nhiên, không phải tất cả giao diện có cấu trúc đều ổn định. Để ổn định, một giao diện chỉ được dùng các loại có cấu trúc và cũng phải dùng các tính năng lập phiên bản sau. Ngược lại, một giao diện sẽ không ổn định nếu hệ thống xây dựng cốt lõi được dùng để tạo giao diện đó hoặc nếu unstable:true được đặt.

Xác định giao diện AIDL

Định nghĩa về aidl_interface sẽ có dạng như sau:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name: Tên của mô-đun giao diện AIDL giúp xác định riêng một giao diện AIDL.
  • srcs: Danh sách các tệp nguồn AIDL tạo nên giao diện. Đường dẫn cho một loại AIDL Foo được xác định trong gói com.acme phải nằm ở <base_path>/com/acme/Foo.aidl, trong đó <base_path> có thể là bất kỳ thư mục nào liên quan đến thư mục chứa Android.bp. Trong ví dụ trước, <base_path>srcs/aidl.
  • local_include_dir: Đường dẫn bắt đầu từ tên gói. Nó tương ứng với <base_path> được giải thích ở trên.
  • imports: Danh sách các mô-đun aidl_interface mà mô-đun này sử dụng. Nếu một trong các giao diện AIDL của bạn sử dụng một giao diện hoặc một đối tượng có thể chuyển đổi thành gói từ aidl_interface khác, hãy đặt tên của giao diện hoặc đối tượng đó vào đây. Đây có thể là tên của chính phiên bản đó để chỉ phiên bản mới nhất, hoặc tên có hậu tố phiên bản (chẳng hạn như -V1) để chỉ một phiên bản cụ thể. Việc chỉ định phiên bản đã được hỗ trợ kể từ Android 12
  • versions: Các phiên bản trước của giao diện được cố định trong api_dir. Kể từ Android 11, versions được cố định trong aidl_api/name. Nếu không có phiên bản cố định nào của một giao diện, thì bạn không nên chỉ định phiên bản đó và sẽ không có các bước kiểm tra khả năng tương thích. Trường này đã được thay thế bằng versions_with_info cho Android 13 trở lên.
  • versions_with_info: Danh sách các bộ, mỗi bộ chứa tên của một phiên bản cố định và một danh sách có các lượt nhập phiên bản của các mô-đun aidl_interface khác mà phiên bản aidl_interface này đã nhập. Định nghĩa của phiên bản V của giao diện AIDL IFACE nằm tại aidl_api/IFACE/V. Trường này được ra mắt trong Android 13 và không được phép sửa đổi trực tiếp trong Android.bp. Trường này được thêm hoặc cập nhật bằng cách gọi *-update-api hoặc *-freeze-api. Ngoài ra, các trường versions sẽ tự động được di chuyển sang versions_with_info khi người dùng gọi *-update-api hoặc *-freeze-api.
  • stability: Cờ không bắt buộc cho cam kết về tính ổn định của giao diện này. Tính năng này chỉ hỗ trợ "vintf". Nếu stability chưa được đặt, hệ thống bản dựng sẽ kiểm tra để đảm bảo giao diện tương thích ngược, trừ phi bạn chỉ định unstable. Trạng thái chưa đặt tương ứng với một giao diện có độ ổn định trong ngữ cảnh biên dịch này (vì vậy, tất cả các thành phần hệ thống, ví dụ: các thành phần trong system.img và các phân vùng liên quan, hoặc tất cả các thành phần của nhà cung cấp, ví dụ: các thành phần trong vendor.img và các phân vùng liên quan). Nếu stability được đặt thành "vintf", thì điều này tương ứng với một lời hứa về độ ổn định: giao diện phải luôn ổn định miễn là được sử dụng.
  • gen_trace: Cờ không bắt buộc để bật hoặc tắt tính năng theo dõi. Kể từ Android 14, giá trị mặc định là true cho các phần phụ trợ cppjava.
  • host_supported: Cờ không bắt buộc. Khi được đặt thành true, cờ này sẽ cung cấp các thư viện đã tạo cho môi trường máy chủ.
  • unstable: Cờ không bắt buộc dùng để đánh dấu rằng giao diện này không cần ổn định. Khi bạn đặt giá trị này thành true, hệ thống bản dựng sẽ không tạo bản kết xuất API cho giao diện cũng như không yêu cầu bạn cập nhật bản kết xuất đó.
  • frozen: Cờ không bắt buộc. Khi được đặt thành true, cờ này có nghĩa là giao diện không có thay đổi nào kể từ phiên bản trước của giao diện. Điều này cho phép kiểm tra thêm trong thời gian xây dựng. Khi được đặt thành false, điều này có nghĩa là giao diện đang trong quá trình phát triển và có những thay đổi mới, vì vậy, việc chạy foo-freeze-api sẽ tạo ra một phiên bản mới và tự động thay đổi giá trị thành true. Ra mắt trong Android 14.
  • backend.<type>.enabled: Các cờ này chuyển đổi từng phần phụ trợ mà trình biên dịch AIDL tạo mã cho. Có 4 chương trình phụ trợ được hỗ trợ: Java, C++, NDK và Rust. Các phần phụ trợ Java, C++ và NDK được bật theo mặc định. Nếu không cần bất kỳ một trong ba chương trình phụ trợ này, bạn cần tắt rõ ràng. Theo mặc định, Rust sẽ bị tắt cho đến Android 15.
  • backend.<type>.apex_available: Danh sách tên APEX mà thư viện gốc được tạo có sẵn.
  • backend.[cpp|java].gen_log: Cờ không bắt buộc kiểm soát việc có tạo mã bổ sung để thu thập thông tin về giao dịch hay không.
  • backend.[cpp|java].vndk.enabled: Cờ không bắt buộc để đưa giao diện này vào VNDK. Giá trị mặc định là false.
  • backend.[cpp|ndk].additional_shared_libraries: Được giới thiệu trong Android 14, cờ này sẽ thêm các phần phụ thuộc vào thư viện gốc. Cờ này hữu ích với ndk_headercpp_header.
  • backend.java.sdk_version: Cờ không bắt buộc để chỉ định phiên bản của SDK mà thư viện gốc Java được xây dựng dựa trên. Giá trị mặc định là "system_current". Bạn không nên đặt giá trị này khi backend.java.platform_apistrue.
  • backend.java.platform_apis: Cờ không bắt buộc phải được đặt thành true khi các thư viện được tạo cần được tạo dựa trên API nền tảng thay vì SDK.

Một thư viện stub sẽ được tạo cho mỗi tổ hợp phiên bản và các phần phụ trợ đã bật. Để biết cách tham chiếu đến phiên bản cụ thể của thư viện stub cho một phần phụ trợ cụ thể, hãy xem Quy tắc đặt tên mô-đun.

Viết tệp AIDL

Các giao diện trong AIDL ổn định tương tự như các giao diện truyền thống, ngoại trừ việc chúng không được phép sử dụng các đối tượng có thể phân chia không có cấu trúc (vì chúng không ổn định! hãy xem AIDL có cấu trúc so với AIDL ổn định). Điểm khác biệt chính trong AIDL ổn định là cách xác định các đối tượng có thể chuyển đổi thành gói. Trước đây, các đối tượng có thể chuyển đổi tuần tự được khai báo chuyển tiếp; trong AIDL ổn định (và do đó có cấu trúc), các trường và biến có thể chuyển đổi tuần tự được xác định một cách rõ ràng.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

Bạn có thể dùng giá trị mặc định (nhưng không bắt buộc) cho boolean, char, float, double, byte, int, longString. Trong Android 12, các giá trị mặc định cho các kiểu liệt kê do người dùng xác định cũng được hỗ trợ. Khi bạn không chỉ định giá trị mặc định, giá trị tương tự như 0 hoặc giá trị trống sẽ được sử dụng. Các giá trị liệt kê không có giá trị mặc định được khởi chạy thành 0 ngay cả khi không có giá trị liệt kê 0.

Sử dụng thư viện stub

Sau khi thêm các thư viện stub làm phần phụ thuộc vào mô-đun, bạn có thể thêm các thư viện đó vào tệp của mình. Dưới đây là các ví dụ về thư viện gốc trong hệ thống xây dựng (Android.mk cũng có thể dùng cho các định nghĩa mô-đun cũ). Xin lưu ý rằng trong những ví dụ này, phiên bản không xuất hiện, vì vậy, phiên bản này biểu thị việc sử dụng một giao diện không ổn định, nhưng tên cho các giao diện có phiên bản bao gồm thông tin bổ sung, hãy xem phần Giao diện lập phiên bản.

cc_... {
    name: ...,
    // use `shared_libs:` to load your library and its transitive dependencies
    // dynamically
    shared_libs: ["my-module-name-cpp"],
    // use `static_libs:` to include the library in this binary and drop
    // transitive dependencies
    static_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // use `static_libs:` to add all jars and classes to this jar
    static_libs: ["my-module-name-java"],
    // use `libs:` to make these classes available during build time, but
    // not add them to the jar, in case the classes are already present on the
    // boot classpath (such as if it's in framework.jar) or another jar.
    libs: ["my-module-name-java"],
    // use `srcs:` with `-java-sources` if you want to add classes in this
    // library jar directly, but you get transitive dependencies from
    // somewhere else, such as the boot classpath or another jar.
    srcs: ["my-module-name-java-source", ...],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

Ví dụ bằng C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Ví dụ trong Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Ví dụ trong Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

Lập phiên bản giao diện

Việc khai báo một mô-đun có tên foo cũng tạo ra một mục tiêu trong hệ thống xây dựng mà bạn có thể dùng để quản lý API của mô-đun. Khi được tạo, foo-freeze-api sẽ thêm một định nghĩa API mới trong api_dir hoặc aidl_api/name, tuỳ thuộc vào phiên bản Android, đồng thời thêm một tệp .hash. Cả hai đều đại diện cho phiên bản giao diện mới được cố định. foo-freeze-api cũng cập nhật thuộc tính versions_with_info để phản ánh phiên bản bổ sung và imports cho phiên bản. Về cơ bản, imports trong versions_with_info được sao chép từ trường imports. Nhưng phiên bản ổn định mới nhất được chỉ định trong imports trong versions_with_info cho hoạt động nhập, không có phiên bản rõ ràng. Sau khi bạn chỉ định thuộc tính versions_with_info, hệ thống bản dựng sẽ chạy các quy trình kiểm tra khả năng tương thích giữa các phiên bản cố định, cũng như giữa Phiên bản mới nhất (ToT) và phiên bản cố định mới nhất.

Ngoài ra, bạn cần quản lý định nghĩa API của phiên bản ToT. Bất cứ khi nào một API được cập nhật, hãy chạy foo-update-api để cập nhật aidl_api/name/current chứa định nghĩa API của phiên bản ToT.

Để duy trì tính ổn định của giao diện, chủ sở hữu có thể thêm:

  • Các phương thức ở cuối giao diện (hoặc các phương thức có số sê-ri mới được xác định rõ ràng)
  • Các phần tử ở cuối một đối tượng có thể chuyển đổi (yêu cầu phải thêm một giá trị mặc định cho từng phần tử)
  • Giá trị không đổi
  • Trong Android 11, các trình liệt kê
  • Trong Android 12, các trường đến cuối một liên kết

Không được phép thực hiện các thao tác khác và không ai khác có thể sửa đổi giao diện (nếu không, họ có nguy cơ xung đột với những thay đổi mà chủ sở hữu thực hiện).

Để kiểm thử rằng tất cả các giao diện đều được cố định cho bản phát hành, bạn có thể tạo bằng cách đặt các biến môi trường sau:

  • AIDL_FROZEN_REL=true m ... – bản dựng yêu cầu tất cả các giao diện AIDL ổn định phải được cố định mà không có trường owner: nào được chỉ định.
  • AIDL_FROZEN_OWNERS="aosp test" – bản dựng yêu cầu tất cả các giao diện AIDL ổn định phải được cố định với trường owner: được chỉ định là "aosp" hoặc "test".

Độ ổn định của dữ liệu nhập

Việc cập nhật các phiên bản nhập cho các phiên bản cố định của một giao diện tương thích ngược ở lớp AIDL ổn định. Tuy nhiên, việc cập nhật các loại này đòi hỏi bạn phải cập nhật tất cả các máy chủ và máy khách sử dụng phiên bản trước của giao diện, đồng thời một số ứng dụng có thể bị nhầm lẫn khi kết hợp các phiên bản khác nhau của các loại. Nhìn chung, đối với các gói chỉ có loại hoặc gói chung, điều này là an toàn vì mã cần được viết sẵn để xử lý các loại không xác định từ các giao dịch IPC.

Trong mã nền tảng Android, android.hardware.graphics.common là ví dụ lớn nhất về loại nâng cấp phiên bản này.

Sử dụng các giao diện có phiên bản

Phương thức giao diện

Trong thời gian chạy, khi cố gắng gọi các phương thức mới trên một máy chủ cũ, các ứng dụng mới sẽ gặp lỗi hoặc ngoại lệ, tuỳ thuộc vào phần phụ trợ.

  • phần phụ trợ cpp nhận được ::android::UNKNOWN_TRANSACTION.
  • phần phụ trợ ndk nhận được STATUS_UNKNOWN_TRANSACTION.
  • Phụ trợ java nhận được android.os.RemoteException kèm theo thông báo cho biết API chưa được triển khai.

Để biết các chiến lược xử lý vấn đề này, hãy xem phần truy vấn các phiên bảnsử dụng giá trị mặc định.

Parcelable

Khi các trường mới được thêm vào các đối tượng có thể chuyển đổi, các máy chủ và ứng dụng cũ sẽ loại bỏ các trường đó. Khi máy chủ và máy khách mới nhận được các đối tượng có thể chuyển đổi cũ, các giá trị mặc định cho các trường mới sẽ tự động được điền. Điều này có nghĩa là bạn cần chỉ định giá trị mặc định cho tất cả các trường mới trong một đối tượng có thể chuyển đổi thành gói.

Các ứng dụng không nên mong đợi máy chủ sử dụng các trường mới, trừ phi chúng biết máy chủ đang triển khai phiên bản có trường được xác định (xem phần truy vấn phiên bản).

Enum và hằng số

Tương tự, các máy khách và máy chủ nên từ chối hoặc bỏ qua các giá trị hằng số và trình liệt kê không xác định cho phù hợp, vì có thể sẽ có thêm các giá trị này trong tương lai. Ví dụ: một máy chủ không được huỷ khi nhận được một trình liệt kê mà máy chủ đó không biết. Máy chủ nên bỏ qua trình liệt kê hoặc trả về một giá trị nào đó để ứng dụng biết rằng trình liệt kê không được hỗ trợ trong quá trình triển khai này.

Liên minh

Việc cố gắng gửi một union có trường mới sẽ không thành công nếu máy nhận là máy cũ và không biết về trường đó. Quá trình triển khai sẽ không bao giờ thấy được sự kết hợp với trường mới. Lỗi này sẽ bị bỏ qua nếu đó là giao dịch một chiều; nếu không, lỗi sẽ là BAD_VALUE(đối với phần phụ trợ C++ hoặc NDK) hoặc IllegalArgumentException(đối với phần phụ trợ Java). Lỗi này xảy ra nếu ứng dụng đang gửi một tập hợp hợp nhất đến trường mới trên một máy chủ cũ hoặc khi đó là một ứng dụng cũ nhận tập hợp hợp nhất từ một máy chủ mới.

Quản lý nhiều phiên bản

Một không gian tên trình liên kết trong Android chỉ có thể có 1 phiên bản của một giao diện aidl cụ thể để tránh trường hợp các loại aidl được tạo có nhiều định nghĩa. C++ có Quy tắc một định nghĩa, chỉ yêu cầu một định nghĩa cho mỗi biểu tượng.

Bản dựng Android sẽ báo lỗi khi một mô-đun phụ thuộc vào các phiên bản khác nhau của cùng một thư viện aidl_interface. Mô-đun có thể phụ thuộc trực tiếp hoặc gián tiếp vào các thư viện này thông qua các phần phụ thuộc của phần phụ thuộc. Những lỗi này cho thấy biểu đồ phần phụ thuộc từ mô-đun không thành công đến các phiên bản xung đột của thư viện aidl_interface. Bạn cần cập nhật tất cả các phần phụ thuộc để bao gồm cùng một phiên bản (thường là phiên bản mới nhất) của các thư viện này.

Nếu nhiều mô-đun khác nhau sử dụng thư viện giao diện, thì bạn nên tạo cc_defaults, java_defaultsrust_defaults cho mọi nhóm thư viện và quy trình cần sử dụng cùng một phiên bản. Khi giới thiệu một phiên bản mới của giao diện, các giá trị mặc định đó có thể được cập nhật và tất cả các mô-đun sử dụng chúng sẽ được cập nhật cùng nhau, đảm bảo rằng chúng không sử dụng các phiên bản khác nhau của giao diện.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

Khi các mô-đun aidl_interface nhập các mô-đun aidl_interface khác, điều này sẽ tạo ra các phần phụ thuộc bổ sung yêu cầu sử dụng các phiên bản cụ thể cùng nhau. Tình huống này có thể trở nên khó quản lý khi có các mô-đun aidl_interface phổ biến được nhập vào nhiều mô-đun aidl_interface được dùng cùng nhau trong cùng một quy trình.

Bạn có thể dùng aidl_interfaces_defaults để giữ một định nghĩa về các phiên bản mới nhất của phần phụ thuộc cho một aidl_interface có thể được cập nhật ở một nơi duy nhất và được dùng bởi tất cả các mô-đun aidl_interface muốn nhập giao diện chung đó.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Phát triển dựa trên cờ

Bạn không thể dùng các giao diện đang phát triển (chưa hoàn thiện) trên các thiết bị phát hành, vì chúng không được đảm bảo là tương thích ngược.

AIDL hỗ trợ dự phòng thời gian chạy cho các thư viện giao diện chưa cố định này để mã được viết dựa trên phiên bản chưa cố định mới nhất và vẫn được dùng trên các thiết bị phát hành. Hành vi tương thích ngược của các ứng dụng tương tự như hành vi hiện có và với cơ chế dự phòng, các hoạt động triển khai cũng cần tuân theo những hành vi đó. Xem phần Sử dụng giao diện có phiên bản.

Cờ dựng AIDL

Cờ kiểm soát hành vi này được xác định trong RELEASE_AIDL_USE_UNFROZENbuild/release/build_flags.bzl. true có nghĩa là phiên bản không cố định của giao diện được dùng trong thời gian chạy và false có nghĩa là các thư viện của phiên bản không cố định đều hoạt động như phiên bản cố định gần đây nhất. Bạn có thể ghi đè cờ thành true để phát triển cục bộ, nhưng phải hoàn nguyên cờ thành false trước khi phát hành. Thông thường, quá trình phát triển được thực hiện bằng một cấu hình có cờ được đặt thành true.

Ma trận và tệp kê khai về khả năng tương thích

Các đối tượng giao diện nhà cung cấp (đối tượng VINTF) xác định những phiên bản dự kiến và những phiên bản được cung cấp ở cả hai phía của giao diện nhà cung cấp.

Hầu hết các thiết bị không phải Cuttlefish chỉ nhắm đến ma trận tương thích mới nhất sau khi các giao diện bị đóng băng, vì vậy, không có sự khác biệt nào trong các thư viện AIDL dựa trên RELEASE_AIDL_USE_UNFROZEN.

Ma trận

Các giao diện thuộc sở hữu của đối tác được thêm vào ma trận tương thích dành riêng cho thiết bị hoặc sản phẩm mà thiết bị nhắm đến trong quá trình phát triển. Vì vậy, khi một phiên bản mới, chưa cố định của một giao diện được thêm vào ma trận tương thích, các phiên bản cố định trước đó cần được giữ lại cho RELEASE_AIDL_USE_UNFROZEN=false. Bạn có thể xử lý việc này bằng cách sử dụng các tệp ma trận tương thích khác nhau cho các cấu hình RELEASE_AIDL_USE_UNFROZEN khác nhau hoặc cho phép cả hai phiên bản trong một tệp ma trận tương thích duy nhất được dùng trong tất cả các cấu hình.

Ví dụ: khi thêm phiên bản 4 chưa cố định, hãy dùng <version>3-4</version>.

Khi phiên bản 4 bị đóng băng, bạn có thể xoá phiên bản 3 khỏi ma trận tương thích vì phiên bản 4 bị đóng băng được dùng khi RELEASE_AIDL_USE_UNFROZENfalse.

Tệp kê khai

Trong Android 15, một thay đổi về libvintf được giới thiệu để sửa đổi các tệp kê khai tại thời điểm tạo dựa trên giá trị của RELEASE_AIDL_USE_UNFROZEN.

Tệp kê khai và các mảnh tệp kê khai khai báo phiên bản giao diện mà một dịch vụ triển khai. Khi sử dụng phiên bản mới nhất chưa bị khoá của một giao diện, bạn phải cập nhật tệp kê khai để phản ánh phiên bản mới này. Khi RELEASE_AIDL_USE_UNFROZEN=false điều chỉnh các mục trong tệp kê khai bằng libvintf để phản ánh thay đổi trong thư viện AIDL đã tạo. Phiên bản này được sửa đổi từ phiên bản chưa dừng phát triển N thành phiên bản dừng phát triển gần đây nhất N - 1. Do đó, người dùng không cần quản lý nhiều tệp kê khai hoặc mảnh tệp kê khai cho từng dịch vụ của họ.

Các thay đổi về ứng dụng HAL

Mã ứng dụng HAL phải tương thích ngược với từng phiên bản cố định được hỗ trợ trước đó. Khi RELEASE_AIDL_USE_UNFROZENfalse, các dịch vụ luôn trông giống như phiên bản đóng băng gần đây nhất hoặc phiên bản trước đó (ví dụ: việc gọi các phương thức mới không đóng băng sẽ trả về UNKNOWN_TRANSACTION hoặc các trường parcelable mới có giá trị mặc định). Các ứng dụng khung Android phải tương thích ngược với các phiên bản trước khác, nhưng đây là một thông tin chi tiết mới cho các ứng dụng của nhà cung cấp và ứng dụng của giao diện do đối tác sở hữu.

Các thay đổi về việc triển khai HAL

Điểm khác biệt lớn nhất trong quá trình phát triển HAL bằng phương pháp phát triển dựa trên cờ là yêu cầu các hoạt động triển khai HAL phải tương thích ngược với phiên bản cố định gần đây nhất để hoạt động khi RELEASE_AIDL_USE_UNFROZENfalse. Việc xem xét khả năng tương thích ngược trong quá trình triển khai và mã thiết bị là một bài tập mới. Xem phần Sử dụng các giao diện có phiên bản.

Các điểm cần cân nhắc về khả năng tương thích ngược thường giống nhau đối với máy khách và máy chủ, cũng như đối với mã khung và mã nhà cung cấp, nhưng có những điểm khác biệt nhỏ mà bạn cần lưu ý, vì giờ đây bạn đang triển khai hiệu quả hai phiên bản sử dụng cùng một mã nguồn (phiên bản hiện tại, chưa cố định).

Ví dụ: Một giao diện có 3 phiên bản cố định. Giao diện được cập nhật bằng một phương thức mới. Cả ứng dụng và dịch vụ đều được cập nhật để sử dụng thư viện phiên bản 4 mới. Vì thư viện V4 dựa trên một phiên bản chưa cố định của giao diện, nên thư viện này hoạt động như phiên bản cố định gần đây nhất (phiên bản 3) khi RELEASE_AIDL_USE_UNFROZENfalse và ngăn việc sử dụng phương thức mới.

Khi giao diện bị đóng băng, tất cả các giá trị của RELEASE_AIDL_USE_UNFROZEN đều sử dụng phiên bản đóng băng đó và bạn có thể xoá mã xử lý khả năng tương thích ngược.

Khi gọi các phương thức trên lệnh gọi lại, bạn phải xử lý trường hợp khi UNKNOWN_TRANSACTION được trả về một cách thích hợp. Các ứng dụng có thể triển khai 2 phiên bản lệnh gọi lại dựa trên cấu hình phát hành, vì vậy, bạn không thể giả định rằng ứng dụng gửi phiên bản mới nhất và các phương thức mới có thể trả về phiên bản này. Điều này tương tự như cách các ứng dụng AIDL ổn định duy trì khả năng tương thích ngược với các máy chủ được mô tả trong phần Sử dụng các giao diện có phiên bản.

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

Các trường mới trong các loại hiện có (parcelable, enum, union) có thể không tồn tại hoặc chứa các giá trị mặc định khi RELEASE_AIDL_USE_UNFROZENfalse và các giá trị của các trường mới mà một dịch vụ cố gắng gửi sẽ bị loại bỏ khi thoát khỏi quy trình.

Bạn không thể gửi hoặc nhận các loại mới được thêm vào phiên bản chưa cố định này thông qua giao diện.

Hoạt động triển khai không bao giờ nhận được lệnh gọi cho các phương thức mới từ bất kỳ ứng dụng nào khi RELEASE_AIDL_USE_UNFROZENfalse.

Hãy cẩn thận chỉ sử dụng các trình liệt kê mới với phiên bản mà chúng được giới thiệu, chứ không phải phiên bản trước đó.

Thông thường, bạn dùng foo->getInterfaceVersion() để xem phiên bản mà giao diện từ xa đang dùng. Tuy nhiên, với tính năng hỗ trợ lập phiên bản dựa trên cờ, bạn đang triển khai 2 phiên bản khác nhau, vì vậy, bạn có thể muốn lấy phiên bản của giao diện hiện tại. Bạn có thể thực hiện việc này bằng cách lấy phiên bản giao diện của đối tượng hiện tại, chẳng hạn như this->getInterfaceVersion() hoặc các phương thức khác cho my_ver. Hãy xem phần Truy vấn phiên bản giao diện của đối tượng từ xa để biết thêm thông tin.

Các giao diện ổn định VINTF mới

Khi một gói giao diện AIDL mới được thêm vào, sẽ không có phiên bản cố định gần đây nhất, vì vậy, sẽ không có hành vi nào để quay lại khi RELEASE_AIDL_USE_UNFROZENfalse. Đừng sử dụng các giao diện này. Khi RELEASE_AIDL_USE_UNFROZENfalse, Trình quản lý dịch vụ sẽ không cho phép dịch vụ đăng ký giao diện và các ứng dụng sẽ không tìm thấy giao diện đó.

Bạn có thể thêm các dịch vụ có điều kiện dựa trên giá trị của cờ RELEASE_AIDL_USE_UNFROZEN trong tệp makefile của thiết bị:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

Nếu dịch vụ là một phần của quy trình lớn hơn nên bạn không thể thêm dịch vụ này vào thiết bị một cách có điều kiện, thì bạn có thể kiểm tra để xem dịch vụ có được khai báo bằng IServiceManager::isDeclared() hay không. Nếu được khai báo và không đăng ký được, hãy huỷ quy trình. Nếu không được khai báo, thì dự kiến sẽ không đăng ký được.

Các giao diện tiện ích ổn định VINTF mới

Các giao diện tiện ích mới không có phiên bản trước để quay lại và vì chúng không được đăng ký bằng ServiceManager hoặc khai báo trong tệp kê khai VINTF, nên bạn không thể dùng IServiceManager::isDeclared() để xác định thời điểm đính kèm giao diện tiện ích vào một giao diện khác.

Bạn có thể dùng biến RELEASE_AIDL_USE_UNFROZEN để xác định xem có nên đính kèm giao diện tiện ích mới chưa cố định vào giao diện hiện có hay không để tránh sử dụng giao diện này trên các thiết bị đã phát hành. Giao diện cần được cố định để sử dụng trên các thiết bị đã phát hành.

Các kiểm thử VTS vts_treble_vintf_vendor_testvts_treble_vintf_framework_test phát hiện thời điểm một giao diện tiện ích chưa được cố định được dùng trong một thiết bị đã phát hành và đưa ra lỗi.

Nếu giao diện tiện ích không phải là giao diện mới và có phiên bản đã bị đóng băng trước đó, thì giao diện đó sẽ quay lại phiên bản đã bị đóng băng trước đó và không cần thêm bước nào.

Cuttlefish làm công cụ phát triển

Mỗi năm sau khi VINTF được cố định, chúng tôi sẽ điều chỉnh ma trận khả năng tương thích của khung (FCM) target-levelPRODUCT_SHIPPING_API_LEVEL của Cuttlefish để chúng phản ánh các thiết bị ra mắt cùng với bản phát hành của năm tiếp theo. Chúng tôi điều chỉnh target-levelPRODUCT_SHIPPING_API_LEVEL để đảm bảo có một số thiết bị khởi chạy đã được kiểm thử và đáp ứng các yêu cầu mới cho bản phát hành vào năm tới.

Khi RELEASE_AIDL_USE_UNFROZENtrue, Cuttlefish sẽ được dùng để phát triển các bản phát hành Android trong tương lai. Mục tiêu của bản phát hành này là cấp độ FCM của bản phát hành Android vào năm tới và PRODUCT_SHIPPING_API_LEVEL, yêu cầu bản phát hành này phải đáp ứng Yêu cầu về phần mềm của nhà cung cấp (VSR) của bản phát hành tiếp theo.

Khi RELEASE_AIDL_USE_UNFROZENfalse, Cuttlefish sẽ có target-levelPRODUCT_SHIPPING_API_LEVEL trước đó để phản ánh một thiết bị phát hành. Trong Android 14 trở xuống, sự khác biệt này sẽ được thực hiện bằng các nhánh Git khác nhau không nhận được thay đổi đối với target-level FCM, cấp độ API vận chuyển hoặc bất kỳ mã nào khác nhắm đến bản phát hành tiếp theo.

Quy tắc đặt tên mô-đun

Trong Android 11, đối với mỗi tổ hợp phiên bản và các phần phụ trợ đã bật, một mô-đun thư viện gốc sẽ được tạo tự động. Để tham chiếu đến một mô-đun thư viện gốc cụ thể để liên kết, đừng sử dụng tên của mô-đun aidl_interface mà hãy sử dụng tên của mô-đun thư viện gốc, tức là ifacename-version-backend, trong đó

  • ifacename: tên của mô-đun aidl_interface
  • version là một trong hai giá trị sau:
    • Vversion-number cho các phiên bản dừng phát triển
    • Vlatest-frozen-version-number + 1 cho phiên bản đầu nguồn (chưa được cố định)
  • backend là một trong hai giá trị sau:
    • java cho phần phụ trợ Java,
    • cpp cho phần phụ trợ C++,
    • ndk hoặc ndk_platform cho phần phụ trợ NDK. Cái trước dành cho ứng dụng và cái sau dành cho việc sử dụng nền tảng cho đến Android 13. Trong Android 13 trở lên, chỉ sử dụng ndk.
    • rust cho phần phụ trợ Rust.

Giả sử có một mô-đun có tên là foo và phiên bản mới nhất của mô-đun này là 2, đồng thời mô-đun này hỗ trợ cả NDK và C++. Trong trường hợp này, AIDL sẽ tạo các mô-đun sau:

  • Dựa trên phiên bản 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Dựa trên phiên bản 2 (phiên bản ổn định mới nhất)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Dựa trên phiên bản ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

So với Android 11:

  • foo-backend (đề cập đến phiên bản ổn định mới nhất) trở thành foo-V2-backend
  • foo-unstable-backend (đề cập đến phiên bản ToT) trở thành foo-V3-backend

Tên tệp đầu ra luôn giống với tên mô-đun.

  • Dựa trên phiên bản 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Dựa trên phiên bản 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Dựa trên phiên bản ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

Xin lưu ý rằng trình biên dịch AIDL không tạo mô-đun phiên bản unstable hoặc mô-đun không có phiên bản cho giao diện AIDL ổn định. Kể từ Android 12, tên mô-đun được tạo từ một giao diện AIDL ổn định luôn bao gồm phiên bản của giao diện đó.

Các phương thức giao diện meta mới

Android 10 bổ sung một số phương thức giao diện meta cho AIDL ổn định.

Truy vấn phiên bản giao diện của đối tượng từ xa

Các ứng dụng có thể truy vấn phiên bản và hàm băm của giao diện mà đối tượng từ xa đang triển khai, đồng thời so sánh các giá trị được trả về với các giá trị của giao diện mà ứng dụng đang sử dụng.

Ví dụ về phần phụ trợ cpp:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Ví dụ với phần phụ trợ ndk (và ndk_platform):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Ví dụ về phần phụ trợ java:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Đối với ngôn ngữ Java, phía từ xa PHẢI triển khai getInterfaceVersion()getInterfaceHash() như sau (super được dùng thay cho IFoo để tránh lỗi sao chép và dán. Bạn có thể cần chú thích @SuppressWarnings("static") để tắt cảnh báo, tuỳ thuộc vào cấu hình javac):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

Điều này là do các lớp được tạo (IFoo, IFoo.Stub, v.v.) được chia sẻ giữa máy khách và máy chủ (ví dụ: các lớp có thể nằm trong đường dẫn lớp khởi động). Khi các lớp được chia sẻ, máy chủ cũng được liên kết với phiên bản mới nhất của các lớp, mặc dù có thể máy chủ đã được tạo bằng phiên bản cũ hơn của giao diện. Nếu giao diện meta này được triển khai trong lớp dùng chung, thì giao diện này sẽ luôn trả về phiên bản mới nhất. Tuy nhiên, bằng cách triển khai phương thức như trên, số phiên bản của giao diện sẽ được nhúng vào mã của máy chủ (vì IFoo.VERSION là một static final int được nội tuyến khi được tham chiếu) và do đó, phương thức này có thể trả về chính xác phiên bản mà máy chủ được tạo.

Xử lý các giao diện cũ

Có thể một ứng dụng được cập nhật bằng phiên bản mới hơn của giao diện AIDL nhưng máy chủ lại đang dùng giao diện AIDL cũ. Trong những trường hợp như vậy, việc gọi một phương thức trên giao diện cũ sẽ trả về UNKNOWN_TRANSACTION.

Với AIDL ổn định, các ứng dụng có nhiều quyền kiểm soát hơn. Ở phía máy khách, bạn có thể đặt một chế độ triển khai mặc định cho giao diện AIDL. Một phương thức trong quá trình triển khai mặc định chỉ được gọi khi phương thức đó không được triển khai ở phía từ xa (vì phương thức đó được tạo bằng phiên bản cũ hơn của giao diện). Vì các giá trị mặc định được đặt trên toàn cầu, nên bạn không được dùng các giá trị này từ những ngữ cảnh có khả năng được chia sẻ.

Ví dụ bằng C++ trong Android 13 trở lên:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Ví dụ trong Java:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process

foo.anAddedMethod(...);

Bạn không cần cung cấp chế độ triển khai mặc định của tất cả các phương thức trong giao diện AIDL. Bạn không cần ghi đè các phương thức chắc chắn được triển khai ở phía từ xa (vì bạn chắc chắn rằng phía từ xa được tạo khi các phương thức nằm trong phần mô tả giao diện AIDL) trong lớp impl mặc định.

Chuyển đổi AIDL hiện có thành AIDL có cấu trúc hoặc ổn định

Nếu bạn có giao diện AIDL hiện có và mã sử dụng giao diện đó, hãy làm theo các bước sau để chuyển đổi giao diện thành giao diện AIDL ổn định.

  1. Xác định tất cả các phần phụ thuộc của giao diện. Đối với mọi gói mà giao diện phụ thuộc vào, hãy xác định xem gói đó có được xác định trong AIDL ổn định hay không. Nếu không được xác định, gói phải được chuyển đổi.

  2. Chuyển đổi tất cả các đối tượng có thể đóng gói trong giao diện của bạn thành các đối tượng có thể đóng gói ổn định (bản thân các tệp giao diện có thể không thay đổi). Hãy làm việc này bằng cách thể hiện cấu trúc của chúng ngay trong tệp AIDL. Bạn phải viết lại các lớp quản lý để sử dụng những loại mới này. Bạn có thể thực hiện việc này trước khi tạo gói aidl_interface (bên dưới).

  3. Tạo một gói aidl_interface (như mô tả ở trên) chứa tên mô-đun, các phần phụ thuộc và mọi thông tin khác mà bạn cần. Để ổn định (không chỉ có cấu trúc), nó cũng cần được phân phiên bản. Để biết thêm thông tin, hãy xem phần Tạo phiên bản cho các giao diện.