AIDL ổn định

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

  • 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. Parcelables đại diện cho được tạo tự động dựa trên định nghĩa AIDL và sẽ tự động được tổng hợp và không có sự đồng bộ.
  • Bạn có thể khai báo 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à tạo 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ụ: một nội dung khai báo theo gói (Parceable tuỳ chỉnh) không có cấu trúc AIDL. Các đối tượng có thể phân phối có các trường được xác định trong AIDL được gọi là các đối tượng có thể phân phối có cấu trúc.

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

Xác định giao diện AIDL

Định nghĩa của 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 một giao diện AIDL một cách duy nhất.
  • srcs: Danh sách các tệp nguồn AIDL tạo nên giao diện. Đường dẫn cho 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. Phương thức này 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à lớp 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 gói có thể phân phối từ một aidl_interface khác, hãy đặt tên của giao diện đó vào đây. Đây có thể là tên riêng để tham chiếu đến 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) để tham chiếu đến một phiên bản cụ thể. Tính năng chỉ định một phiên bản đã được hỗ trợ kể từ Android 12
  • versions: Các phiên bản trước đây của giao diện bị treo trong api_dir, Kể từ Android 11, versions bị cố định trong aidl_api/name. Nếu không có phiên bản giao diện bị đóng băng, bạn không nên chỉ định thuộc tính này và sẽ không có hoạt động kiểm tra khả năng tương thích. Trường này đã được thay thế bằng versions_with_info đối với Android 13 trở lên.
  • versions_with_info: Danh sách các bộ dữ liệu, mỗi bộ dữ liệu chứa tên của một phiên bản bị treo và danh sách có nhập phiên bản của aidl_Interface khác các mô-đun mà phiên bản aidl_ giao diện này đã nhập. Định nghĩ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 giới thiệu trong Android 13 và không được 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 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. Phương thức này chỉ hỗ trợ "vintf". Nếu bạn không đặt stability, 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. Việc không đặt giá trị 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 bạn đặt stability 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 được giữ ổ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 phần phụ trợ cppjava.
  • host_supported: Cờ không bắt buộc, khi được đặt thành true, sẽ cung cấp các thư viện đã tạo cho môi trường lưu trữ.
  • unstable: Cờ không bắt buộc dùng để đánh dấu giao diện này không cần phải ổn định. Khi bạn đặt giá trị này thành true, hệ thống xây dựng sẽ không tạo tệp báo lỗi API cho giao diện cũng như không yêu cầu cập nhật tệp báo lỗi đó.
  • frozen: Cờ không bắt buộc khi được đặt thành true có nghĩa là giao diện không có thay đổi nào kể từ phiên bản giao diện trước đó. Điều này cho phép thực hiện nhiều bước kiểm tra hơn 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ó các thay đổi mới, vì vậy, việc chạy foo-freeze-api sẽ tạo 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: Những cờ này bật/tắt từng phần phụ trợ trình biên dịch AIDL tạo mã cho. Hỗ trợ 4 phần phụ 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 đến bất kỳ phần phụ trợ nào trong số ba phần phụ trợ này, bạn cần tắt phần phụ trợ đó một cách 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 giả lập đã 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 để tạo giao diện này một phần của VNDK. Mặc định là false.
  • backend.[cpp|ndk].additional_shared_libraries: Ra mắt trong Trên Android 14, cờ này 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ờ tuỳ chọn để chỉ định phiên bản của SDK mà thư viện mã giả lập Java được xây dựng dựa trên đó. 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 thư viện đã tạo cần xây dựng dựa trên API nền tảng thay vì SDK.

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

Ghi 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, với có ngoại lệ là chúng không được phép sử dụng các gói không có cấu trúc (vì chúng không ổn định! xem Có cấu trúc so với ổn định AIDL). Điểm khác biệt chính đối với AIDL ổn định là cách Lô sản phẩm được xác định. Trước đây, các đối tượng có thể phân phối được khai báo trước; trong AIDL ổn định (và do đó có cấu trúc), các trường và biến có thể phân phối được được xác định rõ ràng.

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

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

Chế độ mặc định được hỗ trợ (nhưng không bắt buộc) cho boolean, char, float, double, byte, int, longString. Trong Android 12, giá trị mặc định cho giá trị enum do người dùng xác định cũng được hỗ trợ. Khi giá trị mặc định không được chỉ định, giá trị giống 0 hoặc trống sẽ được sử dụng. Liệt kê không có giá trị mặc định sẽ được khởi tạo về 0 ngay cả khi có không có liệt kê 0.

Sử dụng thư viện giả lập

Sau khi thêm thư viện mã giả lập làm phần phụ thuộc cho mô-đun, bạn có thể đưa chúng vào tệp của bạn. Sau đây là ví dụ về thư viện mã giả lập trong hệ thống xây dựng (Android.mk cũng có thể được sử dụng cho các định nghĩa mô-đun cũ):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

Ví dụ trong 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

Giao diện tạo phiên bản

Việc khai báo một mô-đun có tên foo cũng sẽ tạo một mục tiêu trong hệ thống xây dựng mà bạn có thể sử dụng để quản lý API của mô-đun. Khi được tạo, foo-freeze-api thêm định nghĩa API mới trong api_dir hoặc aidl_api/name, tuỳ thuộc vào phiên bản Android và thêm một tệp .hash, cả hai đều đại diện cho phiên bản mới bị treo của . 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. Tuy nhiên, phiên bản ổn định mới nhất được chỉ định trong imports trong versions_with_info cho lệnh nhập không có phiên bản rõ ràng. Sau khi thuộc tính versions_with_info được chỉ định, hệ thống xây dựng sẽ chạy kiểm tra khả năng tương thích giữa các phiên bản bị treo cũng như giữa các đầu cây (ToT) và phiên bản bị treo 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 mới:

  • 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 phần tử có thể phân phối (yêu cầu thêm một phần tử mặc định cho mỗi phần tử)
  • Giá trị không đổi
  • Trong Android 11, enum
  • 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 hành động nào 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 các thay đổi mà chủ sở hữu thực hiện).

Để kiểm thử rằng tất cả giao diện đều bị đóng băng để phát hành, bạn có thể tạo bản dựng 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ả giao diện AIDL ổn định phải được đóng băng mà không có trường owner: được chỉ định.
  • AIDL_FROZEN_OWNERS="aosp test" – bản dựng yêu cầu tất cả giao diện AIDL ổn định được cố định bằng trường owner: được chỉ định là "aosp" hoặc "test".

Độ ổn định của các tệp nhập

Việc cập nhật phiên bản nhập cho các phiên bản giao diện bị treo tương thích ngược ở lớp AIDL chính thức. Tuy nhiên, việc cập nhật các giá trị này đòi hỏi cập nhật tất cả máy chủ và ứng dụng khách sử dụng phiên bản giao diện trước đó, và 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 phổ biến, đây là cách an toàn vì mã cần đã được viết để xử lý các loại không xác định trong giao dịch IPC.

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

Sử dụng 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ẽ nhận được 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ần phụ trợ của java nhận được android.os.RemoteException kèm theo một thông báo cho biết Chưa triển khai API.

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

Theo gói

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

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

Enum và hằng số

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

Liên minh

Việc cố gắng gửi kết hợp với một trường mới sẽ không thành công nếu trình thu nhận cũ và không biết về lĩnh vực này. Quá trình triển khai sẽ không bao giờ cho thấy 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 là đã nhận được nếu máy khách đang gửi một hợp nhất được đặt đến trường mới thành một tập hợp hoặc khi máy khách cũ nhận được liên kết từ máy chủ mới.

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

Không gian tên của trình liên kết trong Android chỉ có thể có 1 phiên bản của 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 báo lỗi khi một mô-đun phụ thuộc vào các các phiên bản 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 các phần phụ thuộc đó. Những lỗi này cho thấy biểu đồ phần phụ thuộc từ mô-đun gặp lỗi đến các phiên bản xung đột của thư viện aidl_interface. Tất cả các phần phụ thuộc cần được cập nhật để 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 thư viện giao diện được nhiều mô-đun khác nhau sử dụng, điều này có thể hữu ích để tạo cc_defaults, java_defaultsrust_defaults cho bất kỳ nhóm nào những 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 được cập nhật cùng nhau, đảm bảo 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, thao tác này sẽ tạo ra các phần phụ thuộc bổ sung yêu cầu sử dụng cùng nhau các phiên bản cụ thể. Chiến dịch này tình hình có thể trở nên khó quản lý khi có aidl_interface thường gặp các mô-đun được nhập trong nhiều mô-đun aidl_interface được sử dụng trong cùng một quy trình.

Có thể sử dụng aidl_interfaces_defaults để giữ một định nghĩa của phiên bản phần phụ thuộc mới nhất cho aidl_interface có thể được cập nhật trong một vị trí duy nhất và được sử dụng bởi tất 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ờ

Không thể sử dụng giao diện đang phát triển (không được cố định) trên thiết bị phát hành, vì chúng không được đảm bảo tương thích ngược.

AIDL hỗ trợ tính năng dự phòng thời gian chạy cho các thư viện giao diện không được cố định này theo thứ tự để mã được viết dựa trên phiên bản không được cố định mới nhất và vẫn được sử 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 tại và với phương án dự phòng thì việc triển khai cũng cần phải tuân thủ những hành vi đó. Xem Sử dụng giao diện được tạo phiên bản.

Cờ bản dựng AIDL

Cờ kiểm soát hành vi này là RELEASE_AIDL_USE_UNFROZEN được xác định trong build/release/build_flags.bzl. true có nghĩa là phiên bản không được đóng băng của giao diện này đượ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 bị treo đều hoạt động như phiên bản bị treo gần đây nhất. Bạn có thể ghi đè cờ thành true cho phát triển cục bộ, nhưng phải hoàn nguyên về 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 tương thích và tệp kê khai

Đối tượng giao diện nhà cung cấp (đối tượng VINTF) xác định phiên bản nào được mong đợi và phiên bản nào được cung cấp ở mỗi bên giao diện của nhà cung cấp.

Hầu hết các thiết bị không phải là loài giáp xác đều nhắm đến ma trận tương thích mới nhất chỉ sau khi giao diện bị treo, nên không có gì khác biệt trong AIDL dựa trên RELEASE_AIDL_USE_UNFROZEN.

Ma trận

Giao diện do đối tác sở hữu được thêm vào các thiết bị hoặc sản phẩm cụ thể ma trận tương thích 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, không cố định của giao diện sẽ đượ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 duy trì trong RELEASE_AIDL_USE_UNFROZEN=false. Bạn có thể xử lý vấn đề này bằng cách sử dụng nhiều tệp ma trận tương thích cho nhiều cấu hình RELEASE_AIDL_USE_UNFROZEN 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ấu hình.

Ví dụ: khi thêm phiên bản 4 không cố định, hãy sử 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 sử dụng khi RELEASE_AIDL_USE_UNFROZENfalse.

Tệp kê khai

Trong Android 15, thay đổi trong libvintf được áp dụng cho sửa đổi các tệp kê khai tại thời điểm xây dựng 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à dịch vụ triển khai. Khi sử dụng phiên bản được đóng băng mới nhất của một giao diện, tệp kê khai phải được cập nhật để phản ánh phiên bản mới này. Thời gian RELEASE_AIDL_USE_UNFROZEN=false các mục nhập tệp kê khai được điều chỉnh theo libvintf để phản ánh thay đổi trong thư viện AIDL đã tạo. Phiên bản được sửa đổi từ phiên bản chưa đóng băng, N, thành phiên bản đóng băng 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ọ.

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 đã đóng băng được hỗ trợ trước đó. Khi RELEASE_AIDL_USE_UNFROZENfalse, các dịch vụ sẽ luôn xem như phiên bản bị treo gần đây nhất hoặc phiên bản cũ hơn (ví dụ: gọi mới là phiên bản được đóng băng phương thức trả về UNKNOWN_TRANSACTION hoặc các trường parcelable mới có mặc định). Ứng dụng khung Android bắt buộc phải tương thích ngược với các phiên bản trước đó, nhưng đây là thông tin chi tiết mới cho ứ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.

Thay đổi về việc triển khai HAL

Sự khác biệt lớn nhất trong việc phát triển HAL với việc phát triển dựa trên cờ là yêu cầu về việc triển khai HAL để tương thích ngược với phiên bản bị treo 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 phiên bản .

Các cân nhắc về khả năng tương thích ngược thường giống nhau đối với ứng dụng và máy chủ, cũng như đối với mã khung và mã nhà cung cấp, nhưng có một số khác biệt nhỏ mà bạn cần lưu ý vì hiện tại, 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 bị đóng băng).

Ví dụ: Một giao diện có ba 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 phiên bản 4 mới thư viện của bạn. Vì thư viện V4 dựa trên phiên bản giao diện chưa bị đóng băng, nên thư viện này hoạt động giống như phiên bản đóng băng 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ị cố định, tất cả giá trị của RELEASE_AIDL_USE_UNFROZEN đều sử dụng giá trị đó phiên bản bị treo và mã xử lý khả năng tương thích ngược có thể bị xoá.

Khi gọi phương thức trên lệnh gọi lại, bạn phải xử lý trường hợp một cách linh hoạt khi Trả về UNKNOWN_TRANSACTION. Ứng dụng có thể đang triển khai hai phiên bản lệnh gọi lại khác nhau 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 sẽ 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 ứng dụng AIDL ổn định duy trì khả năng tương thích ngược với máy chủ được mô tả trong phần Sử dụng 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 giá trị mặc định khi RELEASE_AIDL_USE_UNFROZENfalse và các giá trị của trường mới mà dịch vụ cố gắng gửi sẽ bị loại bỏ khi thoát khỏi quy trình.

Không thể gửi những loại mới được thêm vào phiên bản đã bỏ cố định này hoặc nhận được thông qua giao diện.

Quá trình triển khai không bao giờ nhận lệnh gọi 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 bộ đếm 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ì bạn dùng foo->getInterfaceVersion() để xem phiên bản điều khiển từ xa đang sử dụng. Tuy nhiên, với tính năng hỗ trợ tạo phiên bản dựa trên cờ, bạn triển khai hai phiên bản khác nhau, vì vậy bạn có thể muốn lấy phiên bản 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.

Giao diện VINTF mới và ổn định

Khi thêm một gói giao diện AIDL mới, sẽ không có phiên bản đã đóng băng 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. Khô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à khách hàng sẽ không tìm thấy quảng cáo đó.

Bạn có thể thêm các dịch vụ theo đ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 một quy trình lớn hơn nên bạn không thể thêm dịch vụ đó vào thiết bị theo điều kiện, 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 ứng dụng được khai báo nhưng không đăng ký được, thì huỷ quá trình này. Nếu không được khai báo, thì lớp này sẽ không đăng ký được.

Mực ống làm công cụ phát triển

Mỗi năm sau khi VINTF bị đóng băng, chúng tôi điều chỉnh ma trận tương thích với khung (FCM) target-levelPRODUCT_SHIPPING_API_LEVEL của Cuttlefish để phản ánh các thiết bị ra mắt bằng bản phát hành năm tới. 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. Phiên bản này nhắm đến cấp độ FCM và PRODUCT_SHIPPING_API_LEVEL của bản phát hành Android vào năm tới, yêu cầu 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, mực ống sẽ có giá trị trước target-levelPRODUCT_SHIPPING_API_LEVEL để phản ánh thiết bị phát hành. Trên 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 thay đổi đối với FCM target-level, 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à phần phụ trợ được bật, hệ thống sẽ tự động tạo một mô-đun thư viện giả lập. Để tham chiếu đến một mô-đun thư viện giả lập 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 giả lập là ifacename-version-backend, trong đó

  • ifacename: tên của mô-đun aidl_interface
  • version là một trong
    • Vversion-number đối với các phiên bản bị treo
    • Vlatest-frozen-version-number + 1 cho phiên bản đầu cây (chưa được đóng băng)
  • backend là một trong
    • 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. Hộp cát về quyền riêng tư là dành cho ứng dụng và 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 foo và phiên bản mới nhất 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, tham chiếu đến phiên bản ổn định mới nhất trở thành foo-V2-backend
  • foo-unstable-backend tham chiếu đến phiên bản ToT sẽ 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 Điều khoản dịch vụ: foo-V3-(cpp|ndk|ndk_platform|rust).so

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 tạo phiên bản cho giao diện AIDL ổn định. Kể từ Android 12, tên mô-đun được tạo từ giao diện AIDL ổn định luôn bao gồm phiên bản của mô-đun đó.

Phương thức giao diện meta mới

Android 10 thêm 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

Ứ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 và so sánh các giá trị được trả về với các giá trị của giao diện mà khách hàng đang sử dụng.

Ví dụ với 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ù máy chủ có thể được tạo bằng phiên bản giao diện cũ hơn. Nếu giao diện meta này được triển khai trong thì lớp 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 pháp như trên, số phiên bản của giao diện được nhúng vào mã của máy chủ (vì IFoo.VERSION là một static final int cùng dòng khi được tham chiếu) do đó phương thức này có thể trả về phiên bản chính xác mà máy chủ được tạo cùng.

Xử lý giao diện cũ

Có thể ứng dụng được cập nhật bằng phiên bản giao diện AIDL mới hơn nhưng máy chủ đang sử 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, ứ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 phương thức triển khai mặc định cho giao diện AIDL. Một phương thức trong giá trị mặc định Phương thức triển khai chỉ được gọi khi phương thức không được triển khai trong điều khiển từ xa (vì nó được xây dựng bằng phiên bản giao diện cũ hơn). Vì các giá trị mặc định được đặt trên toàn cục, nên bạn không nên sử dụng các giá trị này từ các ngữ cảnh có thể được chia sẻ.

Ví dụ trong C++ trên 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 phương thức triển khai mặc định của tất cả phương thức trong giao diện AIDL. Các phương thức được đảm bảo sẽ triển khai ở phía xa (vì bạn chắc chắn rằng điều khiển từ xa được tạo khi các phương thức nằm trong Nội dung mô tả giao diện AIDL) không cần ghi đè trong 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ó mã và giao diện AIDL sử dụng giao diện này, hãy dùng những cách sau các bước chuyển đổi giao diện sang giao diện AIDL ổn định.

  1. Xác định tất 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 chưa đượ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ể phân phối trong giao diện thành các đối tượng có thể phân phối ổn định (chính các tệp giao diện có thể không thay đổi). Bạn có thể thực hiện việc này bằng cách thể hiện cấu trúc của các lớp đó ngay trong tệp AIDL. Các lớp quản lý phải được viết lại để sử dụng các 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 (dưới đây).

  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 của mô-đun đó và bất kỳ thông tin nào khác mà bạn cần. Để làm cho mã ổn định (không chỉ có cấu trúc), bạn cũng cần tạo phiên bản cho mã. Để biết thêm thông tin, hãy xem nội dung Giao diện tạo phiên bản.