Стабильный AIDL

В Android 10 добавлена ​​поддержка стабильного языка определения интерфейса Android (AIDL), нового способа отслеживания интерфейса прикладной программы (API) / двоичного интерфейса приложения (ABI), предоставляемого интерфейсами AIDL. Стабильный AIDL имеет следующие ключевые отличия от AIDL:

  • Интерфейсы определяются в системе сборки с aidl_interfaces .
  • Интерфейсы могут содержать только структурированные данные. Parcelables, представляющие желаемые типы, автоматически создаются на основе их определения AIDL и автоматически сортируются и немаршалируются.
  • Интерфейсы могут быть объявлены стабильными (обратно совместимыми). Когда это происходит, их API отслеживается и версируется в файле рядом с интерфейсом AIDL.

Определение интерфейса AIDL

Определение aidl_interface выглядит следующим образом :

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
    },

}
  • name : Имя модуля интерфейса AIDL , который уникально интерфейс лиц , по AIDL.
  • srcs : Список AIDL исходных файлов , которые составляют интерфейс. Путь для типа AIDL Foo , определенной в пакете com.acme должен быть <base_path>/com/acme/Foo.aidl , где <base_path> может быть любой каталог , связанный с директории , где Android.bp есть. В приведенном выше примере, <base_path> есть srcs/aidl .
  • local_include_dir : Путь от которой начинается имя пакета. Это соответствует <base_path> пояснено выше.
  • imports : список А aidl_interface модулей , что это использования. Если один из ваших интерфейсов AIDL использует интерфейс или parcelable из другого aidl_interface , ее имя здесь. Это может быть имя само по себе, чтобы обратиться к последней версии, или имя с версией суффиксом (например , как -V1 ) , чтобы обратиться к конкретной версии. Указание версии поддерживается с Android 12
  • versions : Предыдущие версии интерфейса, которые заморожены под api_dir , Начиная с Android 11, в versions заморожены под aidl_api/ name . Если нет замороженных версий интерфейса, это не следует указывать, и проверки совместимости производиться не будут.
  • stability : Дополнительный флаг для стабильности обещания этого интерфейса. В настоящее время поддерживает только "vintf" . Если он не установлен, это соответствует интерфейсу со стабильностью в этом контексте компиляции (поэтому загруженный здесь интерфейс может использоваться только с вещами, скомпилированными вместе, например, в system.img). Если установлено значение "vintf" , это соответствует обещание стабильности: интерфейс должен быть стабильным до тех пор , как она используется.
  • gen_trace : Необязательный флаг , чтобы включить трассировку или выключить. По умолчанию это false .
  • host_supported : Дополнительный флаг , который при установке на true производитель сгенерированных библиотек , доступные для принимающей среды.
  • unstable : Необязательный флаг , используемый , чтобы отметить , что этот интерфейс не должен быть стабильным. Когда установлено значение true , система сборки ни создает дамп API для интерфейса и не требует его обновления.
  • backend.<type>.enabled Эти флаги установите переключатель в каждой из движков , что компилятор AIDL будет генерировать код. В настоящее время три движков поддерживаются: java , cpp и ndk . Все серверные ВМ включены по умолчанию. Когда конкретный бэкэнд не нужен, его необходимо явно отключить.
  • backend.<type>.apex_available : Список имен APEX , что генерируемая библиотека заглушки доступна.
  • backend.[cpp|java].gen_log : Необязательный флаг , который определяет , будет ли генерировать дополнительный код для сбора информации о сделке.
  • backend.[cpp|java].vndk.enabled : Необязательный флаг , чтобы этот интерфейс стал частью VNDK. По умолчанию это false .
  • backend.java.platform_apis : Необязательный флаг , который определяет , будет ли библиотека Java - заглушек построена против частных API , с платформы. Это должно быть установлено значение "true" , когда stability устанавливается в "vintf" .
  • backend.java.sdk_version : Дополнительный флаг для указания версии SDK , что библиотека Java - заглушек построена против. По умолчанию "system_current" . Это не должно быть установлено , если backend.java.platform_apis верно.
  • backend.java.platform_apis : Необязательный флаг , который должен быть установлен true , когда сгенерированные библиотеки должны строить на платформе API , а не SDK.

Для каждой комбинации versions и включенных движков, библиотека заглушки создается. См Модуль правила именования для того, как обратиться к конкретной версии библиотеки заглушки для конкретного внутреннего интерфейса.

Запись файлов AIDL

Интерфейсы в стабильном AIDL похожи на традиционные интерфейсы, за исключением того, что им не разрешено использовать неструктурированные посылки (потому что они нестабильны!). Основное отличие стабильного AIDL заключается в том, как определены parcelables. Ранее parcelables были объявлены вперед; в стабильном AIDL поля и переменные parcelables определены явно.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

По умолчанию в настоящее время поддерживается (но не обязательно) для boolean , char , float , double , byte , int , long и String . В Android 12 также поддерживаются значения по умолчанию для определяемых пользователем перечислений. Если значение по умолчанию не указано, используется нулевое или пустое значение. Перечисления без значения по умолчанию инициализируются значением 0, даже если нет нулевого перечислителя.

Использование библиотек-заглушек

После добавления библиотек-заглушек в качестве зависимости к вашему модулю вы можете включить их в свои файлы. Вот примеры окурка библиотек в системе сборки ( Android.mk также может быть использован для определения устаревших модулей):

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"],
    ...
}

Пример на C ++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Пример на Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Интерфейсы управления версиями

Декларирование модуля с именем Foo также создает цель в системе сборки, которую можно использовать для управления API модуля. Когда построили, Foo-сублимационной апи добавляет новое определение API под api_dir или aidl_api/ name , в зависимости от версии Android, и добавляет .hash файл, как представляющий недавно замороженную версию интерфейса. Построение это также обновляет versions свойства , чтобы отразить дополнительную версию. После versions свойство задано, система сборки проходит проверку совместимости замороженных версий , а также между Top Дерева (TOT) и последней замерзшей версией.

Кроме того, вам необходимо управлять определением API версии ToT. Всякий раз , когда API обновляется, запустите Foo-обновление-API для обновления aidl_api/ name /current , который содержит определение API TOT версии.

Чтобы поддерживать стабильность интерфейса, владельцы могут добавлять новые:

  • Методы до конца интерфейса (или методы с явно определенными новыми серийными номерами)
  • Элементы до конца объекта (требуется добавить значение по умолчанию для каждого элемента)
  • Постоянные значения
  • В Android 11 счетчики
  • В Android 12 поля до конца объединения

Никакие другие действия не разрешены, и никто другой не может изменять интерфейс (в противном случае они рискуют столкнуться с изменениями, которые вносит владелец).

Использование версионных интерфейсов

Методы интерфейса

Во время выполнения, при попытке вызова новых методов на старом сервере, новые клиенты автоматически получают UNKNOWN_TRANSACTION . Для стратегии , чтобы справиться с этим см запрашивая версии и используя настройки по умолчанию .

Посылки

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

Клиенты не должны ожидать , сервера использовать новые поля , если они не знают , что сервер реализует версию , которая имеет поле , определенное (см запрашивая версию ).

Перечисления и константы

Точно так же клиенты и серверы должны либо отклонять, либо игнорировать нераспознанные значения констант и счетчики, если это необходимо, поскольку в будущем могут быть добавлены другие. Например, сервер не должен прерываться, когда он получает перечислитель, о котором он не знает. Он должен либо игнорировать его, либо возвращать что-то, чтобы клиент знал, что это не поддерживается в этой реализации.

Союзы

Попытка отправить объединение с новым полем не удалась, если получатель старый и не знает о поле. Реализация никогда не увидит объединения с новым полем. Сбой игнорируется, если это односторонняя транзакция; в противном случае ошибка BAD_VALUE (для C ++ или НДК бэкэнд) или IllegalArgumentException (для внутреннего интерфейса Java). Ошибка возникает, если клиент отправляет объединение, установленное для нового поля, на старый сервер или когда старый клиент получает объединение с нового сервера.

Правила именования модулей

В Android 11 для каждой комбинации версий и включенных серверных программ автоматически создается модуль библиотеки-заглушки. Для ссылки на конкретный модуль библиотеки заглушки для соединения, не используйте имя aidl_interface модуля, но имя модуля библиотеки заглушки, которая ifacename - version - backend , где

  • ifacename : название aidl_interface модуля
  • version либо из
    • V version-number для замороженных версий
    • V latest-frozen-version-number + 1 для версии кончика из-дерева (пока еще до замораживани)
  • backend либо из
    • java для внутреннего интерфейса Java,
    • cpp для C ++ бэкэндом,
    • ndk или ndk_platform для внутреннего интерфейса НДК. Первый предназначен для приложений, а второй - для использования на платформе.

Предположим , что имеется модуль с именем Foo и его последняя версия 2, и он поддерживает как NDK и C ++. В этом случае AIDL генерирует эти модули:

  • На основе версии 1
    • foo-V1-(java|cpp|ndk|ndk_platform)
  • На основе версии 2 (последняя стабильная версия)
    • foo-V2-(java|cpp|ndk|ndk_platform)
  • На основе версии ToT
    • foo-V3-(java|cpp|ndk|ndk_platform)

По сравнению с Android 11,

  • foo- backend , в котором говорится о последней стабильной версии становится foo- V2 - backend
  • foo-unstable- backend , в котором говорится о версии ToT становится foo- V3 - backend

Имена выходных файлов всегда совпадают с именами модулей.

  • На основе версии 1: foo-V1-(cpp|ndk|ndk_platform).so
  • На основе версии 2: foo-V2-(cpp|ndk|ndk_platform).so
  • На основе ToT версии: foo-V3-(cpp|ndk|ndk_platform).so

Обратите внимание , что компилятор AIDL не создает либо unstable модуль версии, или не-версионируются модуль для интерфейса стабильной AIDL. Начиная с Android 12, имя модуля, созданное из стабильного интерфейса AIDL, всегда включает его версию.

Новые методы мета-интерфейса

В Android 10 добавлено несколько методов метаинтерфейса для стабильного AIDL.

Запрос версии интерфейса удаленного объекта

Клиенты могут запрашивать версию и хэш интерфейса, который реализует удаленный объект, и сравнивать возвращаемые значения со значениями интерфейса, который использует клиент.

Пример с 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();

Пример с ndkndk_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);

Пример с 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();

Для Java языка, удаленная сторона должна осуществить getInterfaceVersion() и getInterfaceHash() следующим образом :

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return IFoo.VERSION; }

    @Override
    public final String getInterfaceHash() { return IFoo.HASH; }
}

Это происходит потому , генерируемые классы ( IFoo , IFoo.Stub и т.д.) распределяется между клиентом и сервером (например, классы могут быть в загрузочном пути к классам). Когда классы являются общими, сервер также связывается с новейшей версией классов, даже если он мог быть построен с более старой версией интерфейса. Если этот метаинтерфейс реализован в общем классе, он всегда возвращает самую новую версию. Однако при реализации способа , как указаны выше, номер версии интерфейса встроен в коде сервера (поскольку IFoo.VERSION является static final int , который встраиваемый , когда ссылка) и , таким образом , метод может возвращать точную версию сервер был построен с участием.

Работа со старыми интерфейсами

Возможно, что на клиенте установлена ​​более новая версия интерфейса AIDL, но сервер использует старый интерфейс AIDL. В таких случаях вызов метода старый возвращает интерфейс UNKNOWN_TRANSACTION .

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

Пример на C ++:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(std::unique_ptr<IFoo>(MyDefault));

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Пример на 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(...);

Вам не нужно предоставлять реализацию по умолчанию для всех методов в интерфейсе AIDL. Методы, которые гарантированно будут реализованы в удаленной стороне (потому что вы уверены в том , что пульт дистанционного управления построена , когда методы были в описании интерфейса AIDL) не должны быть переопределены в умолчанию impl класса.

Преобразование существующего AIDL в структурированный / стабильный AIDL

Если у вас есть существующий интерфейс AIDL и код, который его использует, выполните следующие действия, чтобы преобразовать интерфейс в стабильный интерфейс AIDL.

  1. Определите все зависимости вашего интерфейса. Для каждого пакета, от которого зависит интерфейс, определите, определен ли пакет в стабильном AIDL. Если не определено, пакет необходимо преобразовать.

  2. Преобразуйте все посылки в вашем интерфейсе в стабильные посылки (сами файлы интерфейса могут оставаться неизменными). Сделайте это, указав их структуру непосредственно в файлах AIDL. Чтобы использовать эти новые типы, необходимо переписать классы управления. Это может быть сделано , прежде чем создать aidl_interface пакет (ниже).

  3. Создание aidl_interface пакета (как описано выше) , который содержит имя модуля, его зависимости, и любую другую информацию , вам нужно. Чтобы сделать его стабилизированным (а не только структурированным), его также необходимо версионировать. Для получения дополнительной информации см интерфейсы управления версиями .