Guía de estilo de AIDL

Las prácticas recomendadas que se describen aquí sirven como guía para desarrollar interfaces AIDL de manera eficaz y con atención a la flexibilidad de la interfaz, en particular, cuando se usa AIDL para definir una API o interactuar con las plataformas de API.

AIDL se puede usar 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 Lenguaje de definición de la interfaz de Android (AIDL). Para ver ejemplos de AIDL en la práctica, consulta AIDL para HAL y AIDL estable.

Control de versiones

Cada instantánea retrocompatible 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 un servidor de la API (por ejemplo, en un tren 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 y más información sobre el tipo de cambios que se permiten, consulta Interfaces de control de versiones.

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 que se muestra.
  • Documenta cada interfaz para su semántica.
  • Documenta el significado semántico de las enums y las constantes.
  • Documenta lo que no esté claro para el implementador.
  • Proporciona ejemplos cuando sea relevante.

2. Carcasa

Usa mayúsculas para los tipos y minúsculas 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 el acrónimo es una palabra (NFC -> Nfc).

[-Wconst-name] Los valores y constantes de enum 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.

Es preferible usar 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 que no se pueden falsificar Binders.

No se recomienda: Una sola interfaz grande con objetos basados en 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
}

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 complica la comprensión del modelo de subprocesos para los clientes y los servidores. Específicamente, cuando leas el código del cliente de una interfaz en particular, debes buscar cada método si ese método se bloqueará o no.

4. Evita mostrar códigos de estado

Los métodos deben evitar los códigos de estado como valores que se muestran, ya que todos los métodos de AIDL tienen un código de estado implícito. Consulta ServiceSpecificException o EX_SERVICE_SPECIFIC. Por convención, estos valores se definen como constantes en una interfaz de AIDL. Puedes encontrar información más detallada en la sección de manejo de errores de los backends de AIDL.

5. Los arrays como parámetros de salida se consideran dañinos

[-Wout-array] Los métodos que tienen parámetros de salida de array, como void foo(out String[] ret), suelen ser 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 ocurre 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 porque incluso los parámetros in se parecen a los parámetros out.

7. Evita los parámetros no array @nullable de entrada y salida

[-Wout-nullable] Como el backend de Java no controla la anotación @nullable, mientras que otros lo hacen, out/inout @nullable T puede generar un comportamiento incoherente entre los backends. Por ejemplo, los backends que no son de Java pueden establecer un parámetro @nullable de salida como nulo (en C++, configurarlo como std::nullopt), pero el cliente de Java no puede leerlo como nulo.

Elementos parcelables estructurados

1. Cuándo debe utilizarse

Usa elementos parcelables estructurados cuando tengas varios tipos de datos para enviar.

O bien, cuando tienes un solo tipo de datos, pero esperas necesitar ampliarlo en el futuro. Por ejemplo, no uses String username. Usa un elemento parcelable extensible, como el siguiente:

parcelable User {
    String username;
}

Para que, en el futuro, puedas ampliarlo, haz lo siguiente:

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.

Parcelables no estructurados

1. Cuándo debe utilizarse

Los elementos parcelables no estructurados están disponibles en Java con @JavaOnlyStableParcelable y en el backend de NDK con @NdkOnlyStableParcelable. Por lo general, estos son elementos parcelables 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. Las enumeraciones 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 ampliar estos campos, se necesita un mecanismo alternativo. Siempre que sea posible, se debe preferir la funcionalidad de proveedores upstream. Sin embargo, en algunos casos, se pueden permitir valores de proveedores personalizados (aunque los proveedores deben tener un mecanismo implementado para crear versiones de esto, tal vez AIDL mismo, 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 las enumeraciones tienen control de versiones, se deben evitar los valores que indican cuántos valores están presentes. En C++, se puede solucionar con enum_range<>. Para 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: Nombra la enumeración directamente.

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] No se recomienda usar FileDescriptor como argumento o el valor que se muestra de un método de interfaz de AIDL. En especial, cuando el AIDL se implementa en Java, esto puede provocar una filtración de descriptores de archivos, a menos que se maneje 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, estás protegido 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, 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 variables

Asegúrate de que las unidades variables se incluyan en el nombre para que estén bien definidas y se entiendan sin necesidad de hacer referencia a 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;