Руководство по стилю AIDL

Изложенные здесь лучшие практики служат руководством по эффективной разработке интерфейсов AIDL с вниманием к гибкости интерфейса, особенно когда AIDL используется для определения API или взаимодействия с поверхностями API.

AIDL можно использовать для определения API, когда приложениям необходимо взаимодействовать друг с другом в фоновом процессе или с системой. Дополнительные сведения о разработке интерфейсов программирования в приложениях с помощью AIDL см. в разделе Язык определения интерфейса Android (AIDL) . Примеры использования AIDL на практике см. в разделах AIDL для HAL и Stable AIDL .

Управление версиями

Каждый обратно совместимый снимок AIDL API соответствует версии. Чтобы сделать снимок, запустите m <module-name>-freeze-api . Всякий раз, когда выпускается клиент или сервер API (например, в поезде Mainline), вам необходимо сделать снимок и сделать новую версию. Для API-интерфейсов системы-поставщика это должно происходить при ежегодном обновлении платформы.

Дополнительные сведения и сведения о типах разрешенных изменений см. в разделе Интерфейсы управления версиями .

Рекомендации по проектированию API

Общий

1. Документируйте все

  • Документируйте каждый метод с учетом его семантики, аргументов, использования встроенных исключений, исключений, специфичных для службы, и возвращаемого значения.
  • Документируйте каждый интерфейс на предмет его семантики.
  • Документируйте семантическое значение перечислений и констант.
  • Документируйте все, что может быть непонятно разработчику.
  • Приведите примеры, где это уместно.

2. Корпус

Используйте верхний регистр для типов и нижний регистр для методов, полей и аргументов. Например, MyParcelable для разделяемого типа и anArgument для аргумента. Что касается аббревиатур, считайте аббревиатуру словом ( NFC -> Nfc ).

[-Wconst-name] Значения перечисления и константы должны быть ENUM_VALUE и CONSTANT_NAME

Интерфейсы

1. Именование

[-Winterface-name] Имя интерфейса должно начинаться с I нравится IFoo .

2. Избегайте большого интерфейса с «объектами» на основе идентификаторов.

Предпочитайте подинтерфейсы, когда есть много вызовов, связанных с конкретным API. Это обеспечивает следующие преимущества:

  • Облегчает понимание кода клиента или сервера.
  • Упрощает жизненный цикл объектов
  • Использует преимущества того, что переплетные материалы невозможно подделать.

Не рекомендуется: единый большой интерфейс с объектами на основе идентификаторов.

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
}

Рекомендуется: индивидуальные интерфейсы.

interface IManager {
    IFoo getFoo();
}

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

3. Не смешивайте односторонние и двусторонние методы

[-Wmixed-oneway] Не смешивайте односторонние и неодносторонние методы, поскольку это усложняет понимание модели потоков для клиентов и серверов. В частности, при чтении клиентского кода определенного интерфейса вам необходимо для каждого метода искать, будет ли этот метод блокироваться или нет.

4. Избегайте возврата кодов состояния

Методам следует избегать использования кодов состояния в качестве возвращаемых значений, поскольку все методы AIDL имеют неявный код возврата состояния. См. ServiceSpecificException или EX_SERVICE_SPECIFIC . По соглашению эти значения определяются как константы в интерфейсе AIDL. Более подробная информация находится в разделе «Обработка ошибок» бэкендов AIDL .

5. Массивы как выходные параметры считаются вредными

[-Wout-array] Методы, имеющие выходные параметры массива, такие как void foo(out String[] ret) обычно являются плохими, поскольку размер выходного массива должен быть объявлен и выделен клиентом в Java, и поэтому размер выходного массива не может быть выбран сервером. Такое нежелательное поведение происходит из-за того, как массивы работают в Java (их нельзя перераспределить). Вместо этого предпочитайте API, такие как String[] foo() .

6. Избегайте входных параметров

[-Winout-parameter] Это может сбить с толку клиентов, поскольку даже in параметры выглядят как out параметры.

7. Избегайте out и inout @nullable параметров, не являющихся массивами.

[-Wout-nullable] Поскольку серверная часть Java не обрабатывает аннотацию @nullable , в отличие от других серверных частей, out/inout @nullable T может привести к несогласованному поведению между серверными модулями. Например, бэкэнды, не поддерживающие Java, могут установить для параметра out @nullable значение null (в C++ — как std::nullopt ), но клиент Java не может прочитать его как null.

Структурированные посылки

1. Когда использовать

Используйте структурированные посылки, если у вас есть несколько типов данных для отправки.

Или когда у вас есть один тип данных, но вы ожидаете, что вам придется расширить его в будущем. Например, не используйте String username . Используйте расширяемый объект, например:

parcelable User {
    String username;
}

Чтобы в будущем вы могли расширить его следующим образом:

parcelable User {
    String username;
    int id;
}

2. Явно укажите значения по умолчанию

[-Wexplicit-default, -Wenum-explicit-default] Укажите явные значения по умолчанию для полей.

Неструктурированные посылки

1. Когда использовать

Неструктурированные посылки доступны в Java с помощью @JavaOnlyStableParcelable и в серверной части NDK с помощью @NdkOnlyStableParcelable . Обычно это старые и существующие посылки, которые невозможно структурировать.

Константы и перечисления

1. Битовые поля должны использовать константные поля.

Битовые поля должны использовать константные поля (например, const int FOO = 3; в интерфейсе).

2. Перечисления должны быть закрытыми множествами.

Перечисления должны быть закрытыми множествами. Примечание. Только владелец интерфейса может добавлять элементы перечисления. Если поставщикам или OEM-производителям необходимо расширить эти поля, необходим альтернативный механизм. По возможности следует отдавать предпочтение функциям восходящего поставщика. Однако в некоторых случаях могут быть разрешены пользовательские значения поставщика (однако у поставщиков должен быть механизм для создания версий, возможно, сам AIDL, они не должны конфликтовать друг с другом, и эти значения не должны быть подвергается воздействию сторонних приложений).

3. Избегайте таких значений, как «NUM_ELEMENTS».

Поскольку перечисления имеют версии, следует избегать значений, указывающих количество присутствующих значений. В C++ это можно обойти с помощью enum_range<> . Для Rust используйте enum_values() . В Java решения пока нет.

Не рекомендуется: использование числовых значений.

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

4. Избегайте лишних префиксов и суффиксов.

[-Wredundant-name] Избегайте избыточных или повторяющихся префиксов и суффиксов в константах и ​​перечислителях.

Не рекомендуется: использование избыточного префикса.

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Рекомендуется: прямое указание перечисления.

enum MyStatus {
    GOOD,
    BAD
}

Файловыйдескриптор

[-Wfile-descriptor] Использование FileDescriptor в качестве аргумента или возвращаемого значения метода интерфейса AIDL настоятельно не рекомендуется. Особенно, когда AIDL реализован на Java, это может привести к утечке файлового дескриптора, если не принять меры предосторожности. По сути, если вы принимаете FileDescriptor , вам необходимо закрыть его вручную, когда он больше не используется.

При использовании собственных бэкэндов вы в безопасности, поскольку FileDescriptor сопоставляется с unique_fd , который автоматически закрывается. Но независимо от того, какой серверный язык вы будете использовать, разумно вообще НЕ использовать FileDescriptor , поскольку это ограничит вашу свободу изменять внутренний язык в будущем.

Вместо этого используйте ParcelFileDescriptor , который можно автоматически закрыть.

Переменные единицы

Убедитесь, что переменные единицы включены в название, чтобы их единицы были четко определены и понятны без необходимости обращаться к документации.

Примеры

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

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

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

Временные метки должны указывать на их ссылку.

Временные метки (фактически, все единицы!) должны четко обозначать свои единицы измерения и точки отсчета.

Примеры

/**
 * 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;