Android 10 ajoute la prise en charge du langage AIDL (Android Interface Definition Language) stable, une nouvelle façon de suivre l'interface de programme d'application (API)/interface binaire d'application (ABI) fournie par les interfaces AIDL. L'AIDL stable présente les principales différences suivantes par rapport à l'AIDL :
- Les interfaces sont définies dans le système de construction avec
aidl_interfaces
. - Les interfaces ne peuvent contenir que des données structurées. Les parcelles représentant les types souhaités sont automatiquement créées en fonction de leur définition AIDL et sont automatiquement triées et non triées.
- Les interfaces peuvent être déclarées stables (rétrocompatibles). Lorsque cela se produit, leur API est suivie et versionnée dans un fichier à côté de l'interface AIDL.
Définir une interface AIDL
Une définition de aidl_interface
ressemble à ceci :
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
: Le nom du module d'interface AIDL qui identifie de manière unique une interface AIDL. -
srcs
: La liste des fichiers sources AIDL qui composent l'interface. Le chemin d'unFoo
de type AIDL 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 ci-dessus,<base_path>
estsrcs/aidl
. -
local_include_dir
: chemin d'accès à partir duquel le nom du package commence. Il correspond à<base_path>
expliqué ci-dessus. -
imports
: Une liste des modulesaidl_interface
que ceci utilise. Si une de vos interfaces AIDL utilise une interface ou un parcelable d'une autreaidl_interface
, mettez 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
, A partir d'Android 11, lesversions
sont figées sousaidl_api/ name
. S'il n'y a pas de versions figées d'une interface, cela ne doit pas être spécifié et il n'y aura pas de vérification de compatibilité. Ce champ a été remplacé parversions_with_info
pour 13 et supérieur. -
versions_with_info
: liste de tuples, dont chacun contient le nom d'une version gelée et une liste avec les importations de version d'autres modules aidl_interface que cette version de aidl_interface a importés. La définition de la version V d'une interface AIDL IFACE se trouve àaidl_api/ IFACE / V
. Ce champ a été introduit dans Android 13, et il n'est pas censé être modifié directement dans Android.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 invoque*-update-api
ou*-freeze-api
. -
stability
: Le drapeau facultatif pour la promesse de stabilité de cette interface. Actuellement, ne prend en charge que"vintf"
. S'il n'est pas défini, cela correspond à une interface stable dans ce contexte de compilation (ainsi, une interface chargée ici ne peut être utilisée qu'avec des choses compilées ensemble, par exemple sur system.img). S'il vaut"vintf"
, cela correspond à une promesse de stabilité : l'interface doit rester stable tant qu'elle est utilisée. -
gen_trace
: L'indicateur facultatif pour activer ou désactiver le traçage. La valeur par défaut estfalse
. -
host_supported
: l'indicateur facultatif qui, lorsqu'il est défini surtrue
, rend les bibliothèques générées disponibles pour l'environnement hôte. -
unstable
: Le drapeau optionnel utilisé pour marquer que cette interface n'a pas besoin d'être stable. Lorsqu'il est défini surtrue
, le système de construction ne crée pas de vidage d'API pour l'interface et n'exige pas qu'il soit mis à jour. -
frozen
: L'indicateur facultatif qui, lorsqu'il est défini surtrue
, signifie que l'interface n'a subi aucune modification depuis la version précédente de l'interface. Cela permet plus de vérifications au moment de la construction. Lorsqu'il est défini surfalse
, cela signifie que l'interface est en développement et a de nouvelles modifications. Par conséquent, l'exécutionfoo-freeze-api
générera une nouvelle version et changera automatiquement la valeur entrue
. Introduit dans Android 14 (expérimental AOSP). -
backend.<type>.enabled
: ces drapeaux basculent chacun des backends pour lesquels le compilateur AIDL génère du code. Actuellement, quatre backends sont pris en charge : 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. -
backend.<type>.apex_available
: La liste des noms APEX pour lesquels la bibliothèque de stub générée est disponible. -
backend.[cpp|java].gen_log
: L'indicateur facultatif qui contrôle s'il faut générer du code supplémentaire pour collecter des informations sur la transaction. -
backend.[cpp|java].vndk.enabled
: Le drapeau facultatif pour faire de cette interface une partie de VNDK. La valeur par défaut estfalse
. -
backend.java.sdk_version
: L'indicateur facultatif pour spécifier la version du SDK sur laquelle la bibliothèque de stub Java est construite. La valeur par défaut est"system_current"
. Cela ne doit pas être défini lorsquebackend.java.platform_apis
est vrai. -
backend.java.platform_apis
: l'indicateur facultatif qui doit être défini surtrue
lorsque les bibliothèques générées doivent être construites sur l'API de la plate-forme plutôt que sur le SDK.
Pour chaque combinaison des versions et des backends activés, une bibliothèque de stub est créée. Pour savoir comment faire référence à la version spécifique de la bibliothèque de stub pour un backend spécifique, consultez Règles de nommage des modules .
Ecriture de fichiers AIDL
Les interfaces dans AIDL stable sont similaires aux interfaces traditionnelles, à l'exception qu'elles ne sont pas autorisées à utiliser des parcelables non structurés (car ils ne sont pas stables !). La principale différence dans l'AIDL stable est la façon dont les parcelles sont définies. Auparavant, les colis étaient déclarés à terme ; dans AIDL stable, les champs parcelables et les variables sont définis explicitement.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Une valeur par défaut est actuellement prise en charge (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'une valeur par défaut n'est pas spécifiée, une valeur semblable à 0 ou vide 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.
Utilisation 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 construction ( Android.mk
peut également être utilisé pour les définitions de module héritées) :
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"],
...
}
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
La déclaration d'un module avec le nom foo crée également une cible dans le système de construction que vous pouvez utiliser pour gérer l'API du module. Une fois construit, foo-freeze-api ajoute une nouvelle définition d'API sous api_dir
ou aidl_api/ name
, selon la version d'Android, et ajoute un fichier .hash
, tous deux représentant la version nouvellement gelé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. Fondamentalement, imports
dans versions_with_info
sont copiées à partir du champ imports
. Mais la dernière version stable est spécifiée dans imports
dans versions_with_info
pour l'importation qui n'a pas de version explicite. Une fois la propriété versions_with_info
spécifiée, le système de construction exécute des vérifications de compatibilité entre les versions gelées et également entre le Top of Tree (ToT) et la dernière version gelé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 maintenir la stabilité d'une interface, les propriétaires peuvent ajouter de nouveaux :
- Méthodes jusqu'à la fin d'une interface (ou méthodes avec de nouvelles séries explicitement définies)
- Éléments à la fin d'une parcelle (nécessite l'ajout d'une valeur par défaut pour chaque élément)
- Valeurs constantes
- Dans Android 11, les enquêteurs
- Dans Android 12, champs jusqu'à la fin d'une union
Aucune autre action n'est autorisée et personne d'autre ne peut modifier une interface (sinon elles risquent d'entrer en collision avec les modifications apportées par un propriétaire).
Pour tester que toutes les interfaces sont gelées pour la publication, vous pouvez créer avec l'ensemble de variables d'environnement suivant :
-
AIDL_FROZEN_REL=true m ...
- la construction nécessite le gel de toutes les interfaces AIDL stables qui n'ont pasowner:
champ spécifié. -
AIDL_FROZEN_OWNERS="aosp test"
- la construction nécessite que toutes les interfaces AIDL stables soient gelées avec leowner:
champ 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. Cependant, leur mise à jour nécessite la mise à jour de tous les serveurs et clients qui utilisent l'ancienne version de l'interface, et certaines applications peuvent être confondues lors du mélange de différentes versions de types. Généralement, pour les packages de types uniquement ou communs, cela est sûr 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 versionnées
Méthodes d'interface
Lors de l'exécution, lorsque vous essayez d'appeler de nouvelles méthodes sur un ancien serveur, les nouveaux clients obtiennent soit une erreur, soit une exception, selon le backend.
-
cpp
backend obtient::android::UNKNOWN_TRANSACTION
. -
ndk
backend obtientSTATUS_UNKNOWN_TRANSACTION
. -
java
backend obtientandroid.os.RemoteException
avec un message indiquant que l'API n'est pas implémentée.
Pour les stratégies permettant de gérer cela, voir interroger les versions et utiliser les valeurs par défaut .
Colisables
Lorsque de nouveaux champs sont ajoutés aux parcelles, les anciens clients et serveurs les suppriment. Lorsque de nouveaux clients et serveurs reçoivent d'anciens colisables, 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 colisable.
Les clients ne doivent pas s'attendre à ce que les serveurs utilisent les nouveaux champs à moins qu'ils ne sachent que le serveur implémente la version dans laquelle le champ est défini (voir interroger les versions ).
Énumérations et constantes
De même, les clients et les serveurs devraient rejeter ou ignorer les valeurs constantes et les énumérateurs non reconnus, selon le cas, car d'autres pourraient être ajoutés à l'avenir. Par exemple, un serveur ne doit pas abandonner lorsqu'il reçoit un énumérateur dont il n'a pas connaissance. Il doit soit l'ignorer, soit renvoyer quelque chose pour que le client sache qu'il n'est pas pris en charge dans cette implémentation.
Les syndicats
Essayer d'envoyer 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 à sens unique ; 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 qui reçoit l'union d'un nouveau serveur.
Règles de nommage des modules
Dans Android 11, pour chaque combinaison des versions et des backends activés, un module de bibliothèque de stub est automatiquement créé. Pour faire référence à un module de bibliothèque stub spécifique pour la liaison, n'utilisez pas le nom du module aidl_interface
, mais le nom du module de bibliothèque stub, qui est ifacename - version - backend , où
-
ifacename
: nom du moduleaidl_interface
-
version
est l'une des-
V version-number
pour les versions gelées -
V latest-frozen-version-number + 1
pour la version tip-of-tree (encore à geler)
-
-
backend
est l'un des-
java
pour le backend Java, -
cpp
pour le backend C++, -
ndk
oundk_platform
pour le backend NDK. Le premier est pour les applications, et le second est pour l'utilisation de la plate-forme, -
rust
pour le backend Rust.
-
Supposons qu'il existe un module avec le nom foo et que sa dernière version est 2 , et qu'il prend en charge à la fois NDK et C++. Dans ce cas, AIDL génère ces modules :
- 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)
-
- Basé sur 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 les mêmes que les noms des modules.
- Basé sur la version 1 :
foo-V1-(cpp|ndk|ndk_platform|rust).so
- Basé sur la version 2 :
foo-V2-(cpp|ndk|ndk_platform|rust).so
- Basé sur la version ToT :
foo-V3-(cpp|ndk|ndk_platform|rust).so
Notez que le compilateur AIDL ne crée ni un module de version unstable
, ni un module non versionné 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 de méta-interface
Android 10 ajoute plusieurs méthodes de méta-interface pour l'AIDL stable.
Interrogation de la version d'interface de l'objet distant
Les clients peuvent interroger la version et le hachage de l'interface que l'objet distant implémente et comparer les valeurs renvoyées avec les valeurs de l'interface que le client utilise.
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 le 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, selon la configuration 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 chemin de classe de démarrage). Lorsque les classes sont partagées, le serveur est également lié à la version la plus récente des classes, même s'il peut avoir été construit avec une version plus ancienne de l'interface. Si cette méta-interface est implémentée dans la classe partagée, elle renvoie toujours la version la plus récente. Cependant, en implémentant la méthode comme ci-dessus, le numéro de version de l'interface est intégré dans le code du serveur (car IFoo.VERSION
est un static final int
qui est en ligne lorsqu'il est référencé) et ainsi la méthode peut renvoyer la version exacte que le serveur a été construit avec.
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 de tels 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 sur une interface AIDL. Une méthode dans l'implémentation par défaut est invoquée uniquement lorsque la méthode n'est pas implémentée du côté distant (car elle a été construite 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++ sous 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 du côté distant (parce que vous êtes certain que la télécommande est construite lorsque les méthodes étaient dans la description de l'interface AIDL) n'ont pas besoin d'être remplacées dans la classe impl
par défaut.
Conversion d'AIDL existant en AIDL structuré/stable
Si vous disposez d'une interface AIDL existante et du code qui l'utilise, procédez comme suit pour convertir l'interface en une 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 une AIDL stable. S'il n'est pas défini, le package doit être converti.
Convertissez tous les colis de votre interface en colis 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. Cela peut être fait avant de créer un package
aidl_interface
(ci-dessous).Créez un package
aidl_interface
(comme décrit ci-dessus) qui contient le nom de votre module, ses dépendances et toute autre information dont vous avez besoin. Pour le rendre stabilisé (pas seulement structuré), il doit également être versionné. Pour plus d'informations, voir Gestion des versions des interfaces .