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)/l'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 organisées et désorganisées.
- Les interfaces peuvent être déclarées comme stables (rétrocompatibles). Lorsque cela se produit, 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 à des types définis uniquement dans AIDL. Par exemple, une déclaration colisable (un colisable personnalisé) n'est pas structurée AIDL. Les parcelles avec leurs champs définis dans AIDL sont appelées parcelles structurées .
Une AIDL stable nécessite une AIDL structurée afin que le système de construction et le compilateur puissent comprendre si les modifications apportées aux parcelles sont rétrocompatibles. Cependant, toutes les interfaces structurées ne sont pas stables. Pour être stable, une interface doit utiliser uniquement des types structurés et doit également utiliser les fonctionnalités de gestion de versions suivantes. À l’inverse, une interface n’est pas stable si le système de build principal est utilisé pour la construire ou si unstable:true
est défini.
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'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 ci-dessus,<base_path>
estsrcs/aidl
. -
local_include_dir
: Le chemin d'où commence le nom du package. Cela correspond à<base_path>
expliqué ci-dessus. -
imports
: Une liste des modulesaidl_interface
que cela utilise. Si l'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'existe pas de versions figées d'une interface, cela ne doit pas être spécifié et il n'y aura pas de contrôle de compatibilité. Ce champ a été remplacé parversions_with_info
pour 13 et versions ultérieures. -
versions_with_info
: Liste de tuples, dont chacun contient le nom d'une version figée et une liste avec les importations de versions 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 dansaidl_api/ IFACE / V
. Ce champ a été introduit dans Android 13 et 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
: L'indicateur facultatif pour la promesse de stabilité de cette interface. Actuellement, ne prend en charge que"vintf"
. Si ce n'est pas défini, cela correspond à une interface stable dans ce contexte de compilation (donc une interface chargée ici ne peut être utilisée qu'avec des éléments compilés ensemble, par exemple sur system.img). Si ceci est défini sur"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. À partir d’Android 14, la valeur par défaut esttrue
pour les backendscpp
etjava
. -
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
: L'indicateur facultatif utilisé pour marquer que cette interface n'a pas besoin d'être stable. Lorsque cela est défini surtrue
, le système de build ne crée pas le vidage de l'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 aucun changement depuis la version précédente de l'interface. Cela permet davantage de contrôles au moment de la construction. Lorsqu'elle est définie surfalse
, cela signifie que l'interface est en développement et qu'elle comporte 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. -
backend.<type>.enabled
: Ces indicateurs 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 explicitement désactivé. Rust est désactivé par défaut. -
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
: 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
: L'indicateur facultatif pour intégrer cette interface à 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. Cet indicateur 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 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
: 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 de versions et de backends activés, une bibliothèque stub est créée. Pour savoir comment faire référence à la version spécifique de la bibliothèque stub pour un backend spécifique, consultez Règles de dénomination des modules .
Écriture de fichiers AIDL
Les interfaces dans AIDL stable sont similaires aux interfaces traditionnelles, à l'exception du fait qu'elles ne sont pas autorisées à utiliser des parcelles non structurées (car celles-ci ne sont pas stables ! voir AIDL structuré versus stable ). La principale différence dans AIDL stable réside dans la façon dont les parcelles sont définies. Auparavant, les parcelles étaient déclarées à terme ; dans AIDL stable (et donc structuré), les champs et variables parcelles 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 pour les é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 de type 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 build ( Android.mk
peut également être utilisé pour les définitions de modules hérités) :
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 dans 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 build 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 build exécute des contrôles de compatibilité entre les versions gelées et également entre 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'un colisable (nécessite l'ajout d'une valeur par défaut pour chaque élément)
- Valeurs constantes
- Dans Android 11, les enquêteurs
- Sous 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 que toutes les interfaces AIDL stables qui n'ont pas de champowner:
spécifié soient gelées. -
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 d'importation pour les versions gelées d'une interface est rétrocompatible au niveau de la couche Stable AIDL. 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 confuses 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
Au moment 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.
- le backend
cpp
obtient::android::UNKNOWN_TRANSACTION
. - Le backend
ndk
obtientSTATUS_UNKNOWN_TRANSACTION
. - Le backend
java
obtientandroid.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 cela, consultez 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 colis, les valeurs par défaut des nouveaux champs sont automatiquement renseignées. Cela signifie que les valeurs par défaut doivent être spécifiées pour tous les nouveaux champs d'un colis.
Les clients ne doivent pas s'attendre à ce que les serveurs utilisent les nouveaux champs à moins qu'ils sachent que le serveur implémente la version dans laquelle le champ est défini (voir interrogation des 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 pourraient être ajoutés à l'avenir. Par exemple, un serveur ne doit pas abandonner lorsqu'il reçoit un énumérateur dont il ne connaît pas l'existence. Il doit soit l'ignorer, soit renvoyer quelque chose pour que le client sache que ce n'est pas pris en charge dans cette implémentation.
Les syndicats
Essayer d'envoyer une union avec un nouveau champ échoue si le destinataire est ancien et ne connaît pas le champ. La mise en œuvre ne verra jamais l’union avec le nouveau domaine. 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'unions vers le nouveau champ à un ancien serveur, ou lorsqu'il s'agit d'un ancien client qui reçoit l'union d'un nouveau serveur.
Développement basé sur les drapeaux
Les interfaces en développement (non gelées) ne peuvent pas être utilisées sur les appareils en version, car leur compatibilité ascendante n'est pas garantie.
AIDL prend en charge le repli au moment de l'exécution pour ces bibliothèques d'interfaces non gelées afin que le code soit écrit sur la dernière version non gelée et continue d'être utilisé sur les appareils publiés. Le comportement rétrocompatible des clients est similaire au comportement existant et avec la solution de repli, les implémentations doivent également suivre ces comportements. Voir Utilisation d'interfaces versionnées .
Indicateur de construction 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 gelée de l'interface est utilisée au moment de l'exécution et false
signifie que les bibliothèques des versions non gelées se comportent toutes comme leur dernière version gelée. Vous pouvez remplacer l'indicateur par true
pour le développement local, mais vous devez le rétablir sur false
avant la publication. Généralement, le développement est effectué avec une configuration dont l'indicateur est défini sur true
.
Matrice de compatibilité et manifestes
Les objets d'interface fournisseur (objets VINTF) définissent les versions attendues et les versions fournies de chaque côté de l'interface 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
.
Les interfaces appartenant aux partenaires sont ajoutées aux matrices de compatibilité spécifiques à l'appareil ou au produit que l'appareil cible pendant le développement. Ainsi, lorsqu'une nouvelle version non gelée d'une interface est ajoutée à une matrice de compatibilité, les versions gelées précédentes doivent rester pendant RELEASE_AIDL_USE_UNFROZEN=false
. Vous pouvez gérer cela en utilisant différents fichiers de matrice de compatibilité pour différentes configurations RELEASE_AIDL_USE_UNFROZEN
ou en autorisant les deux versions dans un seul fichier de matrice de compatibilité utilisé dans toutes les configurations.
Par exemple, lors de l'ajout d'une version 4 non gelée, utilisez <version>3-4</version>
.
Lorsque la version 4 est gelée, vous pouvez supprimer la version 3 de la matrice de compatibilité car la version 4 gelée est utilisée lorsque RELEASE_AIDL_USE_UNFROZEN
est false
.
Modifications du client HAL
Le code client HAL doit être rétrocompatible avec chaque version gelée prise en charge précédente. Lorsque RELEASE_AIDL_USE_UNFROZEN
est false
, les services ressemblent toujours à la dernière version gelée ou à une version antérieure (par exemple, l'appel de nouvelles méthodes non gelé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 les versions précédentes supplémentaires, mais il s'agit d'un nouveau détail pour les clients des fournisseurs et les clients des interfaces appartenant aux partenaires.
Modifications de la mise en œuvre de HAL
La plus grande 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 gelée afin de fonctionner lorsque RELEASE_AIDL_USE_UNFROZEN
est false
. Prendre en compte la compatibilité ascendante dans les implémentations et le code des appareils est un nouvel exercice. Voir Utilisation d'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 framework et le code fournisseur, mais il existe des différences subtiles dont vous devez être conscient, car vous implémentez désormais efficacement deux versions qui utilisent le même code source. (la version actuelle non gelée).
Exemple : Une interface possède 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. La bibliothèque V4 étant 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
vaut false
et empêche l'utilisation de la nouvelle méthode.
Lorsque l'interface est gelée, toutes les valeurs de RELEASE_AIDL_USE_UNFROZEN
utilisent cette version gelée et le code gérant la compatibilité ascendante peut être supprimé.
Lorsque vous appelez des méthodes lors de rappels, vous devez gérer avec élégance 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 enverra la version la plus récente, et de nouvelles méthodes pourraient la renvoyer. Ceci est similaire à la manière dont les clients AIDL stables maintiennent une compatibilité ascendante avec les serveurs décrite dans Utilisation d'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();
}
}
Les nouveaux champs dans les types existants ( parcelable
, enum
, union
) peuvent ne pas exister ou contenir leurs valeurs par défaut lorsque RELEASE_AIDL_USE_UNFROZEN
est false
et que les valeurs des nouveaux champs qu'un service tente d'envoyer sont supprimées à la sortie du processus.
Les nouveaux types ajoutés dans cette version non gelée ne peuvent pas être envoyés ou reçus via l'interface.
L'implémentation ne reçoit jamais d'appel de nouvelles méthodes de la part d'un client lorsque RELEASE_AIDL_USE_UNFROZEN
est false
.
Veillez à utiliser les nouveaux énumérateurs uniquement 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 utilise l'interface distante. Cependant, avec la prise en charge du versionnement basé sur des indicateurs, vous implémentez deux versions différentes, vous souhaiterez donc peut-être obtenir la version de l'interface actuelle. Vous pouvez le faire en obtenant la version d'interface de l'objet actuel, par exemple this->getInterfaceVersion()
ou les autres méthodes pour my_ver
. Voir Interrogation de la version d'interface de l'objet distant pour plus d'informations.
Nouvelles interfaces stables VINTF
Lorsqu'un nouveau package d'interface AIDL est ajouté, il n'y a pas de dernière version gelée, il n'y a donc aucun comportement vers lequel recourir lorsque RELEASE_AIDL_USE_UNFROZEN
est false
. N'utilisez pas ces interfaces. Lorsque RELEASE_AIDL_USE_UNFROZEN
est false
, 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 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 donc pas l'ajouter au périphérique de manière conditionnelle, vous pouvez vérifier si le service est déclaré avec IServiceManager::isDeclared()
. S'il est déclaré et que l'enregistrement n'a pas abouti, abandonnez le processus. S'il n'est pas déclaré, son enregistrement échouera probablement.
La seiche comme outil de développement
Chaque année, après le gel du VINTF, nous ajustons le target-level
la matrice de compatibilité du framework (FCM) et le PRODUCT_SHIPPING_API_LEVEL
de Cuttlefish afin qu'ils reflètent les appareils lancés avec la version de l'année prochaine. Nous ajustons target-level
et PRODUCT_SHIPPING_API_LEVEL
pour nous assurer qu'un dispositif de lancement est testé et répond aux nouvelles exigences pour la version de l'année prochaine.
Lorsque RELEASE_AIDL_USE_UNFROZEN
est true
, Cuttlefish est utilisé pour le développement des futures versions d'Android. Il cible le niveau FCM et PRODUCT_SHIPPING_API_LEVEL
de la version Android de l'année prochaine, l'obligeant à satisfaire aux exigences logicielles du fournisseur (VSR) de la prochaine version.
Lorsque RELEASE_AIDL_USE_UNFROZEN
est false
, Cuttlefish a le target-level
précédent et PRODUCT_SHIPPING_API_LEVEL
pour refléter un périphérique de version. Dans Android 14 et versions antérieures, cette différenciation serait réalisée avec différentes branches Git qui ne prennent pas en compte la modification apportée au target-level
FCM, au niveau de l'API d'expédition ou à tout autre code ciblant la prochaine version.
Règles de dénomination des modules
Dans Android 11, pour chaque combinaison de versions et de backends activés, un module de bibliothèque 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 pointe de l'arbre (pas encore gelée)
-
-
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 destiné aux applications et le second à l'utilisation de la plate-forme. -
rust
pour le backend Rust.
-
Supposons qu'il existe un module nommé 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 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 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 des classes 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 a pu être construit avec une ancienne version 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 intégré lorsqu'il est référencé) et la méthode peut donc renvoyer la version exacte sur laquelle 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 dont l'implémentation est garantie du côté distant (car vous êtes certain que le distant est construit alors que les méthodes figuraient 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 existante en AIDL structuré/stable
Si vous disposez d'une interface AIDL existante et du code qui l'utilise, suivez les étapes suivantes 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 AIDL stable. S’il n’est pas défini, le package doit être converti.
Convertissez tous les colisables de votre interface en colisables stables (les fichiers d'interface eux-mêmes peuvent rester inchangés). Pour ce faire, exprimez leur structure directement dans des 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, consultez Gestion des versions des interfaces .