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 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ó các 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. Các đối tượng có thể đóng gói đại diện cho các loại ưu tiên được tạo tự động dựa trên định nghĩa AIDL và được tự động sắp xếp và huỷ sắp xếp.
- 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ụ: nội dung khai báo theo gói (gói 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à đố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 tất cả giao diện có cấu trúc đều ổ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 sẽ không ổn định nếu hệ thống bản dựng cốt lõi được dùng để tạo giao diện đó hoặc nếu bạn đặt unstable:true
.
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 AIDLFoo
được xác định trong góicom.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ứaAndroid.bp
. Trong ví dụ trước,<base_path>
là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ô-đunaidl_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ộtaidl_interface
khác, hãy đặt tên của giao diện đó vào đây. Đây có thể là tên riêng để 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ể. Tính năng chỉ định phiên bản được hỗ trợ kể từ Android 12versions
: Các phiên bản giao diện trước đó bị đóng băng trongapi_dir
. Kể từ Android 11,versions
bị đóng băng trongaidl_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ằngversions_with_info
cho 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 cố định và một danh sách có các lệnh nhập phiên bản của các mô-đun aidl_Interface khác mà phiên bản aidl_ giao diện này đã nhập. Định nghĩa của phiên bản V của IFACE giao diện AIDL nằm tạiaidl_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 trongAndroid.bp
. Bạn có thể thêm hoặc cập nhật trường này bằng cách gọi*-update-api
hoặc*-freeze-api
. Ngoài ra, các trườngversions
sẽ tự động di chuyển sangversions_with_info
khi người dùng gọi*-update-api
hoặc*-freeze-api
.stability
: Cờ tuỳ chọn cho cam kết về độ ổ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 đặtstability
, hệ thống xây dựng sẽ kiểm tra để đảm bảo rằng giao diện này có khả năng tương thích ngược trừ phi bạn chỉ địnhunstable
. 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 trongsystem.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 trongvendor.img
và các phân vùng liên quan). Nếu bạn đặtstability
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ợcpp
vàjava
.host_supported
: Cờ không bắt buộc, khi được đặt thànhtrue
, 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 ổn định. Khi bạn đặt giá trị này thànhtrue
, 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ànhtrue
, 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 hoạt động kiểm tra thời gian xây dựng hơn. Khi được đặt thànhfalse
, đ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ạyfoo-freeze-api
sẽ tạo một phiên bản mới và tự động thay đổi giá trị thànhtrue
. Ra mắt trong Android 14.backend.<type>.enabled
: Các cờ này bật/tắt từng phần phụ trợ mà trình biên dịch AIDL tạo mã. Hỗ trợ 4 phần phụ trợ: Java, C++, NDK và Rust. Theo mặc định, các phần phụ trợ Java, C++ và NDK được bật. Nếu không cần đến một trong ba phần phụ trợ này, bạn cần tắt phần phụ trợ đó một cách rõ ràng. Rust bị tắt theo mặc định 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 để đưa giao diện này vào VNDK. Giá trị mặc định làfalse
.backend.[cpp|ndk].additional_shared_libraries
: Được ra mắt 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ớindk_header
vàcpp_header
.backend.java.sdk_version
: Cờ không bắt buộc để chỉ định phiên bản SDK mà thư viện giả lập Java được tạo dựa trên đó. Giá trị mặc định là"system_current"
. Bạn không nên đặt giá trị này khibackend.java.platform_apis
làtrue
.backend.java.platform_apis
: Cờ không bắt buộc nên được đặt thànhtrue
khi các 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 thư viện giả lập sẽ được tạo. Để biết cách tham chiếu đến phiên bản cụ thể của thư viện giả lập cho 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, ngoại trừ việc chúng không được phép sử dụng các gói có thể phân phối không có cấu trúc (vì các gói này không ổn định! hãy xem phần 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 gói. Trước đây, các gói đượ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ủa gói đó đượ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;
}
Giá trị mặc định được hỗ trợ (nhưng không bắt buộc) cho boolean
, char
, float
, double
, byte
, int
, long
và String
. Trong Android 12, các giá trị mặc định cho các giá trị 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, hệ thống sẽ sử dụng giá trị giống 0 hoặc giá trị trống.
Các enum không có giá trị mặc định sẽ được khởi tạo thành 0 ngay cả khi không có bộ đếm số không.
Sử dụng thư viện giả lập
Sau khi thêm thư viện giả lập làm phần phụ thuộc vào mô-đun, bạn có thể đưa các thư viện đó vào tệp. Dưới đây là ví dụ về thư viện giả lập trong hệ thống xây dựng (Android.mk
cũng có thể được 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
Lập phiên bản giao diện
Việc khai báo mô-đun có tên foo cũng 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 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 và thêm một tệp .hash
, cả hai đều đại diện cho phiên bản giao diện mới được đóng băng. 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 chỉ định thuộc tính versions_with_info
, hệ thống xây dựng sẽ chạy quá trình 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 Top of Tree (ToT) với 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ì sự ổ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ó chuỗi mới được xác định rõ ràng)
- Các phần tử nằm cuối gói theo gói (bắt buộc phải thêm giá trị mặc định cho mỗi phần tử)
- Giá trị không đổi
- Trong Android 11, bộ đếm
- Trong Android 12, các trường đến cuối kết hợp
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, chúng 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 được đó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ườngowner:
được chỉ định.AIDL_FROZEN_OWNERS="aosp test"
– bản dựng yêu cầu cố định mọi giao diện AIDL ổn định với trườngowner:
đượ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 đã đóng băng sẽ 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 yêu cầu cập nhật tất cả máy chủ và ứng dụng sử dụng phiên bản giao diện trước đó, đồ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 loại khác nhau. Nhìn chung, đối với các gói chỉ dùng loại hoặc gói phổ biến, điều này là an toàn vì cần phải viết mã để 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 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 đượcSTATUS_UNKNOWN_TRANSACTION
. - Phần phụ trợ
java
nhận đượcandroid.os.RemoteException
với 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 phiên bản và sử dụng mặc định.
Parcelable
Khi bạn thêm các trường mới vào các gói được, các ứng dụng và máy chủ cũ sẽ loại bỏ những trường đó. Khi máy khách và máy chủ mới nhận được các bưu kiện cũ, giá trị mặc định cho trường mới sẽ tự động được điền vào. Đ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ể phân phối.
Ứng dụng không nên trông đợi máy chủ sử dụng các trường mới trừ phi họ biết máy chủ đang triển khai phiên bản đã xác định trường đó (xem các phiên bản truy vấ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ị hằng số và bộ đếm trong tương lai. Ví dụ: một máy chủ không nên huỷ khi nhận được một trình liệt kê mà máy chủ không biết. Máy chủ phải bỏ qua bộ đếm hoặc trả về một giá trị nào đó để ứng dụng biết rằng bộ đếm không được hỗ trợ trong quá trình triển khai này.
Liên minh
Bạn sẽ không thể gửi một union có trường mới nếu trình nhận là cũ và không biết về trường đó. Quá trình triển khai sẽ không bao giờ 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 này sẽ xảy ra nếu ứng dụng đang gửi một liên kết được đặt thành trường mới đến một máy chủ cũ hoặc khi ứng dụng cũ nhận được liên kết từ mộ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 sẽ báo lỗi khi một mô-đun phụ thuộc vào nhiều phiên bản của cùng một thư viện aidl_interface
. Mô-đun có thể phụ thuộc vào các thư viện này trực tiếp hoặc gián tiếp thông qua các phần phụ thuộc của các phần phụ thuộc đó. Các 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 sử dụng thư viện giao diện, bạn nên tạo cc_defaults
, java_defaults
và rust_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 phiên bản giao diện mới, các giá trị mặc định đó có thể được cập nhật và tất cả mô-đun sử dụng chúng sẽ được cập nhật cùng nhau, đảm bảo chúng không sử dụng các phiên bản giao diện khác nhau.
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 phải sử dụng cùng nhau các phiên bản cụ thể. 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
sử dụng cùng nhau trong cùng một quy trình.
Bạn có thể sử dụng aidl_interfaces_defaults
để giữ một định nghĩa của các phiên bản phần phụ thuộc mới nhất cho aidl_interface
(có thể được cập nhật ở một nơi duy nhất) và được sử 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ờ
Không thể sử dụng các giao diện đang phát triển (chưa bị đóng băng) trên các thiết bị phát hành vì các giao diện này không được đảm bảo 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 bị đóng băng này để mã được viết dựa trên phiên bản chưa bị đóng băng 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 ứng dụng tương tự như hành vi hiện có và với phương thức 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ờ 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ố định của giao diện sẽ được sử dụng trong thời gian chạy và false
có nghĩa là thư viện của các phiên bản không được cố định đề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
để phát triển cục bộ, nhưng phải chuyể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 với 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 dự kiến và phiên bản được cung cấp ở một trong hai bên của giao diện nhà cung cấp.
Hầu hết các thiết bị không phải là mực ống đều chỉ nhắm đến ma trận tương thích mới nhất sau khi giao diện bị treo, do đó, không có gì khác biệt trong các thư viện AIDL dựa trên RELEASE_AIDL_USE_UNFROZEN
.
Ma trận
Các giao diện do đối tác sở hữu đượ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 giao diện mới, chưa bị đóng băng được thêm vào ma trận tương thích, các phiên bản đã đóng băng trước đó cần phải được giữ lại cho 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 không cố định 4, 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_UNFROZEN
là false
.
Tệp kê khai
Trong Android 15, chúng tôi đã thay đổi libvintf
để sửa đổi các tệp kê khai tại thời điểm tạo bản 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 không cố định mới nhất 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
, các mục nhập tệp kê khai sẽ được điều chỉnh bằng 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 ứ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_UNFROZEN
là false
, các dịch vụ luôn giống như phiên bản đã đóng băng gần đây nhất hoặc phiên bản cũ hơn (ví dụ: lệnh gọi các phương thức mới chưa đóng băng sẽ trả về UNKNOWN_TRANSACTION
hoặc các trường parcelable
mới sẽ có giá trị 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
Điểm khác biệt lớn nhất trong quá trình phát triển HAL với phát triển dựa trên cờ là yêu cầu triển khai HAL phải tương thích ngược với phiên bản đóng băng gần đây nhất để hoạt động khi RELEASE_AIDL_USE_UNFROZEN
là false
.
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 giao diện có 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 bị treo. 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 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_UNFROZEN
là false
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ả giá trị của RELEASE_AIDL_USE_UNFROZEN
sẽ 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 phương thức trên các lệnh gọi lại, bạn phải xử lý linh hoạt trường hợp khi UNKNOWN_TRANSACTION
được trả về. Ứ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_UNFROZEN
là false
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.
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 không bị đóng băng này thông qua giao diện.
Quá trình 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_UNFROZEN
là false
.
Hãy lưu ý chỉ sử dụng liệt kê mới với phiên bản được giới thiệu, chứ không phải phiên bản trước đó.
Thông thường, bạn sử dụng foo->getInterfaceVersion()
để xem giao diện từ xa đang sử dụng phiên bản nào. Tuy nhiên, với tính năng hỗ trợ tạo phiên bản dựa trên cờ, bạn đang triển khai hai phiên bản khác nhau, vì vậy, bạn nên tải 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.
Giao diện ổn định mới của VINTF
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_UNFROZEN
là false
. Đừng sử dụng những giao diện này. Khi RELEASE_AIDL_USE_UNFROZEN
là false
, Trình quản lý dịch vụ sẽ không cho phép dịch vụ đăng ký giao diện và ứ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 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 hệ thống khai báo lệnh này nhưng không đăng ký được, hãy huỷ quá trình này. Nếu bạn không khai báo miền này, thì hệ thống có thể 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-level
và PRODUCT_SHIPPING_API_LEVEL
của Cuttlefish để phản ánh các thiết bị ra mắt với bản phát hành năm tới. Chúng tôi điều chỉnh target-level
và PRODUCT_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_UNFROZEN
là true
, 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_UNFROZEN
là false
, Cuttlefish có target-level
và PRODUCT_SHIPPING_API_LEVEL
trước đó để phản ánh một 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ô-đunaidl_interface
version
là một trong haiVversion-number
đối với các phiên bản bị treoVlatest-frozen-version-number + 1
cho phiên bản đầu cây (chưa được đóng băng)
backend
là một trong haijava
cho phần phụ trợ Java,cpp
cho phần phụ trợ C++,ndk
hoặcndk_platform
cho phần phụ trợ NDK. Phương thức trước dành cho ứng dụng và phương thức 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ụngndk
.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ànhfoo-V2-backend
foo-unstable-backend
tham chiếu đến phiên bản ToT sẽ trở thànhfoo-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ừ giao diện AIDL ổn định luôn bao gồm cả phiên bản của giao diện đó.
Các 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à ứng dụ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ề 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ới 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()
và 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ù phiên bản này có thể được tạo bằng một phiên bản
cũ 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 được nhúng vào mã của máy chủ (vì IFoo.VERSION
là static final int
cùng dòng khi được tham chiếu) và do đó, phương thức này có thể trả về phiên bản chính xác mà máy chủ được xây dựng cùng.
Xử lý các 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 thành 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 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ụ về 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 phương thức triển khai mặc định cho 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 được đảm bảo sẽ được triển khai ở phía từ xa (vì bạn chắc chắn rằng thiết bị 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ó sẵn giao diện và mã AIDL sử dụng giao diện đó, hãy thực hiện các bước sau để chuyển đổi giao diện sang giao diện AIDL ổn định.
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 không được xác định, bạn phải chuyển đổi gói.
Chuyển đổi tất cả các gói được trong giao diện của bạn thành các gói ổn định (bản thân 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 biểu thị cấu trúc của các lớp đó ngay trong tệp AIDL. Bạn phải viết lại các lớp quản lý để 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
(bên dưới).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à mọi thông tin khác mà bạn cần. Để ổn định (không chỉ có cấu trúc), bạn cũng cần phải tạo phiên bản cho mã nguồn. Để biết thêm thông tin, hãy xem phần Tạo phiên bản giao diện.