Android 10 ajoute la prise en charge du langage de définition d'interface Android (AIDL) stable, une nouvelle façon de suivre l'interface de programmation d'application (API) et l'interface binaire d'application (ABI) fournies par les interfaces AIDL. Stable AIDL fonctionne exactement comme AIDL, mais le système de compilation suit la compatibilité des interfaces et il existe des restrictions sur ce que vous pouvez faire :
- Les interfaces sont définies dans le système de compilation avec
aidl_interfaces
. - Les interfaces ne peuvent contenir que des données structurées. Les Parcelables représentant les types préférés sont créés automatiquement en fonction de leur définition AIDL et sont automatiquement sérialisés et désérialisés.
- Les interfaces peuvent être déclarées comme stables (rétrocompatibles). Dans ce cas, leur API est suivie et versionnée dans un fichier à côté de l'interface AIDL.
AIDL structuré ou stable
AIDL structuré fait référence aux types définis uniquement dans AIDL. Par exemple, une déclaration Parcelable (un Parcelable personnalisé) n'est pas une AIDL structurée. Les Parcelables dont les champs sont définis dans AIDL sont appelés Parcelables structurés.
L'AIDL stable nécessite un AIDL structuré pour que le système de compilation et le compilateur puissent déterminer si les modifications apportées aux éléments Parcelable sont rétrocompatibles.
Cependant, toutes les interfaces structurées ne sont pas stables. Pour être stable, une interface ne doit utiliser que des types structurés et les fonctionnalités de gestion des versions suivantes. À l'inverse, une interface n'est pas stable si le système de compilation principal est utilisé pour la compiler ou si unstable:true
est défini.
Définir une interface AIDL
Voici à quoi ressemble une définition de 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
: nom du module d'interface AIDL qui identifie de manière unique une interface AIDL.srcs
: liste des fichiers sources AIDL qui composent l'interface. Le chemin d'accès d'un type AIDLFoo
défini dans un packagecom.acme
doit être<base_path>/com/acme/Foo.aidl
, où<base_path>
peut être n'importe quel répertoire lié au répertoire où se trouveAndroid.bp
. Dans l'exemple précédent,<base_path>
estsrcs/aidl
.local_include_dir
: chemin d'accès à partir duquel le nom du package commence. Elle correspond à<base_path>
expliqué ci-dessus.imports
: liste des modulesaidl_interface
utilisés. Si l'une de vos interfaces AIDL utilise une interface ou un élément Parcelable provenant d'un autreaidl_interface
, indiquez son nom ici. Il peut s'agir du nom seul, pour faire référence à la dernière version, ou du nom avec le suffixe de version (tel que-V1
) pour faire référence à une version spécifique. La spécification d'une version est prise en charge depuis Android 12.versions
: les versions précédentes de l'interface qui sont figées sousapi_dir
. À partir d'Android 11, lesversions
sont figées sousaidl_api/name
. S'il n'existe aucune version figée d'une interface, cela ne doit pas être spécifié et il n'y aura pas de vérification de la compatibilité. Ce champ a été remplacé parversions_with_info
pour Android 13 et versions ultérieures.versions_with_info
: liste de tuples, chacun contenant le nom d'une version figée et une liste avec les importations de version d'autres modules aidl_interface que cette version de l'aidl_interface a importés. La définition de la version V d'une interface AIDL IFACE se trouve à l'adresseaidl_api/IFACE/V
. Ce champ a été introduit dans Android 13 et n'est pas censé être modifié directement dansAndroid.bp
. Le champ est ajouté ou mis à jour en appelant*-update-api
ou*-freeze-api
. De plus, les champsversions
sont automatiquement migrés versversions_with_info
lorsqu'un utilisateur appelle*-update-api
ou*-freeze-api
.stability
: option facultative pour la promesse de stabilité de cette interface. Cette fonctionnalité n'est compatible qu'avec"vintf"
. Sistability
n'est pas défini, le système de compilation vérifie que l'interface est rétrocompatible, sauf siunstable
est spécifié. L'état "non défini" correspond à une interface dont la stabilité est assurée dans ce contexte de compilation (c'est-à-dire soit tous les éléments système, par exemple les éléments danssystem.img
et les partitions associées, soit tous les éléments du fournisseur, par exemple les éléments dansvendor.img
et les partitions associées). Sistability
est défini sur"vintf"
, cela correspond à une promesse de stabilité : l'interface doit rester stable tant qu'elle est utilisée.gen_trace
: option facultative permettant d'activer ou de désactiver le traçage. À partir d'Android 14, la valeur par défaut esttrue
pour les backendscpp
etjava
.host_supported
: indicateur facultatif qui, lorsqu'il est défini surtrue
, rend les bibliothèques générées disponibles pour l'environnement hôte.unstable
: indicateur facultatif utilisé pour indiquer que cette interface n'a pas besoin d'être stable. Lorsque cette valeur est définie surtrue
, le système de compilation ne crée pas le dump de l'API pour l'interface et ne nécessite pas sa mise à jour.frozen
: indicateur facultatif qui, lorsqu'il est défini surtrue
, signifie que l'interface n'a pas changé depuis la version précédente. Cela permet d'effectuer davantage de vérifications au moment de la compilation. Si la valeur est définie surfalse
, cela signifie que l'interface est en cours de développement et a subi de nouvelles modifications. L'exécution defoo-freeze-api
génère une nouvelle version et remplace automatiquement la valeur partrue
. Introduit dans Android 14.backend.<type>.enabled
: ces indicateurs activent ou désactivent chacun des backends pour lesquels le compilateur AIDL génère du code. Quatre backends sont compatibles : Java, C++, NDK et Rust. Les backends Java, C++ et NDK sont activés par défaut. Si l'un de ces trois backends n'est pas nécessaire, il doit être désactivé explicitement. Rust est désactivé par défaut jusqu'à Android 15.backend.<type>.apex_available
: liste des noms APEX pour lesquels la bibliothèque de stub générée est disponible.backend.[cpp|java].gen_log
: option facultative qui contrôle si du code supplémentaire doit être généré pour collecter des informations sur la transaction.backend.[cpp|java].vndk.enabled
: option facultative permettant d'intégrer cette interface au VNDK. La valeur par défaut estfalse
.backend.[cpp|ndk].additional_shared_libraries
: introduit dans Android 14, cet indicateur ajoute des dépendances aux bibliothèques natives. Cette option est utile avecndk_header
etcpp_header
.backend.java.sdk_version
: indicateur facultatif permettant de spécifier la version du SDK sur laquelle la bibliothèque de stub Java est basée. La valeur par défaut est"system_current"
. Ce paramètre ne doit pas être défini lorsquebackend.java.platform_apis
esttrue
.backend.java.platform_apis
: indicateur facultatif qui doit être défini surtrue
lorsque les bibliothèques générées doivent être compilées avec l'API de la plate-forme plutôt qu'avec le SDK.
Une bibliothèque de stub est créée pour chaque combinaison de versions et de backends activés. Pour savoir comment faire référence à la version spécifique de la bibliothèque de stubs pour un backend spécifique, consultez Règles de dénomination des modules.
Écrire des fichiers AIDL
Les interfaces dans AIDL stable sont semblables aux interfaces traditionnelles, à la différence qu'elles ne sont pas autorisées à utiliser des Parcelables non structurés (car ils ne sont pas stables ! Voir AIDL structuré et stable). La principale différence dans l'AIDL stable réside dans la façon dont les éléments Parcelable sont définis. Auparavant, les Parcelables étaient déclarés en avant. Dans l'AIDL stable (et donc structuré), les champs et variables Parcelable sont définis de manière explicite.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Une valeur par défaut est acceptée (mais pas obligatoire) pour boolean
, char
, float
, double
, byte
, int
, long
et String
. Dans Android 12, les valeurs par défaut des énumérations définies par l'utilisateur sont également prises en charge. Lorsqu'aucune valeur par défaut n'est spécifiée, une valeur vide ou équivalente à zéro est utilisée.
Les énumérations sans valeur par défaut sont initialisées à 0, même s'il n'y a pas d'énumérateur zéro.
Utiliser des bibliothèques de stub
Après avoir ajouté des bibliothèques de stub en tant que dépendance à votre module, vous pouvez les inclure dans vos fichiers. Voici des exemples de bibliothèques de stub dans le système de compilation (Android.mk
peut également être utilisé pour les anciennes définitions de module).
Notez que dans ces exemples, la version n'est pas présente. Il s'agit donc d'une interface instable. Toutefois, les noms des interfaces avec des versions incluent des informations supplémentaires. Pour en savoir plus, consultez Gestion des versions des interfaces.
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"],
...
}
Exemple en C++ :
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
Exemple en Java :
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
Exemple en Rust :
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
Interfaces de gestion des versions
Déclarer un module avec le nom foo crée également une cible dans le système de compilation que vous pouvez utiliser pour gérer l'API du module. Lors de la compilation, foo-freeze-api ajoute une définition d'API sous api_dir
ou aidl_api/name
, selon la version d'Android, et ajoute un fichier .hash
, qui représentent tous deux la nouvelle version figée de l'interface. foo-freeze-api met également à jour la propriété versions_with_info
pour refléter la version supplémentaire et imports
pour la version. En gros, imports
dans versions_with_info
est copié à partir du champ imports
. Toutefois, la dernière version stable est spécifiée dans imports
dans versions_with_info
pour l'importation, qui ne comporte pas de version explicite.
Une fois la propriété versions_with_info
spécifiée, le système de compilation exécute des vérifications de compatibilité entre les versions figées, ainsi qu'entre la version "Top of Tree" (ToT) et la dernière version figée.
De plus, vous devez gérer la définition de l'API de la version ToT. Chaque fois qu'une API est mise à jour, exécutez foo-update-api pour mettre à jour aidl_api/name/current
, qui contient la définition de l'API de la version ToT.
Pour préserver la stabilité d'une interface, les propriétaires peuvent ajouter les éléments suivants :
- Méthodes à la fin d'une interface (ou méthodes avec de nouveaux numéros de série définis de manière explicite)
- Éléments à la fin d'un élément Parcelable (nécessite l'ajout d'une valeur par défaut pour chaque élément)
- Valeurs constantes
- Dans Android 11, les énumérateurs
- Dans Android 12, champs à la fin d'une union
Aucune autre action n'est autorisée, et personne d'autre ne peut modifier une interface (sinon, il risque d'entrer en conflit avec les modifications apportées par un propriétaire).
Pour vérifier que toutes les interfaces sont figées pour la publication, vous pouvez effectuer une compilation avec les variables d'environnement suivantes définies :
AIDL_FROZEN_REL=true m ...
: la compilation nécessite que toutes les interfaces AIDL stables sans champowner:
spécifié soient figées.AIDL_FROZEN_OWNERS="aosp test"
: la compilation nécessite que toutes les interfaces AIDL stables soient figées avec le champowner:
spécifié comme "aosp" ou "test".
Stabilité des importations
La mise à jour des versions des importations pour les versions figées d'une interface est rétrocompatible au niveau de la couche AIDL stable. Toutefois, la mise à jour de ces éléments nécessite la mise à jour de tous les serveurs et clients qui utilisent une version antérieure de l'interface. De plus, certaines applications peuvent être déroutées lorsque différents types de versions sont mélangés. En général, pour les packages de types uniquement ou courants, cela ne pose pas de problème, car le code doit déjà être écrit pour gérer les types inconnus des transactions IPC.
Dans le code de la plate-forme Android, android.hardware.graphics.common
est le plus grand exemple de ce type de mise à niveau de version.
Utiliser des interfaces avec des versions
Méthodes d'interface
Lors de l'exécution, lorsque de nouveaux clients tentent d'appeler de nouvelles méthodes sur un ancien serveur, ils reçoivent une erreur ou une exception, selon le backend.
- Le backend
cpp
obtient::android::UNKNOWN_TRANSACTION
. - Le backend
ndk
obtientSTATUS_UNKNOWN_TRANSACTION
. - Le backend
java
reçoitandroid.os.RemoteException
avec un message indiquant que l'API n'est pas implémentée.
Pour connaître les stratégies permettant de gérer ce problème, consultez Interroger les versions et Utiliser les valeurs par défaut.
Objets Parcelable
Lorsque de nouveaux champs sont ajoutés aux éléments Parcelable, les anciens clients et serveurs les suppriment. Lorsque de nouveaux clients et serveurs reçoivent d'anciens objets Parcelable, les valeurs par défaut des nouveaux champs sont automatiquement renseignées. Cela signifie que des valeurs par défaut doivent être spécifiées pour tous les nouveaux champs d'un objet Parcelable.
Les clients ne doivent pas s'attendre à ce que les serveurs utilisent les nouveaux champs, sauf s'ils savent que le serveur implémente la version qui définit le champ (voir Interroger les versions).
Énumérations et constantes
De même, les clients et les serveurs doivent rejeter ou ignorer les valeurs constantes et les énumérateurs non reconnus, selon le cas, car d'autres peuvent être ajoutés à l'avenir. Par exemple, un serveur ne doit pas s'arrêter lorsqu'il reçoit un énumérateur qu'il ne connaît pas. Le serveur doit ignorer l'énumérateur ou renvoyer une réponse pour que le client sache qu'il n'est pas compatible avec cette implémentation.
Syndicats
L'envoi d'une union avec un nouveau champ échoue si le récepteur est ancien et ne connaît pas le champ. L'implémentation ne verra jamais l'union avec le nouveau champ. L'échec est ignoré s'il s'agit d'une transaction unidirectionnelle. Sinon, l'erreur est BAD_VALUE
(pour le backend C++ ou NDK) ou IllegalArgumentException
(pour le backend Java). L'erreur est reçue si le client envoie un ensemble d'union au nouveau champ à un ancien serveur, ou lorsqu'il s'agit d'un ancien client recevant l'union d'un nouveau serveur.
Gérer plusieurs versions
Dans Android, un espace de noms de l'éditeur de liens ne peut comporter qu'une seule version d'une interface aidl
spécifique pour éviter les situations où les types aidl
générés ont plusieurs définitions. Le langage C++ dispose de la règle de définition unique, qui n'autorise qu'une seule définition de chaque symbole.
La compilation Android génère une erreur lorsqu'un module dépend de différentes versions de la même bibliothèque aidl_interface
. Le module peut dépendre de ces bibliothèques directement ou indirectement par le biais des dépendances de ses dépendances. Ces erreurs affichent le graphique des dépendances du module en échec aux versions conflictuelles de la bibliothèque aidl_interface
. Toutes les dépendances doivent être mises à jour pour inclure la même version (généralement la dernière) de ces bibliothèques.
Si la bibliothèque d'interface est utilisée par de nombreux modules différents, il peut être utile de créer cc_defaults
, java_defaults
et rust_defaults
pour tout groupe de bibliothèques et de processus qui doivent utiliser la même version. Lorsqu'une nouvelle version de l'interface est introduite, ces valeurs par défaut peuvent être mises à jour et tous les modules qui les utilisent sont mis à jour ensemble, ce qui garantit qu'ils n'utilisent pas différentes versions de l'interface.
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"],
...
}
Lorsque des modules aidl_interface
importent d'autres modules aidl_interface
, cela crée des dépendances supplémentaires qui nécessitent l'utilisation de versions spécifiques ensemble. Cette situation peut devenir difficile à gérer lorsqu'il existe des modules aidl_interface
communs qui sont importés dans plusieurs modules aidl_interface
utilisés ensemble dans les mêmes processus.
aidl_interfaces_defaults
peut être utilisé pour conserver une définition des dernières versions des dépendances pour un aidl_interface
qui peut être mis à jour à un seul endroit et utilisé par tous les modules aidl_interface
qui souhaitent importer cette interface commune.
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"],
...
}
Développement basé sur des indicateurs
Les interfaces en cours de développement (non figées) ne peuvent pas être utilisées sur les appareils de version, car leur rétrocompatibilité n'est pas garantie.
AIDL est compatible avec le remplacement au moment de l'exécution pour ces bibliothèques d'interface non figées afin que le code puisse être écrit par rapport à la dernière version non figée et toujours être utilisé sur les appareils de version. Le comportement rétrocompatible des clients est semblable au comportement existant. Avec le comportement de secours, les implémentations doivent également suivre ces comportements. Consultez Utiliser des interfaces avec gestion des versions.
Indicateur de compilation AIDL
L'indicateur qui contrôle ce comportement est RELEASE_AIDL_USE_UNFROZEN
, défini dans build/release/build_flags.bzl
. true
signifie que la version non figée de l'interface est utilisée au moment de l'exécution, et false
signifie que les bibliothèques des versions non figées se comportent toutes comme leur dernière version figée.
Vous pouvez remplacer le flag par true
pour le développement local, mais vous devez le rétablir sur false
avant la publication. En règle générale, le développement est effectué avec une configuration dont l'indicateur est défini sur true
.
Matrice de compatibilité et fichiers manifestes
Les objets d'interface du fournisseur (objets VINTF) définissent les versions attendues et celles fournies de chaque côté de l'interface du fournisseur.
La plupart des appareils non-Cuttlefish ciblent la dernière matrice de compatibilité uniquement après le gel des interfaces. Il n'y a donc aucune différence dans les bibliothèques AIDL basées sur RELEASE_AIDL_USE_UNFROZEN
.
Matrices
Les interfaces appartenant aux partenaires sont ajoutées aux matrices de compatibilité spécifiques aux appareils ou aux produits que l'appareil cible pendant le développement. Ainsi, lorsqu'une nouvelle version non figée d'une interface est ajoutée à une matrice de compatibilité, les versions figées précédentes doivent rester pendant RELEASE_AIDL_USE_UNFROZEN=false
. Pour ce faire, vous pouvez utiliser différents fichiers de matrice de compatibilité pour différentes configurations RELEASE_AIDL_USE_UNFROZEN
ou autoriser les deux versions dans un seul fichier de matrice de compatibilité utilisé dans toutes les configurations.
Par exemple, lorsque vous ajoutez une version 4 non figée, utilisez <version>3-4</version>
.
Lorsque la version 4 est figée, vous pouvez supprimer la version 3 de la matrice de compatibilité, car la version 4 figée est utilisée lorsque RELEASE_AIDL_USE_UNFROZEN
est false
.
Fichiers manifestes
Dans Android 15, une modification de libvintf
est introduite pour modifier les fichiers manifestes au moment de la compilation en fonction de la valeur de RELEASE_AIDL_USE_UNFROZEN
.
Les fichiers manifestes et les fragments de fichier manifeste déclarent la version d'une interface qu'un service implémente. Lorsque vous utilisez la dernière version non figée d'une interface, le fichier manifeste doit être mis à jour pour refléter cette nouvelle version. Lorsque RELEASE_AIDL_USE_UNFROZEN=false
, les entrées du fichier manifeste sont ajustées par libvintf
pour refléter la modification apportée à la bibliothèque AIDL générée. La version est modifiée, passant de la version non figée N
à la dernière version figée N - 1
. Les utilisateurs n'ont donc pas besoin de gérer plusieurs fichiers manifestes ou fragments de fichier manifeste pour chacun de leurs services.
Modifications apportées au client HAL
Le code client HAL doit être rétrocompatible avec chaque version figée précédente compatible. Lorsque RELEASE_AIDL_USE_UNFROZEN
est défini sur false
, les services ressemblent toujours à la dernière version figée ou à une version antérieure (par exemple, l'appel de nouvelles méthodes non figées renvoie UNKNOWN_TRANSACTION
, ou les nouveaux champs parcelable
ont leurs valeurs par défaut). Les clients du framework Android doivent être rétrocompatibles avec d'autres versions antérieures, mais il s'agit d'une nouvelle information pour les clients des fournisseurs et les clients des interfaces appartenant à des partenaires.
Modifications apportées à l'implémentation de HAL
La principale différence entre le développement HAL et le développement basé sur des indicateurs est l'exigence que les implémentations HAL soient rétrocompatibles avec la dernière version figée pour fonctionner lorsque RELEASE_AIDL_USE_UNFROZEN
est false
.
La prise en compte de la rétrocompatibilité dans les implémentations et le code de l'appareil est un nouvel exercice. Consultez Utiliser des interfaces versionnées.
Les considérations de compatibilité ascendante sont généralement les mêmes pour les clients et les serveurs, ainsi que pour le code du framework et le code du fournisseur. Toutefois, il existe des différences subtiles dont vous devez être conscient, car vous implémentez désormais deux versions qui utilisent le même code source (la version actuelle, non figée).
Exemple : Une interface comporte trois versions figées. L'interface est mise à jour avec une nouvelle méthode. Le client et le service sont tous deux mis à jour pour utiliser la nouvelle bibliothèque version 4. Étant donné que la bibliothèque V4 est basée sur une version non figée de l'interface, elle se comporte comme la dernière version figée, la version 3, lorsque RELEASE_AIDL_USE_UNFROZEN
est false
, et empêche l'utilisation de la nouvelle méthode.
Lorsque l'interface est figée, toutes les valeurs de RELEASE_AIDL_USE_UNFROZEN
utilisent cette version figée, et le code gérant la rétrocompatibilité peut être supprimé.
Lorsque vous appelez des méthodes sur des rappels, vous devez gérer correctement le cas où UNKNOWN_TRANSACTION
est renvoyé. Les clients peuvent implémenter deux versions différentes d'un rappel en fonction de la configuration de la version. Vous ne pouvez donc pas supposer que le client envoie la version la plus récente, et de nouvelles méthodes peuvent renvoyer cette erreur. Cela ressemble à la façon dont les clients AIDL stables maintiennent la rétrocompatibilité avec les serveurs, comme décrit dans Utiliser des interfaces versionnées.
// 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();
}
}
Il est possible que les nouveaux champs des types existants (parcelable
, enum
, union
) n'existent pas ou contiennent leurs valeurs par défaut lorsque RELEASE_AIDL_USE_UNFROZEN
est défini sur false
. Dans ce cas, les valeurs des nouveaux champs qu'un service tente d'envoyer sont supprimées lors du processus.
Les nouveaux types ajoutés dans cette version non figée ne peuvent pas être envoyés ni reçus via l'interface.
L'implémentation ne reçoit jamais d'appel pour de nouvelles méthodes de la part des clients lorsque RELEASE_AIDL_USE_UNFROZEN
est false
.
Veillez à n'utiliser les nouveaux énumérateurs qu'avec la version dans laquelle ils sont introduits, et non avec la version précédente.
Normalement, vous utilisez foo->getInterfaceVersion()
pour voir quelle version l'interface à distance utilise. Toutefois, avec la prise en charge du contrôle des versions basé sur des indicateurs, vous implémentez deux versions différentes. Vous pouvez donc obtenir la version de l'interface actuelle. Pour ce faire, vous pouvez obtenir la version d'interface de l'objet actuel, par exemple this->getInterfaceVersion()
ou les autres méthodes pour my_ver
. Pour en savoir plus, consultez Interroger la version de l'interface de l'objet distant.
Nouvelles interfaces stables VINTF
Lorsqu'un nouveau package d'interface AIDL est ajouté, il n'y a pas de dernière version figée. Il n'y a donc pas de comportement auquel se rabattre lorsque RELEASE_AIDL_USE_UNFROZEN
est false
. N'utilisez pas ces interfaces. Lorsque RELEASE_AIDL_USE_UNFROZEN
est défini sur false
, le Service Manager n'autorise pas le service à enregistrer l'interface et les clients ne la trouveront pas.
Vous pouvez ajouter les services de manière conditionnelle en fonction de la valeur de l'indicateur RELEASE_AIDL_USE_UNFROZEN
dans le fichier makefile de l'appareil :
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
Si le service fait partie d'un processus plus vaste et que vous ne pouvez pas l'ajouter à l'appareil de manière conditionnelle, vous pouvez vérifier s'il est déclaré avec IServiceManager::isDeclared()
. S'il est déclaré et que l'enregistrement a échoué, abandonnez le processus. Si elle n'est pas déclarée, l'enregistrement devrait échouer.
Nouvelles interfaces d'extension stables VINTF
Les nouvelles interfaces d'extension n'ont pas de version précédente à laquelle se référer. De plus, comme elles ne sont pas enregistrées avec ServiceManager
ni déclarées dans les fichiers manifeste VINTF, IServiceManager::isDeclared()
ne peut pas être utilisé pour déterminer quand associer l'interface d'extension à une autre interface.
La variable RELEASE_AIDL_USE_UNFROZEN
peut être utilisée pour déterminer s'il faut associer la nouvelle interface d'extension dégelée à l'interface existante afin d'éviter de l'utiliser sur les appareils commercialisés. L'interface doit être figée pour être utilisée sur les appareils commercialisés.
Les tests VTS vts_treble_vintf_vendor_test
et vts_treble_vintf_framework_test
détectent l'utilisation d'une interface d'extension non figée dans un appareil commercialisé et génèrent une erreur.
Si l'interface de l'extension n'est pas nouvelle et qu'une version a déjà été figée, elle revient à cette version figée et aucune étape supplémentaire n'est requise.
Cuttlefish en tant qu'outil de développement
Chaque année, après le gel de VINTF, nous ajustons la matrice de compatibilité du framework (FCM) target-level
et le PRODUCT_SHIPPING_API_LEVEL
de Cuttlefish afin qu'ils reflètent les appareils lancés avec la version de l'année suivante. Nous ajustons target-level
et PRODUCT_SHIPPING_API_LEVEL
pour nous assurer qu'un appareil de lancement a été testé et répond aux nouvelles exigences de la version de l'année prochaine.
Lorsque RELEASE_AIDL_USE_UNFROZEN
est défini sur true
, Cuttlefish est utilisé pour le développement des futures versions d'Android. Il cible le niveau FCM de la version Android de l'année prochaine et PRODUCT_SHIPPING_API_LEVEL
, ce qui l'oblige à satisfaire aux exigences logicielles du fournisseur (VSR) de la prochaine version.
Lorsque RELEASE_AIDL_USE_UNFROZEN
est défini sur false
, Cuttlefish dispose des valeurs target-level
et PRODUCT_SHIPPING_API_LEVEL
précédentes pour refléter un appareil de version.
Dans Android 14 et versions antérieures, cette différenciation était effectuée avec différentes branches Git qui ne reprenaient pas la modification apportée à FCM target-level
, au niveau d'API d'expédition ni à tout autre code ciblant la prochaine version.
Règles de dénomination des modules
Dans Android 11, un module de bibliothèque stub est automatiquement créé pour chaque combinaison de versions et de backends activés. Pour faire référence à un module de bibliothèque de stub spécifique pour l'association, n'utilisez pas le nom du module aidl_interface
, mais le nom du module de bibliothèque de stub, qui est ifacename-version-backend, où
ifacename
: nom du moduleaidl_interface
version
correspond à l'une des valeurs suivantes :Vversion-number
pour les versions figéesVlatest-frozen-version-number + 1
pour la version "tip-of-tree" (qui n'a pas encore été figée)
backend
correspond à l'une des valeurs suivantes :java
pour le backend Java,cpp
pour le backend C++,ndk
oundk_platform
pour le backend NDK. Le premier concerne les applications, tandis que le second concerne l'utilisation de la plate-forme jusqu'à Android 13. Dans Android 13 et versions ultérieures, utilisez uniquementndk
.rust
pour le backend Rust.
Supposons qu'il existe un module nommé foo dont la dernière version est 2 et qu'il est compatible avec NDK et C++. Dans ce cas, AIDL génère les modules suivants :
- Basé sur la version 1
foo-V1-(java|cpp|ndk|ndk_platform|rust)
- Basé sur la version 2 (la dernière version stable)
foo-V2-(java|cpp|ndk|ndk_platform|rust)
- D'après la version ToT
foo-V3-(java|cpp|ndk|ndk_platform|rust)
Par rapport à Android 11 :
foo-backend
, qui faisait référence à la dernière version stable, devientfoo-V2-backend
.foo-unstable-backend
, qui faisait référence à la version ToT, devientfoo-V3-backend
Les noms des fichiers de sortie sont toujours identiques à ceux des modules.
- D'après la version 1 :
foo-V1-(cpp|ndk|ndk_platform|rust).so
- D'après la version 2 :
foo-V2-(cpp|ndk|ndk_platform|rust).so
- Selon la version de ToT :
foo-V3-(cpp|ndk|ndk_platform|rust).so
Notez que le compilateur AIDL ne crée pas de module de version unstable
ni de module sans version pour une interface AIDL stable.
Depuis Android 12, le nom du module généré à partir d'une interface AIDL stable inclut toujours sa version.
Nouvelles méthodes d'interface méta
Android 10 ajoute plusieurs méthodes d'interface méta pour l'AIDL stable.
Interroger la version de l'interface de l'objet distant
Les clients peuvent interroger la version et le hachage de l'interface implémentée par l'objet distant, puis comparer les valeurs renvoyées avec celles de l'interface utilisée par le client.
Exemple avec le backend 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();
Exemple avec le backend ndk
(et 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);
Exemple avec le backend 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();
Pour le langage Java, le côté distant DOIT implémenter getInterfaceVersion()
et getInterfaceHash()
comme suit (super
est utilisé à la place de IFoo
pour éviter les erreurs de copier-coller). L'annotation @SuppressWarnings("static")
peut être nécessaire pour désactiver les avertissements, en fonction de la configuration de javac
:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
En effet, les classes générées (IFoo
, IFoo.Stub
, etc.) sont partagées entre le client et le serveur (par exemple, les classes peuvent se trouver dans le classpath d'amorçage). Lorsque des classes sont partagées, le serveur est également associé à la version la plus récente des classes, même s'il a été créé avec une version antérieure de l'interface. Si cette interface de métadonnées est implémentée dans la classe partagée, elle renvoie toujours la version la plus récente. Toutefois, en implémentant la méthode comme ci-dessus, le numéro de version de l'interface est intégré au code du serveur (car IFoo.VERSION
est un static final int
qui est intégré lorsqu'il est référencé). La méthode peut donc renvoyer la version exacte avec laquelle le serveur a été créé.
Gérer les anciennes interfaces
Il est possible qu'un client soit mis à jour avec la version la plus récente d'une interface AIDL, mais que le serveur utilise l'ancienne interface AIDL. Dans ce cas, l'appel d'une méthode sur une ancienne interface renvoie UNKNOWN_TRANSACTION
.
Avec AIDL stable, les clients ont plus de contrôle. Côté client, vous pouvez définir une implémentation par défaut pour une interface AIDL. Une méthode de l'implémentation par défaut n'est appelée que lorsque la méthode n'est pas implémentée côté distant (car elle a été créée avec une ancienne version de l'interface). Étant donné que les valeurs par défaut sont définies globalement, elles ne doivent pas être utilisées à partir de contextes potentiellement partagés.
Exemple en C++ dans Android 13 et versions ultérieures :
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
Exemple en 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(...);
Vous n'avez pas besoin de fournir l'implémentation par défaut de toutes les méthodes dans une interface AIDL. Les méthodes qui sont garanties d'être implémentées côté distant (car vous êtes certain que le côté distant est créé lorsque les méthodes se trouvent dans la description de l'interface AIDL) n'ont pas besoin d'être remplacées dans la classe impl
par défaut.
Convertir un fichier AIDL existant en fichier AIDL structuré ou stable
Si vous disposez d'une interface AIDL existante et d'un code qui l'utilise, suivez les étapes ci-dessous pour convertir l'interface en interface AIDL stable.
Identifiez toutes les dépendances de votre interface. Pour chaque package dont dépend l'interface, déterminez si le package est défini dans AIDL stable. Si elle n'est pas définie, le package doit être converti.
Convertissez tous les éléments Parcelable de votre interface en éléments Parcelable stables (les fichiers d'interface eux-mêmes peuvent rester inchangés). Pour ce faire, exprimez leur structure directement dans les fichiers AIDL. Les classes de gestion doivent être réécrites pour utiliser ces nouveaux types. Vous pouvez le faire avant de créer un package
aidl_interface
(voir ci-dessous).Créez un package
aidl_interface
(comme décrit ci-dessus) contenant le nom de votre module, ses dépendances et toute autre information dont vous avez besoin. Pour qu'il soit stabilisé (et pas seulement structuré), il doit également être versionné. Pour en savoir plus, consultez Interfaces de gestion des versions.