Hướng dẫn phong cách AIDL

Các phương pháp hay nhất được nêu ở đâ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 khi AIDL được sử dụng để xác định API hoặc tương tác với các bề mặt API.

AIDL có thể được sử dụng để xác định API khi các ứng dụng cần giao tiếp với nhau trong quy trình 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 Ngôn ngữ định nghĩa giao diện Android (AIDL) . Để biết ví dụ về AIDL trong thực tế, hãy xem AIDL cho HALAIDL ổn định .

Phiên bản

Mỗi ảnh chụp nhanh tương thích ngược của API AIDL đều tương ứng với một phiên bản. Để chụp ảnh nhanh, hãy chạy m <module-name>-freeze-api . Bất cứ khi nào máy khách hoặc máy chủ của API được phát hành (ví dụ: trên tàu tuyến chính), bạn cần chụp ảnh nhanh và tạo phiên bản mới. Đối với 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 chi tiết và thông tin về loại thay đổi được phép, hãy xem Giao diện lập phiên bản .

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

Tổng quan

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 các ngoại lệ tích hợp, ngoại lệ cụ thể của dịch vụ và giá trị trả về.
  • Ghi lại mọi giao diện về ngữ nghĩa của nó.
  • Ghi lại ý nghĩa ngữ nghĩa của enum và hằng.
  • Ghi lại bất cứ điều gì có thể không rõ ràng đối với người thực hiện.
  • Cung cấp các ví dụ khi có liên quan.

2. Vỏ bọc

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

[-Wconst-name] Các 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 nên bắt đầu bằng I like IFoo .

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

Ưu tiên các 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 những lợi ích sau: - Làm cho mã máy khách/máy chủ dễ hiểu hơn - Làm cho vòng đời của các đối tượng trở nên đơn giản hơn - Tận dụng lợi thế của các chất kết dính không thể giả mạo.

Không được đề xuất: Một giao diện lớn, đơn lẻ với các đối tượng dựa trên id

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
}

Khuyến nghị: Giao diện phụ riêng lẻ

interface IManager {
    IFoo getFoo();
}

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

3. Đừng trộn lẫn phương pháp một chiều với hai chiều

[-Wmixed-oneway] Không kết hợp các phương pháp một chiều với các phương pháp không một chiều, vì nó làm cho việc hiểu mô hình luồng trở nên phức tạp đối với máy khách và máy chủ. Cụ thể, khi đọc mã client 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ả các phương thức AIDL đều có mã trả về trạng thái ẩn. Xem ServiceSpecificException hoặc EX_SERVICE_SPECIFIC . Theo quy ước, các giá trị này được xác định là hằng số trong giao diện AIDL. Thông tin chi tiết hơn có trong phần Xử lý lỗi của AIDL Backends .

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

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

6. Tránh tham số inout

[-Winout-parameter] Điều này có thể gây nhầm lẫn cho khách hàng vì ngay cả in các tham số cũng giống như các tham số out .

7. Tránh các tham số không phải mảng out/inout @nullable

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

Các bưu kiện có cấu trúc

1. Khi nào nên sử dụng

Sử dụng các bưu kiện có cấu trúc trong đó bạn có nhiều loại dữ liệu để gửi.

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

parcelable User {
    String username;
}

Vì vậy, trong tương lai, bạn có thể mở rộng nó như sau:

parcelable User {
    String username;
    int id;
}

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

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

Các bưu kiện không có cấu trúc

1. Khi nào nên sử dụng

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

Hằng và Enum

1. Bitfield nên sử dụng các trường không đổi

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

2. Enums phải là tập hợp đóng.

Enums phải là tập hợ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 cần mở rộng các trường này thì cần có cơ chế thay thế. Bất cứ khi nào có thể, chức năng ngược dòng của nhà cung cấp nên được ưu tiên. Tuy nhiên, trong một số trường hợp, các giá trị tùy chỉnh của nhà cung cấp có thể được cho phép thông qua (tuy nhiên, nhà cung cấp phải có sẵn cơ chế để phiên bản này, có lẽ là chính AIDL, chúng không thể xung đột với nhau và các giá trị này không được phép xung đột với nhau. tiếp xúc với các ứng dụng của bên thứ 3).

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

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

Không khuyến khích: Sử dụng các giá trị được đánh số

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

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

[-Wredundant-name] Tránh các tiền tố và hậu tố dư thừa hoặc lặp đi lặp lại trong hằng số và điều tra viên.

Không khuyến khích: Sử dụng tiền tố dư thừa

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Khuyến nghị: Đặt tên trực tiếp cho enum

enum MyStatus {
    GOOD,
    BAD
}

Bộ mô tả tệp

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

Đối với các chương trình phụ trợ gốc, bạn an toàn vì FileDescriptor ánh xạ tới unique_fd có thể tự động đóng. Nhưng bất kể bạn sẽ sử dụng ngôn ngữ phụ trợ nào, tốt nhất là KHÔNG sử dụng FileDescriptor vì điều này sẽ hạn chế quyền tự do của bạn trong việc 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

Đảm bảo rằng các đơn vị biến được bao gồm trong tên để các đơn vị của chúng được xác định và hiểu rõ 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 tham chiếu của chúng

Dấu thời gian (trên thực tế là tất cả các đơn vị!) phải ghi rõ đơn vị và điểm tham chiếu của chúng.

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;