Guide de style AIDL

Les meilleures pratiques décrites ici servent de guide pour développer des interfaces AIDL de manière efficace et en accordant une attention particulière à la flexibilité de l'interface, en particulier lorsque AIDL est utilisé pour définir une API ou interagir avec des surfaces d'API.

AIDL peut être utilisé pour définir une API lorsque les applications doivent s'interfacer entre elles dans un processus en arrière-plan ou doivent s'interfacer avec le système. Pour plus d'informations sur le développement d'interfaces de programmation dans des applications avec AIDL, consultez Android Interface Definition Language (AIDL) . Pour des exemples d'AIDL en pratique, voir AIDL for HALs et Stable AIDL .

Gestion des versions

Chaque instantané rétrocompatible d'une API AIDL correspond à une version. Pour prendre un instantané, exécutez m <module-name>-freeze-api . Chaque fois qu'un client ou un serveur de l'API est publié (par exemple dans un train principal), vous devez prendre un instantané et créer une nouvelle version. Pour les API système-fournisseur, cela devrait se produire lors de la révision annuelle de la plateforme.

Pour plus de détails et d’informations sur le type de modifications autorisées, consultez Interfaces de gestion des versions .

Directives de conception d'API

Général

1. Documentez tout

  • Documentez chaque méthode pour sa sémantique, ses arguments, l'utilisation des exceptions intégrées, les exceptions spécifiques au service et la valeur de retour.
  • Documentez chaque interface pour sa sémantique.
  • Documentez la signification sémantique des énumérations et des constantes.
  • Documentez tout ce qui pourrait ne pas être clair pour un exécutant.
  • Fournissez des exemples le cas échéant.

2. Boîtier

Utilisez la casse supérieure Camel pour les types et la casse inférieure Camel pour les méthodes, les champs et les arguments. Par exemple, MyParcelable pour un type parcelable et anArgument pour un argument. Pour les acronymes, considérez l'acronyme comme un mot ( NFC -> Nfc ).

[-Wconst-name] Les valeurs et constantes d'énumération doivent être ENUM_VALUE et CONSTANT_NAME

Interfaces

1. Nommer

[-Winterface-name] Un nom d'interface doit commencer par I like IFoo .

2. Évitez les grandes interfaces avec des "objets" basés sur l'identifiant

Préférez les sous-interfaces lorsqu'il y a de nombreux appels liés à une API spécifique. Cela offre les avantages suivants : - Rend le code client/serveur plus facile à comprendre. - Rend le cycle de vie des objets plus simple. - Bénéficie du fait que les classeurs sont infalsifiables.

Non recommandé : une interface unique et volumineuse avec des objets basés sur l'identifiant

interface IManager {
   int getFooId();
   void beginFoo(int id); // clients in other processes can guess an ID
   void opFoo(int id);
   void recycleFoo(int id); // ownership not handled by type
}

Recommandé : sous-interfaces individuelles

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. Ne mélangez pas les méthodes unidirectionnelles avec les méthodes bidirectionnelles

[-Wmixed-oneway] Ne mélangez pas de méthodes unidirectionnelles avec des méthodes non-unidirectionnelles, car cela complique la compréhension du modèle de thread pour les clients et les serveurs. Plus précisément, lors de la lecture du code client d'une interface particulière, vous devez rechercher pour chaque méthode si cette méthode bloquera ou non.

4. Évitez de renvoyer les codes de statut

Les méthodes doivent éviter les codes d'état comme valeurs de retour, puisque toutes les méthodes AIDL ont un code de retour d'état implicite. Voir ServiceSpecificException ou EX_SERVICE_SPECIFIC . Par convention, ces valeurs sont définies comme constantes dans une interface AIDL. Des informations plus détaillées se trouvent dans la section Gestion des erreurs de AIDL Backends .

5. Tableaux comme paramètres de sortie considérés comme nuisibles

[-Wout-array] Les méthodes ayant des paramètres de sortie de tableau, comme void foo(out String[] ret) sont généralement mauvaises car la taille du tableau de sortie doit être déclarée et allouée par le client en Java, et donc la taille de la sortie du tableau ne peut pas être choisi par le serveur. Ce comportement indésirable se produit en raison du fonctionnement des tableaux en Java (ils ne peuvent pas être réaffectés). Préférez plutôt les API comme String[] foo() .

6. Évitez les paramètres inout

[-Winout-parameter] Cela peut dérouter les clients car même les paramètres in ressemblent aux paramètres out .

7. Évitez les paramètres non-tableaux out/inout @nullable

[-Wout-nullable] Étant donné que le backend Java ne gère pas l'annotation @nullable contrairement aux autres backends, out/inout @nullable T peut entraîner un comportement incohérent entre les backends. Par exemple, les backends non Java peuvent définir un paramètre out @nullable sur null (en C++, en le définissant sur std::nullopt ), mais le client Java ne peut pas le lire comme null.

Parcelables structurés

1. Quand utiliser

Utilisez des colis structurés où vous avez plusieurs types de données à envoyer.

Ou, lorsque vous disposez actuellement d’un seul type de données mais que vous pensez devoir l’étendre à l’avenir. Par exemple, n'utilisez pas String username . Utilisez un colis extensible, comme celui-ci :

parcelable User {
    String username;
}

Pour que, à l’avenir, vous puissiez l’étendre comme suit :

parcelable User {
    String username;
    int id;
}

2. Fournissez explicitement les valeurs par défaut

[-Wexplicit-default, -Wenum-explicit-default] Fournit des valeurs par défaut explicites pour les champs.

Parcelables non structurés

1. Quand utiliser

Les colisables non structurés sont actuellement disponibles en Java avec @JavaOnlyStableParcelable et dans le backend NDK avec @NdkOnlyStableParcelable . Il s’agit généralement de parcelles anciennes et existantes qui ne peuvent pas être facilement structurées.

Constantes et énumérations

1. Les champs de bits doivent utiliser des champs constants

Les champs de bits doivent utiliser des champs constants (par exemple const int FOO = 3; dans une interface).

2. Les énumérations doivent être des ensembles fermés.

Les énumérations doivent être des ensembles fermés. Remarque : seul le propriétaire de l'interface peut ajouter des éléments enum. Si les fournisseurs ou les OEM doivent étendre ces champs, un mécanisme alternatif est nécessaire. Dans la mesure du possible, la fonctionnalité du fournisseur en amont doit être privilégiée. Cependant, dans certains cas, les valeurs personnalisées des fournisseurs peuvent être autorisées (bien que les fournisseurs devraient avoir un mécanisme en place pour gérer cela, peut-être AIDL lui-même, ils ne devraient pas pouvoir entrer en conflit les uns avec les autres, et ces valeurs ne devraient pas être exposé à des applications tierces).

3. Évitez les valeurs telles que "NUM_ELEMENTS"

Étant donné que les énumérations sont versionnées, les valeurs qui indiquent le nombre de valeurs présentes doivent être évitées. En C++, cela peut être contourné avec enum_range<> . Pour Rust, utilisez enum_values() . En Java, il n'y a pas encore de solution.

Non recommandé : utilisation de valeurs numérotées

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Évitez les préfixes et suffixes redondants

[-Wredundant-name] Évitez les préfixes et suffixes redondants ou répétitifs dans les constantes et les énumérateurs.

Non recommandé : utilisation d'un préfixe redondant

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Recommandé : nommer directement l'énumération

enum MyStatus {
    GOOD,
    BAD
}

Descripteur de fichier

[-Wfile-descriptor] L'utilisation de FileDescriptor comme argument ou comme valeur de retour d'une méthode d'interface AIDL est fortement déconseillée. En particulier, lorsque l'AIDL est implémenté en Java, cela peut provoquer une fuite du descripteur de fichier à moins d'être traité avec soin. Fondamentalement, si vous acceptez un FileDescriptor , vous devez le fermer manuellement lorsqu'il n'est plus utilisé.

Pour les backends natifs, vous êtes en sécurité car FileDescriptor correspond à unique_fd qui peut être fermé automatiquement. Mais quelle que soit la langue du backend que vous utiliseriez, il est sage de NE PAS utiliser FileDescriptor du tout, car cela limitera votre liberté de changer de langue du backend à l'avenir.

Utilisez plutôt ParcelFileDescriptor , qui peut être fermé automatiquement.

Unités variables

Assurez-vous que les unités variables sont incluses dans le nom afin que leurs unités soient bien définies et comprises sans qu'il soit nécessaire de référencer la documentation.

Exemples

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Les horodatages doivent indiquer leur référence

Les horodatages (en fait, toutes les unités !) doivent indiquer clairement leurs unités et leurs points de référence.

Exemples

/**
 * Time since device boot in milliseconds
 */
long timestampMs;

/**
 * UTC time received from the NTP server in units of milliseconds
 * since January 1, 1970
 */
long utcTimeMs;