AIDL stable

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 AIDL Foo défini dans un package com.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 trouve Android.bp . Dans l'exemple ci-dessus, <base_path> est srcs/aidl .
  • local_include_dir : Le chemin d'où commence le nom du package. Cela correspond à <base_path> expliqué ci-dessus.
  • imports : Une liste des modules aidl_interface que cela utilise. Si l'une de vos interfaces AIDL utilise une interface ou un parcelable d'une autre aidl_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 sous api_dir , A partir d'Android 11, les versions sont figées sous aidl_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é par versions_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 dans aidl_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 champs versions sont automatiquement migrés vers versions_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 est true pour les backends cpp et java .
  • host_supported : L'indicateur facultatif qui, lorsqu'il est défini sur true , 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 sur true , 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 sur true , 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 sur false , cela signifie que l'interface est en développement et comporte de nouvelles modifications. Par conséquent, l'exécution foo-freeze-api générera une nouvelle version et changera automatiquement la valeur en true . 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 est false .
  • backend.[cpp|ndk].additional_shared_libraries : Introduit dans Android 14, cet indicateur ajoute des dépendances aux bibliothèques natives. Cet indicateur est utile avec ndk_header et cpp_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 lorsque backend.java.platform_apis est vrai.
  • backend.java.platform_apis : indicateur facultatif qui doit être défini sur true 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: ...,
    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 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 champ owner: spécifié soient gelées.
  • AIDL_FROZEN_OWNERS="aosp test" - la construction nécessite que toutes les interfaces AIDL stables soient gelées avec le owner: 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 obtient STATUS_UNKNOWN_TRANSACTION .
  • Le backend java obtient android.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 .

Matrices

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 .

Manifestes

Dans Android 15 (AOSP expérimental), une modification dans libvintf est introduite pour modifier les fichiers manifestes au moment de la construction en fonction de la valeur de RELEASE_AIDL_USE_UNFROZEN .

Les manifestes et les fragments de manifeste déclarent la version d'une interface implémentée par un service. Lors de l'utilisation de la dernière version non gelée d'une interface, le manifeste doit être mis à jour pour refléter cette nouvelle version. Lorsque RELEASE_AIDL_USE_UNFROZEN=false , les entrées du manifeste sont ajustées par libvintf pour refléter le changement dans la bibliothèque AIDL générée. La version est modifiée de la version non gelée, N , à la dernière version gelée N - 1 . Par conséquent, les utilisateurs n’ont pas besoin de gérer plusieurs manifestes ou fragments de manifeste pour chacun de leurs services.

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 module aidl_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 ou ndk_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 devient foo- V2 - backend
  • foo-unstable- backend , qui faisait référence à la version ToT devient foo- 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.

  1. 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.

  2. 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).

  3. 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 .