Стабильный 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_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            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 . Если нет замороженных версий интерфейса, это не нужно указывать, и проверки совместимости не будет. Это поле заменено на versions_with_info для версии 13 и выше.
  • versions_with_info : список кортежей, каждый из которых содержит имя замороженной версии и список с импортом версий других модулей helpl_interface, импортированных этой версией aidl_interface. Определение версии V интерфейса AIDL IFACE находится в aidl_api/ IFACE / V . Это поле было введено в Android 13, и его нельзя изменять напрямую в Android.bp. Поле добавляется или обновляется вызовом *-update-api или *-freeze-api . Кроме того, поля versions автоматически переносятся в versions_with_info , когда пользователь вызывает *-update-api или *-freeze-api .
  • stability : необязательный флаг обещания стабильности этого интерфейса. В настоящее время поддерживает только "vintf" . Если это не установлено, это соответствует интерфейсу со стабильностью в этом контексте компиляции (поэтому интерфейс, загруженный здесь, может использоваться только с вещами, скомпилированными вместе, например, в system.img). Если установлено значение "vintf" , это соответствует обещанию стабильности: интерфейс должен оставаться стабильным, пока он используется.
  • gen_trace : необязательный флаг для включения или выключения трассировки. Значение по умолчанию — false .
  • host_supported : необязательный флаг, который, если установлено значение true , делает сгенерированные библиотеки доступными для хост-среды.
  • unstable : необязательный флаг, используемый для обозначения того, что этот интерфейс не должен быть стабильным. Если установлено значение true , система сборки не создает дамп API для интерфейса и не требует его обновления.
  • frozen : необязательный флаг, который, если установлено значение true , означает, что интерфейс не претерпел никаких изменений по сравнению с предыдущей версией интерфейса. Это позволяет проводить больше проверок во время сборки. Если установлено значение false это означает, что интерфейс находится в разработке и имеет новые изменения, поэтому запуск foo-freeze-api создаст новую версию и автоматически изменит значение на true . Представлено в Android 14 (экспериментальная версия AOSP).
  • backend.<type>.enabled : Эти флаги переключают каждый из бэкендов, для которых компилятор AIDL генерирует код. В настоящее время поддерживаются четыре бэкенда: Java, C++, NDK и Rust. Серверные части Java, C++ и NDK включены по умолчанию. Если какой-либо из этих трех бэкендов не нужен, его необходимо явно отключить. По умолчанию ржавчина отключена.
  • backend.<type>.apex_available : список имен APEX, для которых доступна сгенерированная библиотека-заглушка.
  • backend.[cpp|java].gen_log : необязательный флаг, который определяет, следует ли генерировать дополнительный код для сбора информации о транзакции.
  • backend.[cpp|java].vndk.enabled : Необязательный флаг, чтобы сделать этот интерфейс частью VNDK. Значение по умолчанию — false .
  • backend.java.sdk_version : необязательный флаг для указания версии SDK, для которой создана библиотека-заглушка Java. По умолчанию используется "system_current" . Это не следует устанавливать, если backend.java.platform_apis установлено значение true.
  • backend.java.platform_apis : необязательный флаг, для которого должно быть установлено значение true , если сгенерированные библиотеки необходимо создавать с использованием API платформы, а не SDK.

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

Запись файлов 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"],
    ...
}
# or
rust_... {
    name: ...,
    rust_libs: ["my-module-name-rust"],
    ...
}

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

#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

Пример на Русте:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

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

Объявление модуля с именем foo также создает цель в системе сборки, которую можно использовать для управления API модуля. При сборке foo-freeze-api добавляет новое определение API в api_dir или aidl_api/ name , в зависимости от версии Android, и добавляет файл .hash , представляющий недавно замороженную версию интерфейса. foo-freeze-api также обновляет свойство versions_with_info , чтобы отразить дополнительную версию и imports для этой версии. По сути, imports в versions_with_info копируется из поля imports . Но последняя стабильная версия указывается в imports в versions_with_info для импорта, который не имеет явной версии. После указания свойства versions_with_info система сборки запускает проверки совместимости между замороженными версиями, а также между Top of Tree (ToT) и последней замороженной версией.

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

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

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

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

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

  • AIDL_FROZEN_REL=true m ... - сборка требует, чтобы все стабильные интерфейсы AIDL были заморожены, у которых не указано поле owner:
  • AIDL_FROZEN_OWNERS="aosp test" - сборка требует, чтобы все стабильные интерфейсы AIDL были заморожены с полем owner: "aosp" или "test".

Стабильность импорта

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

В коде платформы Android android.hardware.graphics.common является самым большим примером такого типа обновления версии.

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

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

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

  • Бэкэнд 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. Первый предназначен для приложений, а второй — для использования платформы.
    • rust для бэкенда Rust.

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

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

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

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

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

  • Основано на версии 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Основано на версии 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • На основе версии ToT: foo-V3-(cpp|ndk|ndk_platform|rust).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() следующим образом ( super используется вместо IFoo , чтобы избежать ошибок копирования/вставки. Аннотация @SuppressWarnings("static") может понадобиться для отключения предупреждений, в зависимости от конфигурация javac ):

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

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

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

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

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

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

Пример на C++ в Android 13 и более поздних версиях:

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

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

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

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

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

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