AIDL stable

Android 10 prend en charge le langage Android Interface Definition Language (AIDL) stable, une nouvelle façon de suivre l'interface de programme d'application (API) ou l'interface binaire d'application (ABI) fournie par les interfaces AIDL. La version stable d'AIDL présente les principales différences suivantes par rapport à AIDL:

  • 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 souhaités sont automatiquement créés en fonction de leur définition AIDL, et sont automatiquement marshalés et non matriciels.
  • Les interfaces peuvent être déclarées comme stables (rétrocompatibles). Dans ce cas, le suivi de l'API et sa version sont enregistrés dans un fichier situé à côté de l'interface AIDL.

AIDL structuré ou stable

L'AIDL structuré fait référence aux types définis uniquement dans AIDL. Par exemple, une déclaration morcelable (parcelable) n'est pas une déclaration AIDL structurée. Les parcelables dont les champs sont définis dans AIDL sont appelés parcelables structurés.

Stable AIDL nécessite un AIDL structuré afin que le système de compilation et le compilateur puissent comprendre si les modifications apportées aux parcelables 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, ainsi que 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 la 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 AIDL Foo défini dans un package com.acme doit se trouver sous <base_path>/com/acme/Foo.aidl, où <base_path> peut correspondre à n'importe quel répertoire lié au répertoire où se trouve Android.bp. Dans l'exemple ci-dessus, <base_path> correspond à srcs/aidl.
  • local_include_dir: chemin d'accès à partir duquel commence le nom du package. Elle correspond à <base_path> expliqué ci-dessus.
  • imports: liste des modules aidl_interface utilisés par ce service. Si l'une de vos interfaces AIDL utilise une interface ou un élément parcelable provenant d'un autre élément aidl_interface, saisissez 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 la version (par exemple, -V1) pour faire référence à une version spécifique. La spécification d'une version est prise en charge depuis Android 12.
  • versions: versions précédentes de l'interface figées sous api_dir. À 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 aucune vérification de compatibilité ne sera effectuée. Ce champ a été remplacé par versions_with_info pour les versions 13 et ultérieures.
  • versions_with_info: liste des tuples, chacun contenant le nom d'une version figée et une liste avec les importations de versions d'autres modules aidl_interface importés par cette version de l'interface aidl_interface. La définition de la version V d'une interface IFACE d'interface AIDL se trouve dans aidl_api/IFACE/V. Ce champ a été introduit dans Android 13 et ne doit pas ê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 appelle *-update-api ou *-freeze-api.
  • stability: option facultative de la promesse de stabilité de cette interface. Actuellement, cette fonctionnalité n'est compatible qu'avec "vintf". Si stability n'est pas défini, le système de compilation vérifie que l'interface est rétrocompatible, sauf si unstable est spécifié. Une interface non définie correspond à une interface stable dans ce contexte de compilation (donc tous les éléments système, par exemple dans system.img et les partitions associées, ou tous les éléments des fournisseurs, par exemple les éléments dans vendor.img et les partitions associées). Si stability 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 est true pour les backends cpp et java.
  • host_supported: option facultative qui, lorsqu'elle est définie sur true, rend les bibliothèques générées disponibles dans l'environnement hôte.
  • unstable: option facultative utilisée pour indiquer que cette interface n'a pas besoin d'être stable. Lorsque ce paramètre est défini sur true, le système de compilation ne crée pas le vidage de l'API pour l'interface et ne nécessite aucune mise à jour.
  • frozen: l'indicateur facultatif qui, lorsqu'il est défini sur true, signifie que l'interface n'a subi aucune modification par rapport à la version précédente. Cela permet d'effectuer davantage de vérifications de la durée de compilation. Si la valeur est false, cela signifie que l'interface est en cours de développement et comporte de nouvelles modifications. Par conséquent, l'exécution de foo-freeze-api génère une nouvelle version et remplace automatiquement la valeur par true. Introduit dans Android 14.
  • backend.<type>.enabled: ces options activent chacun des backends pour lesquels le compilateur AIDL génère du code. Actuellement, 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 (version expérimentale AOSP).
  • backend.<type>.apex_available: liste des noms APEX pour lesquels la bibliothèque de bouchon 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 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: option facultative permettant de spécifier la version du SDK avec laquelle la bibliothèque de bouchons Java est compilée. La valeur par défaut est "system_current". Ce champ ne doit pas être défini lorsque backend.java.platform_apis est défini sur "true".
  • backend.java.platform_apis: option facultative qui doit être définie sur true lorsque les bibliothèques générées doivent compiler avec l'API de la plate-forme plutôt que avec le SDK.

Pour chaque combinaison de versions et de backends activés, une bibliothèque de bouchons est créée. Pour savoir comment faire référence à la version spécifique de la bibliothèque de bouchon pour un backend spécifique, consultez la section Règles de dénomination des modules.

Écrire des fichiers AIDL

Les interfaces dans une version stable d'AIDL 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 Comparaison entre les structures AIDL structurées et stables). La principale différence d'un AIDL stable réside dans la façon dont les parcelles sont définis. Auparavant, les parcelles étaient déclarées vers l'avant. Dans AIDL stable (et donc structurées), les champs et variables parcelables 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 acceptée (mais non obligatoire) pour boolean, char, float, double, byte, int, long et String. Sous Android 12, les valeurs par défaut pour les énumérations définies par l'utilisateur sont également compatibles. Lorsqu'aucune valeur par défaut n'est spécifiée, une valeur vide ou de type 0 est utilisée. Les énumérations sans valeur par défaut sont initialisées sur 0, même en l'absence d'énumérateur nul.

Utiliser des bibliothèques de bouchon

Après avoir ajouté des bibliothèques de bouchon en tant que dépendance à votre module, vous pouvez les inclure dans vos fichiers. Voici des exemples de bibliothèques bouchons dans le système de compilation (Android.mk peut également être utilisé pour les définitions d'anciens modules):

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 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 portant 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 sa compilation, foo-Free-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, représentant tous deux la version nouvellement figée de l'interface. foo-leave-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. Cependant, la dernière version stable est spécifiée dans imports dans versions_with_info pour l'importation, qui ne possède 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 Top of Tree (ToT) et la dernière version figée.

En outre, vous devez gérer la définition de la version de l'API 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 la version de l'API pour la ToT.

Pour maintenir la stabilité d'une interface, les propriétaires peuvent ajouter les éléments suivants:

  • 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 (une valeur par défaut doit être ajoutée pour chaque élément)
  • Valeurs constantes
  • Dans Android 11, les énumérateurs
  • Sous Android 12, les champs situés à la fin d'une union

Aucune autre action n'est autorisée, et personne d'autre ne peut modifier une interface (sinon, cela risque d'entrer en conflit avec les modifications apportées par un propriétaire).

Pour vérifier que toutes les interfaces sont figées en vue de leur 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 soient figées, pour lesquelles aucun champ owner: n'est spécifié.
  • AIDL_FROZEN_OWNERS="aosp test" : la compilation nécessite que toutes les interfaces AIDL stables soient figées avec le champ owner: 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 perturbées lors de la combinaison de différentes versions de types. En règle générale, pour les packages courants ou ne comportant que des types, cela est sûr, car du code doit déjà être écrit pour gérer des types inconnus à partir de 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 gestion des versions

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 reçoivent une erreur ou une exception, en fonction du 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 ce problème, consultez la section Interroger des versions et utiliser les valeurs par défaut.

Éléments Parcelables

Lorsque de nouveaux champs sont ajoutés aux parcelables, les anciens clients et serveurs les suppriment. Lorsque de nouveaux clients et serveurs reçoivent d'anciens parcelables, 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'une parcelle.

Les clients ne doivent pas s'attendre à ce que les serveurs utilisent les nouveaux champs, sauf s'ils savent que le serveur met en œuvre la version pour laquelle le champ est défini (voir Interroger 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, le cas échéant, car d'autres peuvent être ajoutés ultérieurement. Par exemple, un serveur ne doit pas abandonner lorsqu'il reçoit un énumérateur dont il n'a pas connaissance. Elle doit l'ignorer ou renvoyer un élément afin que le client sache qu'il n'est pas compatible avec cette implémentation.

Union

La tentative d'envoi d'une union avec un nouveau champ échoue si le destinataire est ancien et ne connaît pas le champ. L'implémentation ne verra jamais l'union avec le nouveau champ. S'il s'agit d'une transaction à sens unique, l'échec est ignoré. 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 une union définie sur le nouveau champ à un ancien serveur, ou lorsqu'un ancien client reçoit l'union d'un nouveau serveur.

Développement basé sur des indicateurs

Les interfaces en développement (non figées) ne peuvent pas être utilisées sur les appareils en version finale, car leur rétrocompatibilité n'est pas garantie.

AIDL accepte les remplacements au moment de l'exécution pour ces bibliothèques d'interfaces non figées afin que le code soit écrit sur la dernière version non figée et qu'il puisse toujours être utilisé sur les versions disponibles. Le comportement rétrocompatible des clients est semblable au comportement existant et, avec le remplacement, les implémentations doivent également suivre ces comportements. Consultez la section Utiliser des interfaces avec gestion des versions.

Indicateur de build AIDL

L'option qui contrôle ce comportement est RELEASE_AIDL_USE_UNFROZEN définie dans build/release/build_flags.bzl. true signifie que la version détaché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 l'option par true pour le développement local, mais vous devez la rétablir sur false avant la publication. Le développement s'effectue généralement à l'aide d'une configuration dont l'option est définie sur true.

Matrice de compatibilité et fichiers 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 autres que Cuttlefish ciblent la dernière matrice de compatibilité uniquement après le blocage des interfaces. Il n'existe donc aucune différence dans les bibliothèques AIDL basées sur RELEASE_AIDL_USE_UNFROZEN.

Matrices

Les interfaces appartenant au partenaire sont ajoutées aux matrices de compatibilité spécifiques à l'appareil ou au produit que l'appareil cible lors du 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 pour 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, 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 elle est utilisée lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false.

Fichiers manifestes

Dans Android 15 (expérimental AOSP), 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 manifeste déclarent la version d'interface mise en œuvre par un service. 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 dans la bibliothèque AIDL générée. La version est modifiée de la version non figée, N, à la dernière version figée N - 1. Par conséquent, les utilisateurs n'ont pas besoin de gérer plusieurs fichiers manifestes ou fragments de fichiers manifestes pour chacun de leurs services.

Modifications du client HAL

Le code client HAL doit être rétrocompatible avec chaque précédente version figée compatible. Lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false, les services ressemblent toujours à la dernière version figée ou 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 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 détenues par le partenaire.

Modifications apportées à l'implémentation HAL

La principale différence dans le développement HAL avec développement basé sur des indicateurs est que les implémentations HAL doivent être rétrocompatibles avec la dernière version figée pour fonctionner lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false. La prise en compte de la rétrocompatibilité dans les implémentations et le code d'appareil est un nouvel exercice. Consultez la section Utiliser des interfaces avec gestion des versions.

Les considérations de rétrocompatibilité 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 de légères différences que vous devez connaître, car vous implémentez désormais efficacement 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 lorsque UNKNOWN_TRANSACTION est renvoyé. Les clients peuvent implémenter deux versions différentes d'un rappel en fonction de la configuration de version. Vous ne pouvez donc pas supposer que le client envoie la version la plus récente, et de nouvelles méthodes peuvent la renvoyer. Cette approche est semblable à la façon dont les clients AIDL stables maintiennent la rétrocompatibilité avec les serveurs, décrit dans la section Utiliser des interfaces avec gestion des versions.

// 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 des types existants (parcelable, enum, union) peuvent ne pas exister ou contenir leurs valeurs par défaut lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false et que les valeurs des nouveaux champs qu'un service tente d'envoyer sont supprimées en fin de 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 n'obtient jamais d'appel de nouvelles méthodes de la part des clients lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false.

Veillez à n'utiliser les nouveaux énumérateurs qu'avec la version dans laquelle ils ont été introduits, et non avec la version précédente.

Normalement, vous utilisez foo->getInterfaceVersion() pour identifier la version utilisée par l'interface à distance. Toutefois, avec la prise en charge de la gestion des versions basée sur les options, vous implémentez deux versions différentes. Il peut donc être utile d'obtenir la version de l'interface actuelle. Pour ce faire, obtenez la version de l'interface de l'objet actuel, par exemple this->getInterfaceVersion() ou les autres méthodes pour my_ver. Pour en savoir plus, consultez la section Interroger la version d'interface de l'objet distant.

Nouvelles interfaces VINTF stables

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 aucun comportement à utiliser lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false. N'utilisez pas ces interfaces. Lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false, Service Manager n'autorise pas le service à enregistrer l'interface, et les clients ne la trouvent 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 donc pas l'ajouter à l'appareil de manière conditionnelle, vous pouvez vérifier s'il est déclaré avec IServiceManager::isDeclared(). Si elle est déclarée et que son enregistrement échoue, annulez le processus. Si elle n'est pas déclarée, il ne devrait pas s'enregistrer.

Seiche comme outil de développement

Chaque année, après l'arrêt du VINTF, nous ajustons la matrice de compatibilité du framework (FCM) target-level et le PRODUCT_SHIPPING_API_LEVEL de Cuttlefish afin qu'elles reflètent les appareils lancés l'année prochaine. Nous ajustons target-level et PRODUCT_SHIPPING_API_LEVEL pour nous assurer que certains appareils sur le lancement sont testés et répondent aux nouvelles exigences pour 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. Elle cible le niveau FCM de la version Android de l'année prochaine et PRODUCT_SHIPPING_API_LEVEL, ce qui l'oblige à répondre aux exigences logicielles des fournisseurs (VSR) de la version suivante.

Lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false, Cuttlefish dispose des éléments target-level et PRODUCT_SHIPPING_API_LEVEL précédents pour refléter un appareil de version. Dans Android 14 et versions antérieures, cette différenciation est effectuée avec différentes branches Git qui ne récupèrent pas les modifications apportées à FCM target-level, au niveau d'API de livraison ou à tout autre code ciblant la prochaine version.

Règles de dénomination des modules

Dans Android 11, un module de bibliothèque bouchon est automatiquement créé pour chaque combinaison de versions et de backends activés. Pour faire référence à un module de bibliothèque bouchon spécifique à des fins d'association, n'utilisez pas le nom du module aidl_interface, mais le nom du module de bibliothèque bouchon, qui est ifacename-version-backend, où

  • ifacename: nom du module aidl_interface
  • version est l'une des valeurs suivantes :
    • Vversion-number pour les versions figées
    • Vlatest-frozen-version-number + 1 pour la version avec la pointe de l'arbre (encore à geler)
  • backend est l'une des valeurs suivantes :
    • java pour le backend Java
    • cpp pour le backend C++
    • ndk ou ndk_platform pour le backend du NDK. Le premier s'applique 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 est compatible avec le 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)
  • Basé sur la version de la ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Par rapport à Android 11,

  • foo-backend, qui désignait la dernière version stable, devient foo-V2-backend
  • foo-unstable-backend, qui faisait référence à la version des conditions d'utilisation, 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 de la 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 sans gestion des versions pour une interface AIDL stable. À partir d'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 la version stable de l'AIDL.

Interroger la version d'interface de l'objet distant

Les clients peuvent interroger la version et le hachage de l'interface implémentée par l'objet distant, et 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 le backend 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 de classe de démarrage). Lorsque des classes sont partagées, le serveur est également lié à leur version la plus récente, même s'il a pu être créé 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. Toutefois, en implémentant la méthode 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 intégré lorsqu'il est référencé) et la méthode peut donc renvoyer la version exacte avec laquelle le serveur a été créé.

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

Grâce à la version stable d'AIDL, 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 lorsqu'elle 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 de manière globale, vous ne devez pas les utiliser à 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(...);

Il n'est pas nécessaire 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 qu'elle est créée au moment où les méthodes se trouvaient dans la description de l'interface AIDL) n'ont pas besoin d'être remplacées dans la classe impl par défaut.

Convertir un AIDL existant en AIDL structuré/stable

Si vous disposez d'une interface AIDL et que du code l'utilise, procédez comme suit pour la convertir en une interface AIDL stable.

  1. Identifiez toutes les dépendances de votre interface. Déterminez si chaque package dont dépend l'interface est défini dans AIDL stable. S'il n'est pas défini, le package doit être converti.

  2. Convertissez tous les parcelables de votre interface en parcelables stables (les fichiers d'interface eux-mêmes peuvent rester inchangés). Pour ce faire, ils doivent exprimer leur structure directement dans des 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).

  3. 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 la rendre stabilisée (et pas seulement structurée), vous devez également gérer les versions. Pour en savoir plus, consultez la section Interfaces de gestion des versions.