Стабильный 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 использует интерфейс или пакет из другого 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 установлено значение true.
  • backend.java.platform_apis : необязательный флаг, для которого должно быть установлено значение true , если сгенерированные библиотеки необходимо создавать с использованием API платформы, а не SDK.

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

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

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

// 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-подобное или пустое значение. Перечисления без значения по умолчанию инициализируются 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"],
    ...
}

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

#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-freeze-api добавляет новое определение API в api_dir или aidl_api/ name , в зависимости от версии Android, и добавляет файл .hash , представляющий недавно замороженную версию интерфейса. Сборка также обновляет свойство version, чтобы отразить дополнительную versions . После указания свойства version система сборки запускает проверки совместимости между замороженными versions , а также между Top of Tree (ToT) и последней замороженной версией.

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

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

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

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

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

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

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

  • Бэкэнд cpp получает ::android::UNKNOWN_TRANSACTION .
  • ndk получает STATUS_UNKNOWN_TRANSACTION .
  • java получает android.os.RemoteException с сообщением о том, что API не реализован.

Стратегии решения этой проблемы см. в разделе Запрос версий и использование значений по умолчанию .

Parcelables

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

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

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

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

Союзы

Попытка отправить объединение с новым полем не удалась, если получатель старый и не знает о поле. Реализация никогда не увидит объединение с новым полем. Сбой игнорируется, если это односторонняя транзакция; в противном случае возникает ошибка BAD_VALUE (для серверной части C++ или NDK) или 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 для серверной части NDK. Первый предназначен для приложений, а второй — для использования платформы.

Предположим, что есть модуль с именем 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++ в Android T (экспериментальный AOSP) и более поздних версиях:

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

Пример на 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. Методы, которые гарантированно будут реализованы на удаленной стороне (поскольку вы уверены, что удаленная часть построена, когда методы были в описании интерфейса impl ), не нужно переопределять в классе реализации по умолчанию.

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

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

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

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

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