Las prácticas recomendadas que se describen aquí sirven como guía para desarrollar interfaces de AIDL de manera eficaz y con atención a la flexibilidad de la interfaz, en especial cuando se usa AIDL para definir una API o interactuar con superficies de API.
Se puede usar AIDL para definir una API cuando las apps necesitan interactuar entre sí en un proceso en segundo plano o con el sistema. Para obtener más información sobre el desarrollo de interfaces de programación en apps con AIDL, consulta Android Interface Definition Language (AIDL). Para ver ejemplos prácticos del AIDL, consulta AIDL para HALs y AIDL estable.
Control de versiones
Cada instantánea compatible con versiones anteriores de una API de AIDL corresponde a una versión.
Para tomar una instantánea, ejecuta m <module-name>-freeze-api. Cada vez que se lanza un cliente o servidor de la API (por ejemplo, en una versión de Mainline), debes tomar una instantánea y crear una versión nueva. En el caso de las APIs de sistema a proveedor, esto debería ocurrir con la revisión anual de la plataforma.
Para obtener más detalles e información sobre el tipo de cambios que se permiten, consulta Control de versiones de interfaces.
Lineamientos de diseño de APIs
General
1. Documenta todo
- Documenta cada método para su semántica, argumentos, uso de excepciones integradas, excepciones específicas del servicio y valor de devolución.
- Documenta cada interfaz para su semántica.
- Documenta el significado semántico de las enumeraciones y las constantes.
- Documenta todo lo que pueda no estar claro para un implementador.
- Proporciona ejemplos cuando sea pertinente.
2. Carcasa
Usa mayúsculas al principio de cada palabra (excepto la primera) para los tipos y minúsculas al principio de cada palabra (excepto la primera) para los métodos, los campos y los argumentos. Por ejemplo, MyParcelable para un tipo parcelable y anArgument para un argumento. En el caso de las siglas, considera que son una palabra (NFC -> Nfc).
[-Wconst-name] Los valores y las constantes de enumeración deben ser ENUM_VALUE y CONSTANT_NAME.
Interfaces
1. Nombre
[-Winterface-name] El nombre de una interfaz debe comenzar con I, como IFoo.
2. Evita una interfaz grande con "objetos" basados en IDs
Prefiere las subinterfaces cuando hay muchas llamadas relacionadas con una API específica. Esto proporciona los siguientes beneficios:
- Facilita la comprensión del código del cliente o del servidor
- Simplifica el ciclo de vida de los objetos
- Aprovecha el hecho de que los vinculadores no se pueden falsificar.
No se recomienda: Una sola interfaz grande con objetos basados en IDs
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
}
Recomendación: Interfaces individuales
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. No mezcles métodos unidireccionales con bidireccionales
[-Wmixed-oneway] No mezcles métodos unidireccionales con métodos no unidireccionales, ya que esto dificulta la comprensión del modelo de subprocesos para clientes y servidores. Específicamente, cuando lees el código del cliente de una interfaz en particular, debes buscar para cada método si ese método se bloqueará o no.
4. Evita devolver códigos de estado
Los métodos deben evitar los códigos de estado como valores de retorno, ya que todos los métodos de AIDL tienen un código de retorno de estado implícito. Consulta ServiceSpecificException o EX_SERVICE_SPECIFIC. Por convención, estos valores se definen como constantes en una interfaz de AIDL. Encontrarás información más detallada en la sección de administración de errores de los backends de AIDL.
5. Los arrays como parámetros de salida se consideran dañinos
[-Wout-array] Por lo general, los métodos que tienen parámetros de salida de array, como void foo(out String[] ret), son malos porque el cliente debe declarar y asignar el tamaño del array de salida en Java, por lo que el servidor no puede elegir el tamaño de la salida del array. Este comportamiento no deseado se produce debido a la forma en que funcionan los arrays en Java (no se pueden reasignar). En su lugar, prefiere APIs como String[] foo().
6. Evita los parámetros de entrada y salida
[-Winout-parameter] Esto puede confundir a los clientes, ya que incluso los parámetros in parecen parámetros out.
7. Evita los parámetros @nullable que no sean de array y que sean de entrada y salida
[-Wout-nullable] Dado que el backend de Java no controla la anotación @nullable, mientras que otros backends sí lo hacen, out/inout @nullable T puede generar un comportamiento incoherente en los diferentes backends. Por ejemplo, los backends que no son de Java pueden establecer un parámetro @nullable externo como nulo (en C++, se establece como std::nullopt), pero el cliente de Java no puede leerlo como nulo.
Objetos Parcelables estructurados
1. Cuándo debe utilizarse
Usa objetos Parcelables estructurados cuando tengas varios tipos de datos para enviar.
O bien, cuando tienes un solo tipo de datos, pero esperas que deberás extenderlo en el futuro. Por ejemplo, no uses String username. Usa un objeto Parcelable extensible, como el siguiente:
parcelable User {
String username;
}
Para que, en el futuro, puedas extenderlo de la siguiente manera:
parcelable User {
String username;
int id;
}
2. Proporciona valores predeterminados de forma explícita
[-Wexplicit-default, -Wenum-explicit-default] Proporciona valores predeterminados explícitos para los campos.
Objetos Parcelables no estructurados
1. Cuándo debe utilizarse
Los objetos Parcelable no estructurados están disponibles en Java con @JavaOnlyStableParcelable y en el backend del NDK con @NdkOnlyStableParcelable. Por lo general, se trata de objetos Parcelable antiguos y existentes que no se pueden estructurar.
Constantes y enumeraciones
1. Los campos de bits deben usar campos constantes
Los campos de bits deben usar campos constantes (por ejemplo, const int FOO = 3; en una interfaz).
2. Los enums deben ser conjuntos cerrados.
Los enums deben ser conjuntos cerrados. Nota: Solo el propietario de la interfaz puede agregar elementos de enumeración. Si los proveedores o los OEM necesitan extender estos campos, se requiere un mecanismo alternativo. Siempre que sea posible, se debe preferir la funcionalidad del proveedor de upstream. Sin embargo, en algunos casos, se pueden permitir valores personalizados del proveedor (aunque los proveedores deben tener un mecanismo para versionar esto, tal vez AIDL en sí, no deberían poder entrar en conflicto entre sí y estos valores no deberían exponerse a apps de terceros).
3. Evita valores como "NUM_ELEMENTS".
Dado que los enums tienen versiones, se deben evitar los valores que indican cuántos valores hay. En C++, esto se puede solucionar con enum_range<>. En Rust, usa enum_values(). En Java, aún no hay una solución.
No se recomienda: Usar valores numerados
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Evita los prefijos y sufijos redundantes
[-Wredundant-name] Evita los prefijos y sufijos redundantes o repetitivos en las constantes y los enumeradores.
No se recomienda: Usar un prefijo redundante
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Recomendado: Asignar un nombre directamente a la enumeración
enum MyStatus {
GOOD,
BAD
}
FileDescriptor
[-Wfile-descriptor] Se desaconseja el uso de FileDescriptor como argumento o valor de devolución de un método de interfaz de AIDL. En especial, cuando el AIDL se implementa en Java, esto puede provocar una pérdida de descriptores de archivo, a menos que se controle con cuidado. Básicamente, si aceptas un FileDescriptor, debes cerrarlo de forma manual cuando ya no se use.
En el caso de los backends nativos, no hay problemas porque FileDescriptor se asigna a unique_fd, que se puede cerrar automáticamente. Sin embargo, independientemente del lenguaje de backend que uses, es recomendable NO usar FileDescriptor en absoluto, ya que esto limitará tu libertad para cambiar el lenguaje de backend en el futuro.
En su lugar, usa ParcelFileDescriptor, que se puede cerrar automáticamente.
Unidades de la variable
Asegúrate de que las unidades de las variables se incluyan en el nombre para que sus unidades estén bien definidas y se comprendan sin necesidad de consultar la documentación.
Ejemplos
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
Las marcas de tiempo deben indicar su referencia
Las marcas de tiempo (de hecho, todas las unidades) deben indicar claramente sus unidades y puntos de referencia.
Ejemplos
/**
* 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;