AIDL stable

Android 10 prend en charge le langage de définition d'interface Android (AIDL) stable, une nouvelle façon de suivre l'interface de programmation d'application (API) et l'interface binaire d'application (ABI) fournies par les interfaces AIDL. L'AIDL stable fonctionne exactement comme l'AIDL, mais le système de compilation suit la compatibilité des interfaces, et des restrictions s'appliquent à ce que vous pouvez faire:

  • Les interfaces sont définies dans le système de compilation avec aidl_interfaces.
  • Les interfaces ne peuvent contenir que des données structurées. Les éléments Parcelables représentant les types préférés sont automatiquement créés en fonction de leur définition AIDL, et sont automatiquement marshallés et unmarshallés.
  • 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

Le AIDL structuré fait référence aux types définis uniquement en AIDL. Par exemple, une déclaration parcelable (un parcelable personnalisé) n'est pas un AIDL structuré. Les éléments parcelables dont les champs sont définis dans AIDL sont appelés éléments parcellables 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 et doit également utiliser les fonctionnalités de gestion des versions suivantes. À l'inverse, une interface n'est pas stable si le système de compilation de base est utilisé pour la compiler ou si unstable:true est défini.

Définir une interface AIDL

Une définition de aidl_interface se présente comme suit:

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 dans <base_path>/com/acme/Foo.aidl, où <base_path> peut être n'importe quel répertoire associé au répertoire où se trouve Android.bp. Dans l'exemple précédent, <base_path> est srcs/aidl.
  • local_include_dir: chemin d'accès à partir duquel le nom du package commence. Elle correspond à <base_path> expliqué ci-dessus.
  • imports: liste des modules aidl_interface utilisés par ce service. Si l'une de vos interfaces AIDL utilise une interface ou un parcelable d'un autre aidl_interface, indiquez son nom ici. Il peut s'agir du nom seul, pour faire référence à la dernière version, ou du nom avec le suffixe de la version (par exemple, -V1) pour faire référence à une version spécifique. La spécification d'une version est possible 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. Si aucune version congelée d'une interface n'est disponible, 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 Android 13 et versions 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 AIDL IFACE se trouve à l'emplacement 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 pour la promesse de stabilité de cette interface. 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é. L'absence de définition correspond à une interface stable dans ce contexte de compilation (soit toutes les choses système, par exemple, les choses dans system.img et les partitions associées, soit toutes les choses du fournisseur, par exemple, les choses 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, met les bibliothèques générées à la disposition de l'environnement hôte.
  • unstable: option facultative utilisée pour indiquer que cette interface n'a pas besoin d'être stable. Lorsque cette valeur est définie sur true, le système de compilation ne crée pas le dump de l'API pour l'interface et ne nécessite pas qu'il soit mis à jour.
  • frozen: option facultative qui, lorsqu'elle est définie sur true, signifie que l'interface n'a pas été modifiée depuis la version précédente de l'interface. Cela permet d'effectuer davantage de vérifications au moment de la compilation. Lorsque cette valeur est définie sur false, cela signifie que l'interface est en cours de développement et qu'elle comporte de nouvelles modifications. L'exécution de foo-freeze-api génère donc une nouvelle version et modifie automatiquement la valeur en true. Introduit dans Android 14.
  • backend.<type>.enabled: ces indicateurs activent ou désactivent chacun des backends pour lesquels le compilateur AIDL génère du code. Quatre backends sont compatibles: Java, C++, NDK et Rust. Les backends Java, C++ et NDK sont activés par défaut. Si l'un de ces trois backends n'est pas nécessaire, il doit être désactivé explicitement. Rust est désactivé par défaut jusqu'à Android 15.
  • backend.<type>.apex_available: liste des noms APEX pour lesquels la bibliothèque de bouchons 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 paramètre ne doit pas être défini lorsque backend.java.platform_apis est 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 des versions et des 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 bouchons pour un backend spécifique, consultez la section Règles de dénomination des modules.

Écrire des fichiers AIDL

Les interfaces dans l'AIDL stable sont similaires aux interfaces traditionnelles, à l'exception qu'elles ne sont pas autorisées à utiliser des éléments parcelables non structurés (car ils ne sont pas stables ! voir AIDL structuré par rapport à l'AIDL stable). La principale différence dans l'AIDL stable réside dans la façon dont les éléments parcellables 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 acceptée (mais pas 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 semblable à 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 bouchons

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

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference 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

Gérer les versions des interfaces

Déclarer un module avec le nom foo crée également une cible dans le système de compilation que vous pouvez utiliser pour gérer l'API du module. Lors de sa compilation, foo-keep-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-gel-api met également à jour la propriété versions_with_info pour refléter la version supplémentaire et imports pour la version. En gros, imports dans versions_with_info est copié à partir du champ imports. Toutefois, la dernière version stable est spécifiée dans imports dans versions_with_info pour l'importation, qui ne dispose pas d'une version explicite. Une fois la propriété versions_with_info spécifiée, le système de compilation effectue des vérifications de compatibilité entre les versions congelées, ainsi qu'entre le sommet de l'arborescence (ToT) et la dernière version congelée.

En outre, vous devez gérer la définition de l'API de la version ToT. Chaque fois qu'une API est mise à jour, exécutez foo-update-api pour mettre à jour aidl_api/name/current, qui contient la définition de l'API de la version ToT.

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

  • Méthodes à la fin d'une interface (ou méthodes avec de nouveaux numéros de série explicitement définis)
  • Éléments à la fin d'un parcelable (un paramètre par défaut doit être ajouté pour chaque élément)
  • Valeurs constantes
  • Sous 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 pour la publication, vous pouvez effectuer une compilation en définissant les variables d'environnement suivantes:

  • AIDL_FROZEN_REL=true m ... : la compilation nécessite la congélation de toutes les interfaces AIDL stables qui ne spécifient pas de champ owner:.
  • 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 d'importations pour les versions congelé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 une version précédente de l'interface. De plus, certaines applications peuvent être perturbées lors de la combinaison de différentes versions de types. En général, pour les packages de types uniquement ou courants, 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 meilleur 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 reçoit ::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 à mettre en œuvre, consultez les pages Interroger les versions et Utiliser les valeurs par défaut.

Objets Parcelable

Lorsque de nouveaux champs sont ajoutés aux éléments parcellables, les anciens clients et serveurs les abandonnent. 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 des valeurs par défaut doivent être spécifiées pour tous les nouveaux champs d'un parcelable.

Les clients ne doivent pas s'attendre à ce que les serveurs utilisent les nouveaux champs, sauf s'ils savent que le serveur implémente la version dans laquelle le champ est défini (voir la section Interroger les versions).

Énumérations et constantes

De même, les clients et les serveurs doivent rejeter ou ignorer les valeurs constantes et les énumérateurs non reconnus, le cas échéant, car d'autres peuvent être ajoutés à l'avenir. Par exemple, un serveur ne doit pas s'arrêter lorsqu'il reçoit un énumérateur qu'il ne connaît pas. Le serveur doit ignorer l'énumérateur ou renvoyer quelque chose pour que le client sache qu'il n'est pas pris en charge dans cette implémentation.

Unions

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. 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 une union définie sur le nouveau champ à un ancien serveur ou s'il s'agit d'un ancien client qui reçoit l'union à partir d'un nouveau serveur.

Gérer plusieurs versions

Un espace de noms de l'éditeur de liens dans Android ne peut avoir qu'une seule version d'une interface aidl spécifique pour éviter que les types aidl générés aient plusieurs définitions. C++ dispose de la règle de définition unique qui ne nécessite qu'une seule définition de chaque symbole.

La compilation Android génère une erreur lorsqu'un module dépend de différentes versions de la même bibliothèque aidl_interface. Le module peut dépendre de ces bibliothèques directement ou indirectement via les dépendances de leurs dépendances. Ces erreurs affichent le graphique des dépendances du module défaillant vers les versions en conflit de la bibliothèque aidl_interface. Toutes les dépendances doivent être mises à jour pour inclure la même version (généralement la dernière) de ces bibliothèques.

Si la bibliothèque d'interface est utilisée par de nombreux modules différents, il peut être utile de créer cc_defaults, java_defaults et rust_defaults pour tout groupe de bibliothèques et de processus qui doivent utiliser la même version. Lorsque vous introduisez une nouvelle version de l'interface, ces valeurs par défaut peuvent être mises à jour et tous les modules qui les utilisent sont mis à jour ensemble, ce qui garantit qu'ils n'utilisent pas différentes versions de l'interface.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

Lorsque des modules aidl_interface importent d'autres modules aidl_interface, cela crée des dépendances supplémentaires qui nécessitent d'utiliser des versions spécifiques ensemble. Cette situation peut devenir difficile à gérer lorsqu'il existe des modules aidl_interface communs importés dans plusieurs modules aidl_interface utilisés ensemble dans les mêmes processus.

aidl_interfaces_defaults permet de conserver une définition des dernières versions des dépendances pour un aidl_interface qui peut être mise à jour à un seul endroit et utilisée par tous les modules aidl_interface qui souhaitent importer cette interface commune.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Développement basé sur des indicateurs

Les interfaces en cours de développement (non figées) ne peuvent pas être utilisées sur les appareils de version, car elles ne sont pas garanties rétrocompatibles.

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. Avec le remplacement, les implémentations doivent également suivre ces comportements. Consultez la section Utiliser des interfaces avec des versions gérées.

Indicateur de build AIDL

L'indicateur qui contrôle ce comportement est RELEASE_AIDL_USE_UNFROZEN défini dans build/release/build_flags.bzl. true signifie que la version non figée de l'interface est utilisée au moment de l'exécution, et false signifie que les bibliothèques des versions non figées se comportent toutes comme leur dernière version figée. Vous pouvez remplacer l'indicateur par true pour le développement local, mais vous devez le rétablir sur false avant la publication. En règle générale, le développement est effectué avec une configuration dont l'indicateur est défini sur true.

Matrice de compatibilité et fichiers manifestes

Les objets d'interface 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 ne ciblent la dernière matrice de compatibilité qu'après le gel des interfaces. Il n'y a donc aucune différence entre 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 pendant le développement. Par conséquent, 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. Pour ce faire, vous pouvez utiliser différents fichiers de matrice de compatibilité pour différentes configurations RELEASE_AIDL_USE_UNFROZEN ou autoriser les deux versions dans un seul fichier de matrice de compatibilité utilisé dans toutes les configurations.

Par exemple, lorsque vous ajoutez une version 4 non figée, utilisez <version>3-4</version>.

Lorsque la version 4 est figée, vous pouvez supprimer la version 3 de la matrice de compatibilité, car la version 4 figée est utilisée lorsque RELEASE_AIDL_USE_UNFROZEN est false.

Fichiers manifestes

Dans Android 15, une modification de libvintf est introduite pour modifier les fichiers manifestes au moment de la compilation en fonction de la valeur de RELEASE_AIDL_USE_UNFROZEN.

Les fichiers manifestes et les fragments de fichier manifeste déclarent la version d'une interface implémentée 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 apportée à 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 fichier manifeste pour chacun de leurs services.

Modifications apportées au 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 à une version antérieure (par exemple, l'appel de nouvelles méthodes non figées renvoie UNKNOWN_TRANSACTION, ou les nouveaux champs parcelable ont leurs valeurs par défaut). Les clients du framework Android doivent être rétrocompatibles avec d'autres versions précédentes, mais il s'agit d'un nouveau détail pour les clients des fournisseurs et les clients des interfaces appartenant à des partenaires.

Modifications apportées à l'implémentation de HAL

La plus grande différence entre le développement HAL et le 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 false. La prise en compte de la rétrocompatibilité dans les implémentations et le code de l'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, vous devez tenir compte de certaines différences subtiles, car vous implémentez désormais deux versions qui utilisent le même code source (la version actuelle non figée).

Exemple: Une interface comporte trois versions figées. L'interface est mise à jour avec une nouvelle méthode. Le client et le service sont tous deux mis à jour pour utiliser la nouvelle bibliothèque version 4. Étant donné que la bibliothèque V4 est basée sur une version non figée de l'interface, elle se comporte comme la dernière version figée, la version 3, lorsque RELEASE_AIDL_USE_UNFROZEN est false, et empêche l'utilisation de la nouvelle méthode.

Lorsque l'interface est figée, toutes les valeurs de RELEASE_AIDL_USE_UNFROZEN utilisent cette version figée, et le code assurant la rétrocompatibilité peut être supprimé.

Lorsque vous appelez des méthodes sur des rappels, vous devez gérer correctement le cas où UNKNOWN_TRANSACTION est renvoyé. Les clients peuvent implémenter deux versions différentes d'un rappel en fonction de la configuration de la version. Vous ne pouvez donc pas supposer que le client envoie la version la plus récente, et de nouvelles méthodes peuvent le renvoyer. Cela ressemble à la façon dont les clients AIDL stables maintiennent la rétrocompatibilité avec les serveurs, comme décrit dans la section Utiliser des interfaces versionnées.

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

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 false, et 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 figée ne peuvent pas être envoyés ni reçus via l'interface.

L'implémentation ne reçoit jamais d'appel pour de nouvelles méthodes de la part de clients lorsque RELEASE_AIDL_USE_UNFROZEN est 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 des indicateurs, vous implémentez deux versions différentes. Vous pouvez donc obtenir la version de l'interface actuelle. Pour ce faire, obtenez la version d'interface de l'objet actuel, par exemple this->getInterfaceVersion() ou les autres méthodes pour my_ver. Pour en savoir plus, consultez la section Interroger la version de l'interface de l'objet distant.

Nouvelles interfaces stables VINTF

Lorsqu'un nouveau package d'interface AIDL est ajouté, il n'y a pas de dernière version figée. Il n'y a donc aucun comportement à utiliser lorsque RELEASE_AIDL_USE_UNFROZEN est défini sur false. N'utilisez pas ces interfaces. Lorsque RELEASE_AIDL_USE_UNFROZEN est false, le Gestionnaire de services n'autorise pas le service à enregistrer l'interface, et les clients ne la trouveront pas.

Vous pouvez ajouter les services de manière conditionnelle en fonction de la valeur de l'indicateur RELEASE_AIDL_USE_UNFROZEN dans le fichier makefile de l'appareil:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

Si le service fait partie d'un processus plus vaste et que vous ne pouvez pas l'ajouter à l'appareil de manière conditionnelle, vous pouvez vérifier si le service est déclaré avec IServiceManager::isDeclared(). S'il est déclaré et que l'enregistrement a échoué, arrêtez 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 le gel de la VINTF, nous ajustons la matrice de compatibilité du framework (FCM) target-level et la PRODUCT_SHIPPING_API_LEVEL de Cuttlefish afin qu'elles 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 appareil de lancement est testé et qu'il 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, ce qui l'oblige à répondre aux exigences logicielles du fournisseur (VSR) de la prochaine version.

Lorsque RELEASE_AIDL_USE_UNFROZEN est false, Cuttlefish dispose des target-level et PRODUCT_SHIPPING_API_LEVEL précédents pour refléter un appareil de publication. Sous Android 14 et versions antérieures, cette différenciation est effectuée avec différentes branches Git qui ne détectent pas la modification apportée à target-level FCM, au niveau d'API de distribution ni à tout autre code ciblant la prochaine version.

Règles de dénomination des modules

Dans Android 11, un module de bibliothèque fictif est automatiquement créé pour chaque combinaison des versions et des backends activés. Pour faire référence à un module de bibliothèque de bouchon spécifique à l'association, n'utilisez pas le nom du module aidl_interface, mais le nom du module de bibliothèque de 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 de pointe de l'arborescence (à figer)
  • 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 est destiné aux applications, et le second à l'utilisation de la plate-forme jusqu'à Android 13. Sous Android 13 et versions ultérieures, n'utilisez que ndk.
    • 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 (dernière version stable)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Basé sur la version de 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 identiques à ceux 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 pas de module de version unstable ni de module sans version 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 d'interface méta

Android 10 ajoute plusieurs méthodes d'interface méta pour l'AIDL stable.

Interroger la version de l'interface de l'objet distant

Les clients peuvent interroger la version et le hachage de l'interface implémentée par l'objet distant, puis comparer les valeurs renvoyées avec celles de l'interface utilisée par le client.

Exemple avec le backend cpp:

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Exemple avec le backend ndk (et ndk_platform) :

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Exemple avec le backend java:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Pour le langage Java, le côté distant DOIT implémenter getInterfaceVersion() et getInterfaceHash() comme suit (super est utilisé à la place de IFoo pour éviter les erreurs de copier-coller). L'annotation @SuppressWarnings("static") peut être nécessaire pour désactiver les avertissements, en fonction de la configuration de javac):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

En effet, les classes générées (IFoo, IFoo.Stub, etc.) sont partagées entre le client et le serveur (par exemple, les classes peuvent se trouver dans le chemin d'accès au boot classpath). Lorsque des classes sont partagées, le serveur est également associé à la dernière version des classes, même s'il a été créé avec une version antérieure 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 comme ci-dessus, le numéro de version de l'interface est intégré au code du serveur (car IFoo.VERSION est un static final int intégré lors de la référence), et la méthode peut donc renvoyer la version exacte avec laquelle le serveur a été compilé.

Gérer les anciennes interfaces

Il est possible qu'un client soit mis à jour avec la version la plus récente d'une interface AIDL, mais que le serveur utilise l'ancienne interface AIDL. Dans ce cas, l'appel d'une méthode sur une ancienne interface renvoie UNKNOWN_TRANSACTION.

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 sur 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 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(...);

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é ou stable

Si vous disposez d'une interface AIDL et d'un code qui l'utilise, suivez les étapes ci-dessous pour convertir l'interface 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. Si ce n'est pas le cas, le package doit être converti.

  2. Convertissez tous les éléments parcellables de votre interface en éléments parcellables stables (les fichiers d'interface eux-mêmes peuvent rester inchangés). Pour ce faire, exprimez leur structure directement dans les fichiers AIDL. Les classes de gestion doivent être réécrites pour utiliser ces nouveaux types. Vous pouvez le faire avant de créer un package aidl_interface (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 le stabiliser (et pas seulement le structurer), il doit également être versionné. Pour en savoir plus, consultez la section Interfaces de gestion des versions.