Google cam kết thúc đẩy công bằng chủng tộc cho Cộng đồng người da đen. Xem cách thực hiện.

AIDL ổn định

Android 10 bổ sung hỗ trợ cho 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)/giao diện nhị phân ứng dụng (ABI) do giao diện AIDL cung cấp. AIDL ổn định có những điểm khác biệt chính sau đây so với AIDL:

  • Các giao diện được xác định trong hệ thống xây dựng với aidl_interfaces .
  • Giao diện chỉ có thể chứa dữ liệu có cấu trúc. Các bưu kiện đại diện cho các loại mong muốn được tạo tự động dựa trên định nghĩa AIDL của chúng và được tự động sắp xếp theo thứ tự và không sắp xếp theo thứ tự.
  • Các giao diện có thể được khai báo là ổn định (tương thích ngược). Khi điều này xảy ra, API của họ được theo dõi và lập phiên bản trong một tệp bên cạnh giao diện AIDL.

Xác định giao diện AIDL

Định nghĩa của aidl_interface giố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: ["ohter-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 xác định duy nhất một giao diện AIDL.
  • srcs : Danh sách các tệp nguồn AIDL tạo giao diện. Đường dẫn cho loại AIDL Foo được xác định trong gói com.acme phải ở <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ên, <base_path>srcs/aidl .
  • local_include_dir : Đường dẫn từ nơi tên gói bắt đầu. Nó tương ứng với <base_path> đã giải thích ở trên.
  • imports : Một danh sách các mô-đun aidl_interface mà cái 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 giao diện có thể phân phối từ một aidl_interface khác, hãy đặt tên của giao diện đó ở đây. Đây có thể là tên của chính 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ể. Chỉ định một 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 bị đóng băng trong api_dir , Bắt đầu từ Android 11, các versions này bị đóng băng dưới aidl_api/ name . Nếu không có phiên bản cố định nào của giao diện, thì điều này sẽ không được chỉ định và sẽ không có kiểm tra tính tương thích. Trường này đã được thay thế bằng versions_with_info cho phiên bản 13 trở lên.
  • versions_with_info : Danh sách các bộ, mỗi bộ chứa tên của phiên bản cố định và danh sách có 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 về phiên bản V của giao diện AIDL IFACE được đặt 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 đượ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 được tự động di chuyển sang versions_with_info khi người dùng gọi *-update-api hoặc *-freeze-api .
  • stability : Cờ tùy chọn cho lời hứa ổn định của giao diện này. Hiện chỉ hỗ trợ "vintf" . Nếu điều này không được đặt, thì điều này tương ứng với một giao diện có tính ổn định trong bối cảnh biên dịch này (vì vậy, một giao diện được tải ở đây chỉ có thể được sử dụng với những thứ được biên dịch cùng nhau, chẳng hạn như trên system.img). Nếu điều này được đặt thành "vintf" , điều này tương ứng với lời hứa ổn định: giao diện phải được giữ ổn định miễn là nó được sử dụng.
  • gen_trace : Cờ tùy chọn để bật hoặc tắt theo dõi. Mặc định là false .
  • host_supported : Cờ tùy chọn khi được đặt thành true sẽ cung cấp các thư viện được tạo cho môi trường máy chủ.
  • unstable : Cờ tùy chọn được sử dụng để đánh dấu rằng giao diện này không cần phải ổn định. Khi điều này được đặt thành true , hệ thống bản dựng sẽ không tạo kết xuất API cho giao diện cũng như không yêu cầu cập nhật giao diện.
  • 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. Hiện tại, bốn chương trình phụ trợ được hỗ trợ: Java, C++, NDK và Rust. Các chương trình phụ trợ Java, C++ và NDK được bật theo mặc định. Nếu bất kỳ phụ trợ nào trong số ba phụ trợ này không cần thiết, thì nó cần phải được tắt một cách rõ ràng. Rust bị tắt theo mặc định.
  • backend.<type>.apex_available : Danh sách các tên APEX mà thư viện sơ khai đã tạo có sẵn cho.
  • backend.[cpp|java].gen_log : Cờ tùy chọn 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ờ tùy chọn để biến giao diện này thành một phần của VNDK. Mặc định là false .
  • backend.java.platform_apis : Cờ tùy chọn kiểm soát xem thư viện sơ khai Java có được xây dựng dựa trên các API riêng từ nền tảng hay không. Điều này phải được đặt thành "true" khi stability được đặt thành "vintf" .
  • backend.java.sdk_version : Cờ tùy chọn để chỉ định phiên bản SDK mà thư viện sơ khai Java được xây dựng dựa trên đó. Mặc định là "system_current" . Điều này không nên được đặt khi backend.java.platform_apis là đúng.
  • backend.java.platform_apis : Cờ tùy chọn sẽ được đặt thành true khi các thư viện được 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 sự kết hợp của các phiên bản và phụ trợ được kích hoạt, một thư viện sơ khai sẽ được tạo. Để biết cách tham khảo phiên bản cụ thể của thư viện sơ khai cho một chương trình 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 gói có thể phân phối không có cấu trúc (vì chúng không ổn định!). Sự khác biệt chính trong AIDL ổn định là cách xác định các gói có thể phân phối được. Trước đây, bưu phẩm được khai báo chuyển tiếp ; trong AIDL ổn định, các trường và biến có thể chuyển nhượng đượ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 hiện được hỗ trợ (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 kiểu liệt kê 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 như 0 hoặc giá trị trống sẽ được sử dụng. Các liệt kê không có giá trị mặc định được khởi tạo thành 0 ngay cả khi không có điều tra viên bằng không.

Sử dụng thư viện sơ khai

Sau khi thêm các thư viện sơ khai làm phần phụ thuộc vào mô-đun của bạn, bạn có thể đưa chúng vào các tệp của mình. Dưới đây là ví dụ về các thư viện sơ khai 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 kế thừa):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire 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: ...,
    rust_libs: ["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

Phiên bản giao diện

Khai báo một 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 xây dựng, foo-freeze-api thêm một định nghĩa API mới dưới api_dir hoặc aidl_api/ name , tùy thuộc vào phiên bản Android và thêm 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 . Nhưng phiên bản ổn định mới nhất được chỉ định trong các lần imports trong versions_with_info cho lần nhập không có phiên bản rõ ràng. 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 đã đóng băng cũng như giữa Top of Tree (ToT) và phiên bản đã đóng băng 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 mới:

  • 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ử ở cuối phần tử có thể phân phối (yêu cầu thêm giá trị mặc định cho mỗi phần tử)
  • Giá trị không đổi
  • Trong Android 11, điều tra viên
  • Trong Android 12, các trường ở cuối liên kết

Không có hành động nào khác được phép 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 tra xem tất cả các giao diện có bị đóng băng để phát hành hay không, bạn có thể xây dựng với bộ biến môi trường sau:

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

Sự ổn định của hàng nhập khẩu

Cập nhật các phiên bản nhập cho các phiên bản cố định của giao diện tương thích ngược ở lớp AIDL ổn định. Tuy nhiên, việc cập nhật những điều này yêu cầu cập nhật tất cả các máy chủ và máy khách sử dụng phiên bản cũ của giao diện và một số ứng dụng có thể bị nhầm lẫn khi trộn các phiên bản khác nhau của các loại. Nói chung, đối với các gói chỉ loại hoặc chung, điều này là an toàn vì mã cần phải đượ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ề kiểu 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 pháp 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áy chủ cũ, các máy khách mới sẽ gặp lỗi hoặc ngoại lệ, tùy thuộc vào chương trình phụ trợ.

  • chương trình phụ trợ cpp được ::android::UNKNOWN_TRANSACTION .
  • chương trình phụ trợ ndk nhận STATUS_UNKNOWN_TRANSACTION .
  • chương trình phụ trợ java nhận được android.os.RemoteException với thông báo cho biết API không được triển khai.

Để 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 các giá trị mặc định .

bưu kiện

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

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

Enums và hằng số

Tương tự, máy khách và máy chủ nên từ chối hoặc bỏ qua các giá trị không đổi không được nhận dạng và các điều tra viên khi thích hợp, vì có thể thêm nhiều giá trị khác trong tương lai. Ví dụ: một máy chủ không nên hủy bỏ khi nhận được một điều tra viên mà nó không biết. Nó nên bỏ qua nó hoặc trả lại một cái gì đó để khách hàng biết rằng nó không được hỗ trợ trong quá trình triển khai này.

đoàn thể

Cố gắng gửi liên kết với một trường mới không thành công nếu người nhận cũ và không biết về trường này. Việc triển khai sẽ không bao giờ thấy sự kết hợp với trường mới. Lỗi bị bỏ qua nếu đó là giao dịch một chiều; nếu không thì lỗi 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). Đã nhận được lỗi nếu máy khách đang gửi tập hợp liên kết đến trường mới đến máy chủ cũ hoặc khi đó là máy khách cũ nhận liên kết từ máy chủ mới.

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

Trong Android 11, đối với mỗi sự kết hợp của các phiên bản và phần phụ trợ được bật, một mô-đun thư viện sơ khai sẽ tự động được tạo. Để tham chiếu đến một mô-đun thư viện sơ khai cụ thể để liên kết, khô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 sơ khai, đó là ifacename - version - backend , trong đó

  • ifacename : tên của mô-đun aidl_interface
  • version là một trong hai
    • V version-number cho các phiên bản đông lạnh
    • V latest-frozen-version-number + 1 cho phiên bản ngọn cây (chưa bị đóng băng)
  • backend là một trong hai
    • java cho chương trình phụ trợ Java,
    • cpp cho chương trình phụ trợ C++,
    • ndk hoặc ndk_platform cho 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,
    • rust cho chương trình phụ trợ Rust.

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

  • 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 gọi là phiên bản ổn định mới nhất trở thành foo- V2 - backend
  • foo-unstable- backend , được gọi là 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

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 phiên bản của nó.

Phương pháp 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

Máy khách 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 giá trị của giao diện mà máy khách đang sử dụng.

Ví dụ với chương trình 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ụ 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 chương trình 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 sử dụng thay vì IFoo để tránh lỗi sao chép/dán. Chú thích @SuppressWarnings("static") có thể cần thiết để tắt cảnh báo, tùy 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ù nó có thể đã được xây dựng với 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ì nó 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 trong mã của máy chủ (vì IFoo.VERSION là một static final int được đặt nội tuyến khi được tham chiếu) và do đó, phương thức có thể trả về phiên bản chính xác mà máy chủ đã tạo với.

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

Có thể một máy khách được cập nhật với phiên bản mới hơn của giao diện AIDL 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, khách hàng có nhiều quyền kiểm soát hơn. Ở phía máy khách, bạn có thể đặt triển khai mặc định thành giao diện AIDL. Một phương thức trong triển khai mặc định chỉ được gọi khi phương thức đó không được triển khai ở phía xa (vì nó được xây dựng với 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 chúng không nên được sử dụng từ các ngữ cảnh có khả năng đượ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 cài đặt mặc định cho tất cả các phương thức trong giao diện AIDL. Các phương thức được đảm bảo 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 mô tả giao diện AIDL) không cần phải ghi đè trong lớp impl mặc định.

Chuyển đổi AIDL hiện tại sang AIDL có cấu trú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ụ thuộc của giao diện của bạ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 bưu kiện trong giao diện của bạn thành các bưu kiện ổn định (bản thân các tệp giao diện có thể không thay đổi). Thực hiện điều này bằng cách thể hiện trực tiếp cấu trúc của chúng trong các tệp AIDL. Các lớp quản lý phải được viết lại để sử dụng các kiểu mới này. Điều này có thể được thực hiện trước khi bạn tạo gói aidl_interface (bên dưới).

  3. Tạo một gói aidl_interface (như được mô tả ở trên) chứa tên mô-đun của bạn, các thành 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 nó ổn định (không chỉ có cấu trúc), nó cũng cần được tạo phiên bản. Để biết thêm thông tin, hãy xem Lập phiên bản giao diện .