Hướng dẫn quy tắc AIDL

Các phương pháp hay nhất được trình bày tại đây đóng vai trò là hướng dẫn để phát triển giao diện AIDL một cách hiệu quả và chú ý đến tính linh hoạt của giao diện, đặc biệt là khi AIDL được dùng để xác định API hoặc tương tác với các nền tảng API.

Bạn có thể sử dụng AIDL để xác định một API khi các ứng dụng cần giao tiếp với nhau trong quy trình ở chế độ nền hoặc cần giao tiếp với hệ thống. Để biết thêm thông tin về cách phát triển giao diện lập trình trong ứng dụng bằng AIDL, hãy xem nội dung Ngôn ngữ định nghĩa giao diện Android (AIDL). Để biết ví dụ về AIDL trong thực tế, hãy xem phần AIDL cho HALAIDL ổn định.

Lập phiên bản

Mỗi ảnh chụp nhanh có khả năng tương thích ngược của một API AIDL đều tương ứng với một phiên bản. Để chụp nhanh, hãy chạy m <module-name>-freeze-api. Bất cứ khi nào ứng dụng hoặc máy chủ của API được phát hành (ví dụ: trong một chuyến tàu Mainline), bạn cần chụp ảnh nhanh và tạo phiên bản mới. Đối với các API từ hệ thống đến nhà cung cấp, điều này sẽ xảy ra với bản sửa đổi nền tảng hằng năm.

Để biết thêm thông tin chi tiết và thông tin về các loại thay đổi được phép, hãy xem bài viết Giao diện tạo phiên bản.

Nguyên tắc thiết kế API

Giải pháp chung

1. Ghi lại mọi thứ

  • Ghi lại mọi phương thức về ngữ nghĩa, đối số, cách sử dụng ngoại lệ tích hợp sẵn, ngoại lệ dành riêng cho dịch vụ và giá trị trả về.
  • Ghi lại mọi giao diện cho ngữ nghĩa của giao diện đó.
  • Ghi lại ý nghĩa ngữ nghĩa của enum và hằng.
  • Ghi lại những nội dung không rõ ràng đối với người triển khai.
  • Cung cấp ví dụ nếu thích hợp.

2. Gắn vào vỏ

Sử dụng cách viết hoa Camel trên cho các loại và cách viết hoa Camel dưới cho các phương thức, trường và đối số. Ví dụ: MyParcelable cho loại có thể đóng gói và anArgument cho đối số. Đối với từ viết tắt, hãy xem từ viết tắt là một từ (NFC -> Nfc).

[-Wconst-name] Giá trị và hằng số enum phải là ENUM_VALUECONSTANT_NAME

Giao diện

1. Đặt tên

[-WInterface-name] Tên giao diện phải bắt đầu bằng I như IFoo.

2. Tránh giao diện lớn có "đối tượng" dựa trên id

Ưu tiên giao diện phụ khi có nhiều lệnh gọi liên quan đến một API cụ thể. Điều này mang lại các lợi ích sau:

  • Giúp mã máy khách hoặc mã máy chủ dễ hiểu hơn
  • Làm cho vòng đời của các đối tượng đơn giản hơn
  • Tận dụng lợi thế của các yếu tố liên kết không thể sửa đổi.

Không nên: Giao diện lớn, đơn lẻ với các đối tượng dựa trên mã nhận dạng

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Nên dùng: Giao diện riêng lẻ

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Đừng kết hợp phương thức một chiều với phương thức hai chiều

[-Wmixed-oneway] Đừng kết hợp các phương thức một chiều với các phương thức không phải một chiều, vì điều này khiến việc hiểu mô hình phân luồng trở nên phức tạp đối với máy khách và máy chủ. Cụ thể là khi đọc mã ứng dụng khách của một giao diện cụ thể, bạn cần tra cứu từng phương thức để xem phương thức đó có chặn hay không.

4. Tránh trả lại mã trạng thái

Các phương thức nên tránh dùng mã trạng thái làm giá trị trả về vì tất cả phương thức AIDL đều có mã trả về trạng thái ngầm ẩn. Hãy xem ServiceSpecificException hoặc EX_SERVICE_SPECIFIC. Theo quy ước, các giá trị này được định nghĩa dưới dạng hằng số trong giao diện AIDL. Bạn có thể xem thêm thông tin chi tiết trong mục Xử lý lỗi trong phần phụ trợ AIDL.

5. Mảng bị coi là tham số đầu ra có hại

[-Wout-array] Các phương thức có tham số đầu ra dạng mảng như void foo(out String[] ret) thường không hiệu quả vì ứng dụng phải khai báo và phân bổ kích thước mảng đầu ra trong Java, do đó máy chủ không thể chọn kích thước của đầu ra mảng. Hành vi không mong muốn này xảy ra do cách hoạt động của các mảng trong Java (không thể phân bổ lại các mảng đó). Thay vào đó, hãy ưu tiên các API như String[] foo().

6. Tránh các tham số inout

[-Winout-parameter] Điều này có thể khiến ứng dụng bị nhầm lẫn vì ngay cả tham số in cũng trông giống như tham số out.

7. Tránh sử dụng các tham số không phải mảng có giá trị rỗng và không có giá trị inout

[-Wout-null] Vì phần phụ trợ Java không xử lý chú giải @nullable trong khi các phần phụ trợ khác vẫn xử lý, nên out/inout @nullable T có thể dẫn đến hành vi không nhất quán trên các phần phụ trợ. Ví dụ: các phần phụ trợ không phải là Java có thể đặt tham số @nullable thành giá trị rỗng (trong C++, đặt tham số đó là std::nullopt) nhưng ứng dụng Java không thể đọc tham số đó là rỗng.

Gói có cấu trúc

1. Trường hợp sử dụng

Sử dụng các gói có cấu trúc mà bạn cần gửi nhiều loại dữ liệu.

Hoặc khi bạn có một loại dữ liệu duy nhất nhưng dự kiến sẽ cần mở rộng loại dữ liệu đó trong tương lai. Ví dụ: đừng sử dụng String username. Sử dụng một gói có thể mở rộng, chẳng hạn như sau:

parcelable User {
    String username;
}

Để sau này, bạn có thể mở rộng phạm vi cung cấp như sau:

parcelable User {
    String username;
    int id;
}

2. Cung cấp mặc định một cách rõ ràng

[-Wexplicit-default, -Wenum-explicit-default] Cung cấp giá trị mặc định rõ ràng cho các trường.

Gói hàng không có cấu trúc

1. Trường hợp sử dụng

Các gói không có cấu trúc có sẵn trong Java với @JavaOnlyStableParcelable và trong phần phụ trợ NDK với @NdkOnlyStableParcelable. Thông thường, đây là các gói bưu kiện đã cũ và hiện có và không thể tạo cấu trúc.

Hằng số và enum

1. Trường bit phải sử dụng các trường không đổi

Trường bit phải sử dụng các trường không đổi (ví dụ: const int FOO = 3; trong một giao diện).

2. Enum phải là các tập đóng.

Enum phải là các tập đóng. Lưu ý: chỉ chủ sở hữu giao diện mới có thể thêm các phần tử enum. Nếu nhà cung cấp hoặc OEM (Nhà sản xuất thiết bị gốc) cần mở rộng các trường này, thì cần có một cơ chế thay thế. Bất cứ khi nào có thể, bạn nên ưu tiên chức năng của nhà cung cấp dịch vụ phát trực tuyến. Tuy nhiên, trong một số trường hợp, các giá trị tuỳ chỉnh của nhà cung cấp có thể được cho phép (mặc dù vậy, nhà cung cấp nên có một cơ chế để tạo phiên bản này, có thể là chính AIDL, các giá trị này không được xung đột với nhau và các giá trị này không được hiển thị với ứng dụng bên thứ ba).

3. Tránh các giá trị như "NUM_ELEMENTS"

Vì enum được tạo phiên bản nên bạn nên tránh sử dụng các giá trị cho biết số lượng giá trị. Trong C++, bạn có thể giải quyết vấn đề này bằng enum_range<>. Đối với Rust, hãy sử dụng enum_values(). Trong Java, chưa có giải pháp.

Không nên: Sử dụng giá trị được đánh số

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Tránh các tiền tố và hậu tố thừa

[-Wredundant-name] Tránh các tiền tố và hậu tố thừa hoặc lặp lại trong hằng số và giá trị liệt kê.

Không nên: Sử dụng tiền tố thừa

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Nên dùng: Đặt tên trực tiếp cho enum

enum MyStatus {
    GOOD,
    BAD
}

Mô tả tệp

[-Wfile-descriptor] Bạn không nên sử dụng FileDescriptor làm đối số hoặc giá trị trả về của phương thức giao diện AIDL. Đặc biệt, khi AIDL được triển khai trong Java, điều này có thể gây rò rỉ chỉ số mô tả tệp trừ phi bạn xử lý cẩn thận. Về cơ bản, nếu chấp nhận một FileDescriptor, bạn cần đóng tệp đó theo cách thủ công khi không dùng nữa.

Đối với các phần phụ trợ gốc, bạn sẽ an toàn vì FileDescriptor liên kết tới unique_fd (có thể tự động đóng). Tuy nhiên, bất kể ngôn ngữ phụ trợ bạn dùng là gì, bạn không nên sử dụng FileDescriptor vì điều này sẽ hạn chế quyền tự do thay đổi ngôn ngữ phụ trợ trong tương lai.

Thay vào đó, hãy sử dụng ParcelFileDescriptor (có thể tự động đóng).

Đơn vị biến

Hãy nhớ đưa các đơn vị biến vào tên để các đơn vị của biến được xác định rõ và dễ hiểu mà không cần tham khảo tài liệu

Ví dụ

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Dấu thời gian phải cho biết sự tham chiếu

Dấu thời gian (trên thực tế là toàn bộ các đơn vị!) phải chỉ rõ đơn vị và điểm tham chiếu.

Ví dụ

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;