В Android 10 добавлена поддержка стабильного языка описания интерфейсов Android (AIDL) – нового способа отслеживать интерфейс прикладного программирования (API) и двоичный интерфейс приложения (ABI), предоставляемые интерфейсами AIDL. Stable AIDL работает так же, как AIDL, но система сборки отслеживает совместимость интерфейсов, и есть ограничения на то, что можно делать:
- Интерфейсы определяются в системе сборки с помощью
aidl_interfaces. - Интерфейсы могут содержать только структурированные данные. Объекты Parcelable, представляющие предпочитаемые типы, создаются автоматически на основе их определения AIDL, а также автоматически маршалируются и демаршалируются.
- Интерфейсы можно объявлять стабильными (обратно совместимыми). В таких случаях API отслеживается и его версии сохраняются в файле рядом с интерфейсом AIDL.
Структурированный и стабильный AIDL
Структурированный AIDL относится к типам, определенным исключительно в AIDL. Например, объявление parcelable (пользовательский parcelable) не является структурированным AIDL. Объекты Parcelable, поля которых определены в AIDL, называются структурированными объектами Parcelable.
Стабильный AIDL требует структурированного AIDL, чтобы система сборки и компилятор могли определить, обратно совместимы ли изменения, внесенные в объекты Parcelable.
Однако не все структурированные интерфейсы стабильны. Чтобы интерфейс был стабильным, он должен использовать только структурированные типы и следующие функции управления версиями: И наоборот, интерфейс считается нестабильным, если для его создания используется основная система сборки или задано значение unstable:true.
Как определить интерфейс 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, из которых состоит интерфейс. Путь к типу AIDLFoo, определенному в пакете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. Если замороженных версий интерфейса нет, указывать это поле не нужно, и проверки совместимости выполняться не будут. В Android 13 и более поздних версиях это поле заменено наversions_with_info.versions_with_info– список кортежей, каждый из которых содержит название замороженной версии и список импортов версий других модулей aidl_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". Если значениеstabilityне задано, система сборки проверяет, обратно совместим ли интерфейс, если не указано значениеunstable. Если значение не задано, это соответствует интерфейсу со стабильностью в контексте компиляции (то есть либо всем системным элементам, например элементам вsystem.imgи связанных разделах, либо всем элементам поставщика, например элементам вvendor.imgи связанных разделах). Если для параметраstabilityзадано значение"vintf", это означает, что интерфейс должен оставаться стабильным, пока он используется.gen_trace– необязательный флаг, позволяющий включить или отключить трассировку. В Android 14 по умолчанию используется флагtrueдля серверных частейcppиjava.host_supported– необязательный флаг, который при значенииtrueделает сгенерированные библиотеки доступными для хост-среды.unstable– необязательный флаг, который используется, чтобы пометить, что этот интерфейс не должен быть стабильным. Если задано значениеtrue, система сборки не создает дамп API для интерфейса и не требует его обновления.frozen– необязательный флаг. Если задано значениеtrue, это означает, что с момента выхода предыдущей версии интерфейса в нем не было изменений. Это позволяет выполнять больше проверок во время сборки. Если задано значениеfalse, это означает, что интерфейс находится в разработке и в него внесены новые изменения. Поэтому при запускеfoo-freeze-apiсоздается новая версия и значение автоматически меняется наtrue. Представлено в Android 14.backend.<type>.enabled– флаги, которые включают или отключают каждый из бэкендов, для которых компилятор AIDL генерирует код. Поддерживаются четыре бэкенда: Java, C++, NDK и Rust. По умолчанию включены серверные части Java, C++ и NDK. Если какой-либо из этих трех бэкендов не нужен, его необходимо отключить. До Android 15 Rust отключен по умолчанию.backend.<type>.apex_available: список названий APEX, для которых доступна сгенерированная библиотека заглушек.backend.[cpp|java].gen_log– необязательный флаг, который определяет, нужно ли генерировать дополнительный код для сбора информации о транзакции.backend.[cpp|java].vndk.enabled– необязательный флаг, позволяющий сделать этот интерфейс частью VNDK. Значение по умолчанию –false.backend.[cpp|ndk].additional_shared_libraries. Этот флаг, добавленный в Android 14, позволяет добавлять зависимости в нативные библиотеки. Этот флаг полезен при использованииndk_headerиcpp_header.backend.java.sdk_version– необязательный флаг, указывающий версию SDK, на основе которой создана библиотека-заглушка Java. Значение по умолчанию –"system_current". Не следует задавать это значение, еслиbackend.java.platform_apisимеет значениеtrue.backend.java.platform_apis– необязательный флаг, который следует установить в значениеtrue, если сгенерированные библиотеки должны быть созданы на основе API платформы, а не SDK.
Для каждой комбинации версий и включенных бэкендов создается библиотека-заглушка. О том, как ссылаться на определенную версию библиотеки заглушек для определенного бэкенда, рассказывается в статье Правила именования модулей.
Как писать файлы AIDL
Интерфейсы в стабильном AIDL похожи на традиционные, за исключением того, что в них нельзя использовать неструктурированные parcelable-объекты (поскольку они нестабильны; подробнее о структурированном и стабильном AIDL). Основное отличие стабильного AIDL заключается в том, как определяются объекты Parcelable. Ранее объекты Parcelable объявлялись заранее. В стабильном (и, следовательно, структурированном) AIDL поля и переменные Parcelable определяются явным образом.
// 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 также поддерживаются значения по умолчанию для пользовательских перечислений. Если значение по умолчанию не указано, используется значение, аналогичное нулю, или пустое значение.
Перечисления без значения по умолчанию инициализируются нулем, даже если в перечислении нет нулевого значения.
Как использовать заглушки библиотек
После того как вы добавите заглушки библиотек в качестве зависимости в модуль, вы сможете включать их в свои файлы. Ниже приведены примеры заглушек библиотек в системе сборки (Android.mk также можно использовать для устаревших определений модулей).
Обратите внимание, что в этих примерах версия не указана, поэтому используется нестабильный интерфейс. В названиях интерфейсов с версиями есть дополнительная информация. Подробнее об управлении версиями интерфейсов…
cc_... {
name: ...,
// use `shared_libs:` to load your library and its transitive dependencies
// dynamically
shared_libs: ["my-module-name-cpp"],
// use `static_libs:` to include the library in this binary and drop
// transitive dependencies
static_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// use `static_libs:` to add all jars and classes to this jar
static_libs: ["my-module-name-java"],
// use `libs:` to make these classes available during build time, but
// not add them to the jar, in case the classes are already present on the
// boot classpath (such as if it's in framework.jar) or another jar.
libs: ["my-module-name-java"],
// use `srcs:` with `-java-sources` if you want to add classes in this
// library jar directly, but you get transitive dependencies from
// somewhere else, such as the boot classpath or another jar.
srcs: ["my-module-name-java-source", ...],
...
}
# or
rust_... {
name: ...,
rustlibs: ["my-module-name-rust"],
...
}
Пример на 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
Пример на языке Rust:
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.
Чтобы поддерживать стабильность интерфейса, владельцы могут добавлять:
- Методы в конце интерфейса (или методы с явно заданными новыми серийными номерами).
- Элементы в конец объекта Parcelable (для каждого элемента необходимо добавить значение по умолчанию).
- Постоянные значения
- В Android 11 перечислители
- В Android 12 поля добавляются в конец объединения.
Другие действия запрещены, и никто, кроме владельца, не может изменять интерфейс, чтобы избежать конфликтов.
Чтобы проверить, что все интерфейсы заморожены для выпуска, можно выполнить сборку со следующими переменными среды:
AIDL_FROZEN_REL=true m ...– для сборки требуется заморозить все стабильные интерфейсы AIDL, для которых не указано полеowner:.AIDL_FROZEN_OWNERS="aosp test"– для сборки требуется, чтобы все стабильные интерфейсы AIDL были заморожены, а в полеowner:было указано значение aosp или test.
Стабильность импорта
Обновление версий импортируемых объектов для замороженных версий интерфейса обратно совместимо на уровне Stable AIDL. Однако для этого потребуется обновить все серверы и клиенты, использующие предыдущую версию интерфейса, а некоторые приложения могут неправильно работать при использовании разных версий типов. Как правило, для пакетов, содержащих только типы, или общих пакетов это безопасно, поскольку код уже должен быть написан для обработки неизвестных типов из транзакций IPC.
В коде платформы Android android.hardware.graphics.common – самый крупный пример такого обновления.
Как использовать интерфейсы с указанием версии
Методы интерфейса
Во время выполнения при попытке вызвать новые методы на старом сервере новые клиенты получают ошибку или исключение в зависимости от серверной части.
cppполучает серверная часть::android::UNKNOWN_TRANSACTION.ndkполучает серверная частьSTATUS_UNKNOWN_TRANSACTION.- Внутренний сервис
javaполучаетandroid.os.RemoteExceptionс сообщением о том, что API не реализован.
Подробнее о том, как это сделать, читайте в разделах Запрос версий и Использование значений по умолчанию.
Parcelables
Когда в parcelable-объекты добавляются новые поля, старые клиенты и серверы их игнорируют. Когда новые клиенты и серверы получают старые сериализуемые объекты, значения по умолчанию для новых полей заполняются автоматически. Это означает, что для всех новых полей в объекте Parcelable необходимо задать значения по умолчанию.
Клиенты не должны ожидать, что серверы будут использовать новые поля, если не знают, что на сервере реализована версия, в которой определено это поле (см. раздел Как узнать версию).
Перечисления и константы
Аналогичным образом клиенты и серверы должны отклонять или игнорировать неизвестные постоянные значения и перечислители, поскольку в будущем их может стать больше. Например, сервер не должен прерывать работу, когда получает перечислитель, о котором ему ничего не известно. Сервер должен либо игнорировать перечислитель, либо возвращать что-то, чтобы клиент знал, что он не поддерживается в этой реализации.
Профсоюзы
При попытке отправить объединение с новым полем возникает ошибка, если получатель использует старую версию и не знает об этом поле. Реализация никогда не увидит объединение с новым полем. Если транзакция односторонняя, сбой игнорируется. В противном случае возвращается ошибка BAD_VALUE(для C++ или NDK) или IllegalArgumentException(для Java). Ошибка возникает, если клиент отправляет объединение в новое поле на старый сервер или если старый клиент получает объединение от нового сервера.
Заголовок версии на уровне модуля (C++ / NDK)
Для стабильных модулей AIDL с версиями, использующих серверные части cpp или ndk, Soong автоматически создает синтетический заголовок на уровне модуля:
- Бэкенд C++:
#include "<module-name>.h" - Сервер NDK:
#include "aidl/<module-name>.h"
Этот заголовок определяет макрос AIDL_VERSION_<MODULE_NAME> с точками . и дефисами - в названии модуля, преобразованными в подчеркивания в верхнем регистре _. Макросу присваивается номер версии модуля интерфейса на основе версии используемой библиотеки.
Например, вы можете использовать макрос из android.hardware.foo-V2-ndk:
#include <aidl/android.hardware.foo.h>
#if AIDL_VERSION_ANDROID_HARDWARE_FOO >= 2
// Code for version 2 or higher
#endif
Как управлять несколькими версиями
В пространстве имен связывания в Android может быть только одна версия определенного интерфейса aidl, чтобы избежать ситуаций, когда у сгенерированных типов aidl несколько определений. В C++ есть правило одного определения, которое требует только одного определения каждого символа.
При сборке Android возникает ошибка, если модуль зависит от разных версий одной и той же библиотеки aidl_interface. Модуль может зависеть от этих библиотек напрямую или косвенно через зависимости своих зависимостей. Эти ошибки показывают граф зависимостей от неработающего модуля до конфликтующих версий библиотеки aidl_interface. Все зависимости должны быть обновлены, чтобы включать одну и ту же (обычно последнюю) версию этих библиотек.
Если библиотеку интерфейса используют разные модули, может быть полезно создать cc_defaults, java_defaults и rust_defaults для любой группы библиотек и процессов, которым нужна одна и та же версия. При внедрении новой версии интерфейса эти значения по умолчанию можно обновить, и все модули, использующие их, будут обновлены вместе, что гарантирует, что они не используют разные версии интерфейса.
cc_defaults {
name: "my.aidl.my-process-group-ndk-shared",
shared_libs: ["my.aidl-V3-ndk"],
...
}
cc_library {
name: "foo",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
cc_binary {
name: "bar",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
Когда модули aidl_interface импортируют другие модули aidl_interface, создаются дополнительные зависимости, требующие использования определенных версий. Такая ситуация может стать трудноуправляемой, если есть общие модули aidl_interface, которые импортируются в несколько модулей aidl_interface, используемых вместе в одних и тех же процессах.
aidl_interfaces_defaults можно использовать, чтобы хранить одно определение последних версий зависимостей для aidl_interface, которое можно обновлять в одном месте и использовать во всех модулях aidl_interface, которые импортируют этот общий интерфейс.
aidl_interface_defaults {
name: "android.popular.common-latest-defaults",
imports: ["android.popular.common-V3"],
...
}
aidl_interface {
name: "android.foo",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
aidl_interface {
name: "android.bar",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
Разработка на основе флагов
Интерфейсы, находящиеся в разработке (незамороженные), нельзя использовать на устройствах выпуска, поскольку не гарантируется их обратная совместимость.
В AIDL поддерживается резервное копирование во время выполнения для этих размороженных библиотек интерфейсов, чтобы код, написанный для последней размороженной версии, можно было использовать на выпущенных устройствах. Обратная совместимость клиентов аналогична существующему поведению, и при использовании резервного варианта реализации также должны следовать этому поведению. Подробнее о том, как использовать интерфейсы с указанием версии…
Флаг сборки AIDL
Параметр, который управляет этим поведением, – RELEASE_AIDL_USE_UNFROZEN, определенный в build/release/build_flags.bzl. true означает, что во время выполнения используется незамороженная версия интерфейса, а false – что библиотеки незамороженных версий ведут себя как их последняя замороженная версия.
Вы можете переопределить флаг на true для локальной разработки, но перед выпуском необходимо вернуть его значение false. Как правило, разработка ведется с конфигурацией, в которой флаг имеет значение true.
Матрица совместимости и манифесты
Объекты интерфейса поставщика (VINTF) определяют, какие версии ожидаются и какие версии предоставляются с обеих сторон интерфейса поставщика.
Большинство устройств, не относящихся к Cuttlefish, поддерживают последнюю матрицу совместимости только после заморозки интерфейсов, поэтому нет различий в библиотеках AIDL на основе RELEASE_AIDL_USE_UNFROZEN.
Матрицы
Интерфейсы, принадлежащие партнеру, добавляются в матрицы совместимости, предназначенные для определенных устройств или продуктов, на которые ориентируется устройство во время разработки. Поэтому, когда в матрицу совместимости добавляется новая, незамороженная версия интерфейса, предыдущие замороженные версии должны оставаться в ней в течение RELEASE_AIDL_USE_UNFROZEN=false. Вы можете использовать разные файлы матрицы совместимости для разных конфигураций RELEASE_AIDL_USE_UNFROZEN или разрешить обе версии в одном файле матрицы совместимости, который используется во всех конфигурациях.
Например, чтобы добавить размороженную версию 4, используйте <version>3-4</version>.
Если версия 4 заморожена, вы можете удалить версию 3 из матрицы совместимости, поскольку замороженная версия 4 используется, когда RELEASE_AIDL_USE_UNFROZEN имеет значение false.
Манифесты
В Android 15 в libvintf внесены изменения, позволяющие модифицировать файлы манифеста во время сборки на основе значения RELEASE_AIDL_USE_UNFROZEN.
Манифесты и их фрагменты объявляют, какую версию интерфейса реализует сервис. Если вы используете последнюю размороженную версию интерфейса, манифест необходимо обновить, чтобы отразить эту новую версию. Когда
RELEASE_AIDL_USE_UNFROZEN=false записи манифеста корректируются
libvintf, чтобы отразить изменения в сгенерированной библиотеке AIDL. Версия изменена с незамороженной версии N на последнюю замороженную версию N - 1. Поэтому пользователям не нужно управлять несколькими манифестами или фрагментами манифестов для каждого из своих сервисов.
Изменения в клиенте HAL
Код клиента HAL должен быть обратно совместим с каждой предыдущей поддерживаемой замороженной версией. Если RELEASE_AIDL_USE_UNFROZEN имеет значение false, сервисы всегда выглядят как последняя замороженная версия или более ранняя (например, при вызове новых незамороженных методов возвращается значение UNKNOWN_TRANSACTION, а новые поля parcelable имеют значения по умолчанию). Клиенты фреймворка Android должны быть обратно совместимы с дополнительными предыдущими версиями, но это новая деталь для клиентов поставщиков и клиентов интерфейсов, принадлежащих партнерам.
Изменения в реализации HAL
Самое большое отличие разработки HAL с использованием флагов от разработки без них заключается в том, что реализации HAL должны быть обратно совместимы с последней замороженной версией, чтобы работать, когда RELEASE_AIDL_USE_UNFROZEN имеет значение false.
При реализации и написании кода для устройств необходимо учитывать обратную совместимость. Подробнее о том, как использовать интерфейсы с указанием версии…
Принцип обратной совместимости в целом одинаков для клиентов и серверов, а также для кода фреймворка и кода поставщика, но есть нюансы, о которых вам нужно знать, поскольку вы фактически реализуете две версии, использующие один и тот же исходный код (текущую, незамороженную версию).
Пример. У интерфейса есть три замороженные версии. Интерфейс обновляется с помощью нового метода. Клиент и сервис обновлены для использования новой версии библиотеки 4. Поскольку библиотека V4 основана на незамороженной версии интерфейса, она ведет себя как последняя замороженная версия (версия 3), когда RELEASE_AIDL_USE_UNFROZEN имеет значение false, и не позволяет использовать новый метод.
Когда интерфейс заморожен, все значения RELEASE_AIDL_USE_UNFROZEN используют эту замороженную версию, и код, обеспечивающий обратную совместимость, можно удалить.
При вызове методов в обратных вызовах необходимо корректно обрабатывать случаи, когда возвращается значение UNKNOWN_TRANSACTION. Клиенты могут использовать две разные версии обратного вызова в зависимости от конфигурации выпуска, поэтому нельзя предполагать, что клиент отправляет последнюю версию, и новые методы могут возвращать это значение. Это похоже на то, как стабильные клиенты AIDL поддерживают обратную совместимость с серверами, описанными в разделе Использование версионных интерфейсов.
// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
mMyCallback = cb;
// Get the version of the callback for later when we call methods on it
auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
return status;
}
// Example of using the callback later
void NotifyCallbackLater() {
// From the latest frozen version (V2)
mMyCallback->foo();
// Call this method from the unfrozen V3 only if the callback is at least V3
if (mMyCallbackVersion >= 3) {
mMyCallback->bar();
}
}
Новые поля в существующих типах (parcelable, enum, union) могут не существовать или содержать значения по умолчанию, если RELEASE_AIDL_USE_UNFROZEN – false, а значения новых полей, которые сервис пытается отправить, удаляются в процессе.
Новые типы, добавленные в этой размороженной версии, нельзя отправлять или получать через интерфейс.
Реализация никогда не получает вызов для новых методов от клиентов, когда RELEASE_AIDL_USE_UNFROZEN имеет значение false.
Будьте внимательны: новые перечислители можно использовать только в той версии, в которой они появились, но не в предыдущей.
Обычно для того, чтобы узнать, какую версию использует удаленный интерфейс, применяется foo->getInterfaceVersion(). Однако при поддержке управления версиями на основе флагов вы реализуете две разные версии, поэтому вам может понадобиться получить версию текущего интерфейса. Для этого можно получить версию интерфейса текущего объекта, например this->getInterfaceVersion(), или использовать другие методы для my_ver. Подробнее о том, как запросить версию интерфейса удаленного объекта…
Новые стабильные интерфейсы VINTF
При добавлении нового пакета интерфейса AIDL последняя замороженная версия отсутствует, поэтому при значении false для RELEASE_AIDL_USE_UNFROZEN резервное поведение не предусмотрено. Не используйте эти интерфейсы. Если RELEASE_AIDL_USE_UNFROZEN – false, Service Manager не позволит сервису зарегистрировать интерфейс и клиенты не смогут его найти.
Вы можете добавлять сервисы условно, в зависимости от значения флага RELEASE_AIDL_USE_UNFROZEN в файле makefile устройства:
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
Если сервис является частью более крупного процесса и вы не можете добавить его на устройство условно, проверьте, объявлен ли сервис с помощью IServiceManager::isDeclared(). Если он объявлен, но не зарегистрирован, прервите процесс. Если она не объявлена, регистрация не будет выполнена.
Новые стабильные интерфейсы расширений VINTF
У новых интерфейсов расширений нет предыдущей версии, к которой можно было бы вернуться. Поскольку они не зарегистрированы в ServiceManager и не объявлены в манифестах VINTF, IServiceManager::isDeclared() нельзя использовать, чтобы определить, когда нужно подключить интерфейс расширения к другому интерфейсу.
Переменная RELEASE_AIDL_USE_UNFROZEN позволяет определить, нужно ли прикреплять новый размороженный интерфейс расширения к существующему, чтобы избежать его использования на выпущенных устройствах. Чтобы использовать интерфейс на выпущенных устройствах, его нужно заморозить.
Тесты vts_treble_vintf_vendor_test и vts_treble_vintf_framework_test VTS обнаруживают, когда в выпущенном устройстве используется незамороженный интерфейс расширения, и выдают ошибку.
Если интерфейс расширения не новый и у него есть ранее замороженная версия, то он возвращается к этой версии, и никаких дополнительных действий не требуется.
Cuttlefish как инструмент разработки
Каждый год после заморозки VINTF мы корректируем матрицу совместимости фреймворка (FCM) target-level и PRODUCT_SHIPPING_API_LEVEL Cuttlefish, чтобы они отражали устройства, запускаемые с выпуском следующего года. Мы корректируем target-level и PRODUCT_SHIPPING_API_LEVEL, чтобы убедиться, что есть устройство, которое протестировано и соответствует новым требованиям для выпуска в следующем году.
Если для RELEASE_AIDL_USE_UNFROZEN задано значение true, для разработки будущих версий Android используется Cuttlefish. Оно нацелено на уровень FCM версии Android, которая выйдет в следующем году, и PRODUCT_SHIPPING_API_LEVEL, поэтому должно соответствовать требованиям к ПО поставщика (VSR) для следующего выпуска.
Если для RELEASE_AIDL_USE_UNFROZEN задано значение false, Cuttlefish использует предыдущие значения target-level и PRODUCT_SHIPPING_API_LEVEL, чтобы отразить устройство выпуска.
В Android 14 и более ранних версиях это различие достигалось за счет разных ветвей Git, которые не принимали изменения в FCM target-level, уровне API доставки или любом другом коде, предназначенном для следующего выпуска.
Правила присвоения названий модулям
В Android 11 для каждой комбинации версий и включенных серверных частей автоматически создается модуль библиотеки-заглушки. Чтобы указать, какой модуль библиотеки-заглушки нужно использовать для связывания, используйте не название модуля aidl_interface, а название модуля библиотеки-заглушки, которое имеет вид ifacename-version-backend, где:
ifacename– название модуляaidl_interface.version– одно из следующих значений:Vversion-numberдля замороженных версийVlatest-frozen-version-number + 1для версии, которая ещё не заморожена
backend– одно из следующих значений:javaдля бэкенда Java;cppдля серверной части на C++;ndkилиndk_platformдля серверной части NDK. Первый предназначен для приложений, а второй – для платформы (до Android 13). В Android 13 и более поздних версий используйте только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();
Пример с бэкендом ndk (и ndk_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, а сервер использует старую версию. В таких случаях вызов метода в старом интерфейсе возвращает значение 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.
Определите все зависимости интерфейса. Для каждого пакета, от которого зависит интерфейс, определите, задан ли пакет в стабильном AIDL. Если он не задан, пакет необходимо преобразовать.
Преобразуйте все объекты Parcelable в интерфейсе в стабильные (сами файлы интерфейса можно оставить без изменений). Для этого нужно описать их структуру непосредственно в файлах AIDL. Классы управления необходимо переписать, чтобы использовать новые типы. Это можно сделать до создания пакета
aidl_interface(см. ниже).Создайте пакет
aidl_interface(как описано выше), в котором будет указано название модуля, его зависимости и другая необходимая информация. Чтобы сделать его стабильным (а не просто структурированным), его также необходимо версионировать. Подробнее о версиях интерфейсов…