Guia de estilo da AIDL

As práticas recomendadas descritas aqui servem como um guia para desenvolver interfaces da AIDL de forma eficaz e com atenção à flexibilidade da interface, especialmente quando a AIDL é usada para definir uma API ou interagir com as superfícies dela.

A AIDL pode ser usada para definir uma API quando os apps precisam interagir uns com os outros em um processo em segundo plano ou com o sistema. Para mais informações sobre como desenvolver interfaces de programação em apps com AIDL, consulte Linguagem de definição de interface do Android (AIDL). Para conferir exemplos práticos, consulte AIDL para HALs e AIDL estável.

Controle de versões

Cada snapshot compatível com versões anteriores de uma API AIDL corresponde a uma versão. Para criar um snapshot, execute m <module-name>-freeze-api. Sempre que um cliente ou servidor da API é lançado (por exemplo, em um trem Mainline), você precisa capturar um snapshot e criar uma nova versão. Para APIs de sistema para fornecedor, isso precisa acontecer com a revisão anual da plataforma.

Para ver mais detalhes e informações sobre os tipos de mudanças permitidas, consulte Interfaces de controle de versões.

Diretrizes de design de API

Geral

1. Documente tudo

  • Documente todos os métodos para semântica, argumentos, uso de exceções integradas, exceções específicas de serviço e valor de retorno.
  • Documente cada interface para verificar a semântica dela.
  • Documenta o significado semântico de tipos enumerados e constantes.
  • Documente tudo o que não estiver claro para um implementador.
  • Forneça exemplos quando for relevante.

2. Compartimento

Use letras maiúsculas de camel para tipos e maiúsculas de minúsculas para métodos, campos e argumentos. Por exemplo, MyParcelable para um tipo parcelable e anArgument para um argumento. Use acrônimos como uma palavra (NFC -> Nfc).

[-Wconst-name] Os valores e constantes de tipo enumerado precisam ser ENUM_VALUE e CONSTANT_NAME

Interfaces

1. Nomeação

[-Winterface-name] Um nome de interface precisa começar com I, assim como IFoo.

2. Evite interfaces grandes com "objetos" baseados em ID.

Prefira subinterfaces quando há muitas chamadas relacionadas a uma API específica. Isso oferece os seguintes benefícios:

  • Torna o código do cliente ou servidor mais fácil de entender
  • Simplifica o ciclo de vida dos objetos
  • Aproveita as pastas por não serem falsificadas.

Não recomendado:uma interface única e grande com objetos baseados em 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
}

Recomendado:interfaces individuais

interface IManager {
    IFoo getFoo();
}

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

3. Não misture métodos unidirecionais e bidirecionais

[-Wmixed-oneway] Não misture métodos unidirecionais com outros métodos, porque isso dificulta a compreensão do modelo de linha de execução para clientes e servidores. Especificamente, ao ler o código do cliente de uma interface específica, você precisa procurar cada método se ele for bloqueado ou não.

4. Evitar retornar códigos de status

Os métodos precisam evitar códigos de status como valores de retorno, já que todos os métodos AIDL têm um código de retorno de status implícito. Consulte ServiceSpecificException ou EX_SERVICE_SPECIFIC. Por convenção, esses valores são definidos como constantes em uma interface AIDL. Veja informações mais detalhadas na seção "Tratamento de erros" de back-ends da AIDL.

5. Matrizes como parâmetros de saída considerados nocivos

[-Wout-array] Métodos que têm parâmetros de saída da matriz, como void foo(out String[] ret), geralmente são ruins porque o tamanho da matriz de saída precisa ser declarado e alocado pelo cliente em Java. Portanto, o tamanho da saída da matriz não pode ser escolhido pelo servidor. Esse comportamento indesejado acontece devido à forma como as matrizes funcionam em Java (elas não podem ser realocadas). Em vez disso, prefira APIs como String[] foo().

6. Evitar parâmetros de entrada

[-Winout-parameter] Isso pode confundir os clientes porque até mesmo os parâmetros in se parecem com parâmetros out.

7. Evitar a saída e a entrada de parâmetros não @nullable que não são de matriz

[-Wout-nullable] Como o back-end Java não processa a anotação @nullable enquanto outros back-ends fazem, out/inout @nullable T pode gerar um comportamento inconsistente entre os back-ends. Por exemplo, back-ends não Java podem definir um parâmetro @nullable como nulo (em C++, definindo-o como std::nullopt), mas o cliente Java não pode lê-lo como nulo.

Parcelables estruturados

1. Quando usar

Use parcelables estruturados quando você tem vários tipos de dados para enviar.

Ou quando você tem um único tipo de dados, mas acha que precisará estendê-lo no futuro. Por exemplo, não use String username. Use um parcelável extensível, como este:

parcelable User {
    String username;
}

Para que, no futuro, você possa estendê-lo da seguinte maneira:

parcelable User {
    String username;
    int id;
}

2. Fornecer os padrões explicitamente

[-Wexplicit-default, -Wenum-explicit-default] Fornece padrões explícitos para os campos.

Parcelables não estruturados

1. Quando usar

Os parcelables não estruturados estão disponíveis em Java com @JavaOnlyStableParcelable e no back-end do NDK com @NdkOnlyStableParcelable. Normalmente, são parcelables antigos e atuais que não podem ser estruturados.

Constantes e tipos enumerados

1. Bitfields precisam usar campos constantes

Os Bitfields precisam usar campos de constantes (por exemplo, const int FOO = 3; em uma interface).

2. Os tipos enumerados precisam ser conjuntos fechados.

Os tipos enumerados precisam ser conjuntos fechados. Observação: somente o proprietário da interface pode adicionar elementos de tipo enumerado. Se os fornecedores ou OEMs precisarem estender esses campos, será necessário usar um mecanismo alternativo. Sempre que possível, a funcionalidade do fornecedor de upstreaming é preferível. No entanto, em alguns casos, os valores personalizados de fornecedor podem ser permitidos, embora os fornecedores precisem ter um mecanismo para a versão desses valores, talvez a própria AIDL. Eles não podem entrar em conflito entre si e esses valores não podem ser expostos a apps de terceiros.

3. Evite valores como "NUM_ELEMENTS"

Como os tipos enumerados têm controle de versões, evite valores que indicam quantos valores estão presentes. Em C++, isso pode ser resolvido com enum_range<>. Para o Rust, use enum_values(). Em Java, ainda não existe uma solução.

Não recomendado:usar valores numerados

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

4. Evitar prefixos e sufixos redundantes

[-Wredundant-name] Evite prefixos e sufixos redundantes ou repetitivos em constantes e enumeradores.

Não recomendado:usar um prefixo redundante

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Recomendado:nomear diretamente o tipo enumerado

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-Descriptor] Não é recomendável usar FileDescriptor como argumento ou o valor de retorno de um método de interface AIDL. Especialmente quando a AIDL é implementada em Java, isso pode causar vazamento do descritor de arquivo se não for cuidadoamente processado. Basicamente, se você aceitar um FileDescriptor, será necessário fechá-lo manualmente quando ele não for mais usado.

Para back-ends nativos, você é seguro porque FileDescriptor mapeia para unique_fd, que é fechado automaticamente. Mas, independentemente da linguagem de back-end que você usaria, é aconselhável NÃO usar FileDescriptor, porque isso limitará sua liberdade para alterar a linguagem de back-end no futuro.

Em vez disso, use ParcelFileDescriptor, que pode ser fechado automaticamente.

Unidades variáveis

Verifique se as unidades variáveis estão incluídas no nome para que elas sejam bem definidas e compreendidas sem a necessidade de documentação de referência.

Exemplos

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

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

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

Os carimbos de data/hora precisam indicar a referência

Os carimbos de data/hora (na verdade, todas as unidades) precisam indicar claramente as unidades e os pontos de referência.

Exemplos

/**
 * 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;