Les bonnes pratiques décrites ici servent de guide pour développer des interfaces AIDL efficacement et en veillant à 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 interagir entre elles dans un processus en arrière-plan ou avec le système. Pour en savoir plus sur le développement d'interfaces de programmation dans les applications avec AIDL, consultez la section Langage de définition d'interface Android (AIDL). Pour obtenir des exemples d'AIDL en pratique, consultez AIDL pour les HAL et AIDL stable.
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 Mainline), vous devez créer un instantané et créer une nouvelle version. Pour les API système-fournisseur, cela devrait se produire avec la révision annuelle de la plate-forme.
Pour en savoir plus sur les types de modifications autorisés, consultez la section Interfaces de gestion des versions.
Consignes de conception des API
Général
1. Tout documenter
- Documentez chaque méthode pour sa sémantique, ses arguments, l'utilisation d'exceptions intégrées, les exceptions spécifiques au service et la valeur de retour.
- Documentez la sémantique de chaque interface.
- Documentez la signification sémantique des énumérations et des constantes.
- Documentez tout ce qui pourrait être peu clair pour un intégrateur.
- Fournissez des exemples, le cas échéant.
2. Boîtier
Utilisez la casse camel majuscule pour les types et la casse camel minuscule 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-les comme des mots (NFC
-> Nfc
).
[-Wconst-name] Les valeurs et constantes d'énumération doivent être ENUM_VALUE
et CONSTANT_NAME
Interfaces
1. Dénominations
[-Wnom-interface] Le nom d'une interface doit commencer par I
, comme IFoo
.
2. Éviter une interface volumineuse avec des "objets" basés sur des ID
Privilégiez les sous-interfaces lorsque de nombreux appels sont associés à une API spécifique. Cela offre les avantages suivants:
- Facilite la compréhension du code client ou serveur
- Simplifie le cycle de vie des objets
- Exploite le fait que les liaisons ne peuvent pas être falsifiées.
Non recommandé:interface unique et volumineuse avec des objets basés sur des ID
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
}
Recommandation: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 et bidirectionnelles
[-Wmixed-oneway] Ne mélangez pas les méthodes à sens unique et les méthodes non à sens unique, car cela complique la compréhension du modèle de threads pour les clients et les serveurs. Plus précisément, lorsque vous lisez le code client d'une interface particulière, vous devez rechercher pour chaque méthode si elle bloque ou non.
4. Éviter de renvoyer des codes d'état
Les méthodes doivent éviter d'utiliser des codes d'état comme valeurs de retour, car toutes les méthodes AIDL ont un code de retour d'état implicite. Consultez ServiceSpecificException
ou EX_SERVICE_SPECIFIC
. Par convention, ces valeurs sont définies comme constantes dans une interface AIDL. Pour en savoir plus, consultez la section Traitement des erreurs dans les backends AIDL.
5. Les tableaux en tant que paramètres de sortie sont considérés comme dangereux
[-Wout-array] Les méthodes comportant 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. Par conséquent, la taille de la sortie du tableau ne peut pas être choisie par le serveur. Ce comportement indésirable se produit en raison du fonctionnement des tableaux en Java (ils ne peuvent pas être réalloués). Privilégiez plutôt des API telles que String[] foo()
.
6. Éviter les paramètres inout
[-Winout-parameter] Cela peut prêter à confusion pour les clients, car même les paramètres in
ressemblent à des paramètres out
.
7. Éviter les paramètres non array @nullable de sortie et d'entrée
[-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 @nullable
de sortie 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 l'utiliser
Utilisez des parcelables structurés lorsque vous devez envoyer plusieurs types de données.
Ou lorsque vous disposez d'un seul type de données, mais que vous prévoyez de l'étendre à l'avenir. Par exemple, n'utilisez pas String username
. Utilisez un parcelable extensible, comme suit:
parcelable User {
String username;
}
Vous pourrez ainsi l'étendre ultérieurement, comme suit:
parcelable User {
String username;
int id;
}
2. Fournir des valeurs par défaut de manière explicite
[-Wexplicit-default, -Wenum-explicit-default] Fournit des valeurs par défaut explicites pour les champs.
Parcelles non structurées
1. Quand l'utiliser
Les éléments parcelables non structurés sont disponibles en Java avec @JavaOnlyStableParcelable
et dans le backend du NDK avec @NdkOnlyStableParcelable
. Il s'agit généralement d'anciens éléments parcelables existants qui ne peuvent pas être structurés.
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 d'énumération. Si les fournisseurs ou les OEM doivent étendre ces champs, un mécanisme alternatif est nécessaire. Dans la mesure du possible, il est préférable d'utiliser la fonctionnalité d'approvisionnement en amont du fournisseur. Toutefois, dans certains cas, les valeurs de fournisseurs personnalisées peuvent être autorisées (bien que les fournisseurs doivent mettre en place un mécanisme pour mettre à jour ces valeurs, peut-être AIDL lui-même, elles ne doivent pas entrer en conflit les unes avec les autres, et ces valeurs ne doivent pas être exposées aux 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++, vous pouvez contourner ce problème avec enum_range<>
. Pour Rust, utilisez enum_values()
. Il n'existe pas encore de solution pour Java.
Option déconseillée:utiliser des valeurs numérotées
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Éviter les préfixes et suffixes redondants
[-Wnom-redundant] Évitez les préfixes et suffixes redondants ou répétitifs dans les constantes et les énumérateurs.
Option déconseillée:utiliser un préfixe redondant
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Recommandation:Nommer directement l'énumération
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-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 entraîner une fuite de descripteur de fichier, sauf si elle est gérée avec soin. En gros, 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
est mappé sur unique_fd
, qui peut être fermé automatiquement. Toutefois, quel que soit le langage de backend que vous utiliseriez, il est judicieux de NE PAS utiliser FileDescriptor
du tout, car cela limiterait votre liberté de modifier le langage de backend à l'avenir.
Utilisez plutôt ParcelFileDescriptor
, qui peut se fermer automatiquement.
Unités variables
Assurez-vous que les unités de la variable sont incluses dans le nom afin qu'elles soient bien définies et comprises sans avoir à consulter 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 codes temporels doivent indiquer leur référence
Les codes temporels (et 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;