Guía de estilo de AIDL

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

El AIDL se puede usar para definir una API cuando las apps necesitan interactuar entre sí en un proceso en segundo plano o deben interactuar con el sistema. Si quieres obtener más información para desarrollar interfaces de programación en apps con AIDL, consulta Lenguaje de definición de la interfaz de Android (AIDL). Si deseas ver ejemplos del AIDL en la práctica, consulta AIDL para HALs 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 servidor de la API (por ejemplo, en un tren de línea principal), 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 información y detalles sobre los tipos de cambios permitidos, 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 conocer su semántica.
  • Documenta el significado semántico de las enumeraciones y las constantes.
  • Documenta lo que no esté claro para el implementador.
  • Proporciona ejemplos cuando corresponda.

2. Carcasa

Usa el formato de mayúsculas mediales superior para tipos y el de mayúsculas y minúsculas inferior para métodos, campos y argumentos. Por ejemplo, MyParcelable para un tipo parcelable y anArgument para un argumento. En el caso de las siglas, considera el término como una palabra (NFC -> Nfc).

[-Wconst-name] Los valores y las 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. Cómo evitar una interfaz grande con “objetos” basados en ID

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 métodos bidireccionales

[-Wmixed-oneway] No mezcles métodos unidireccionales con métodos no unidireccionales, ya que eso 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. Cómo evitar mostrar 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. Para obtener más información, consulta la sección de manejo de errores de los backends de AIDL.

5. Arreglos como parámetros de salida considerados dañinos

[-Wout-array] Los métodos que tienen parámetros de salida de array, como void foo(out String[] ret), suelen ser incorrectos porque el cliente debe declarar y asignar el tamaño del array de salida en Java. Por lo tanto, el servidor no puede elegir el tamaño de la salida del array. Este comportamiento no deseado sucede debido al funcionamiento de los arrays en Java (no se pueden reasignar). En su lugar, es preferible usar APIs como String[] foo().

6. Evita los parámetros de entrada/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 que no son de 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 como nulo (en C++, configurarlo como std::nullopt), pero el cliente Java no puede leerlo como nulo.

Objetos parcelables estructurados

1. Cuándo se activa

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

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

parcelable User {
    String username;
}

De modo que, en el futuro, puedas ampliarlo de la siguiente manera:

parcelable User {
    String username;
    int id;
}

2. Cómo proporcionar valores predeterminados de forma explícita

[-Wexplícito-default, -Wenum- direct-default] Proporciona valores predeterminados explícitos para los campos.

Parcelables no estructurados

1. Cuándo se activa

Los objetos parcelables no estructurados están disponibles en Java con @JavaOnlyStableParcelable y en el backend del NDK con @NdkOnlyStableParcelable. Por lo general, estos son objetos 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.

Las enumeraciones 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 del proveedor ascendente. 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. Evitar 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 esto con enum_range<>. Para Rust, usa enum_values(). En Java, todavía no existe 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 el uso de FileDescriptor como argumento o el valor de retorno de un método de interfaz de AIDL. En especial, cuando el AIDL se implementa en Java, es posible que esto provoque la fuga del descriptor de archivo, a menos que se maneje con cuidado. En esencia, si aceptas un FileDescriptor, debes cerrarlo de forma manual cuando ya no se use.

En el caso de los backends nativos, estás seguro porque FileDescriptor se asigna a unique_fd, que se puede cerrar automáticamente. Sin embargo, independientemente del lenguaje de backend que uses, es sensato 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 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;