Guia de estilo do AIDL

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

O AIDL pode ser usado para definir uma API quando os apps precisam interagir entre si em um processo em segundo plano ou com o sistema. Para mais informações sobre o desenvolvimento de interfaces de programação em apps com AIDL, consulte Linguagem de definição de interface do Android (AIDL). Para exemplos práticos de AIDL, 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 tirar um snapshot, execute m <module-name>-freeze-api. Sempre que um cliente ou servidor da API é lançado (por exemplo, em um trem Mainline), é necessário fazer um snapshot e criar uma nova versão. Para APIs do sistema para fornecedor, isso deve acontecer com a revisão anual da plataforma.

Para mais detalhes e informações sobre o tipo de mudanças permitidas, consulte Interfaces de controle de versões.

Diretrizes de design de API

Geral

1. Documente tudo

  • Documente cada método para sua semântica, argumentos, uso de exceções integradas, exceções específicas do serviço e valor de retorno.
  • Documente cada interface para a semântica dela.
  • Documente o significado semântico de enums e constantes.
  • Documente tudo o que não estiver claro para um implementador.
  • Dê exemplos quando relevante.

2. Carcaça

Use camel case maiúsculo para tipos e minúsculo para métodos, campos e argumentos. Por exemplo, MyParcelable para um tipo parcelable e anArgument para um argumento. Para acrônimos, considere o acrônimo uma palavra (NFC -> Nfc).

[-Wconst-name] Os valores de enumeração e as constantes precisam ser ENUM_VALUE e CONSTANT_NAME.

Interfaces

1. Nomeação

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

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

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

  • Facilita a compreensão do código do cliente ou do servidor
  • Simplifica o ciclo de vida dos objetos
  • Aproveita o fato de os vinculadores não serem falsificáveis.

Não recomendado:uma única interface 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 não unidirecionais, porque isso dificulta a compreensão do modelo de linhas de execução para clientes e servidores. Especificamente, ao ler o código do cliente de uma interface específica, é necessário pesquisar se cada método vai bloquear 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. Para mais detalhes, consulte a seção de tratamento de erros dos back-ends da AIDL.

5. Matrizes como parâmetros de saída consideradas prejudiciais

[-Wout-array] Métodos com parâmetros de saída de 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. Assim, o tamanho da saída da matriz não pode ser escolhido pelo servidor. Esse comportamento indesejável acontece devido à forma como as matrizes funcionam em Java (não podem ser realocadas). Em vez disso, prefira APIs como String[] foo().

6. Evitar parâmetros inout

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

7. Evitar parâmetros @nullable não de matriz out e inout

[-Wout-nullable] Como o back-end Java não processa a anotação @nullable, mas outros back-ends sim, out/inout @nullable T pode levar a um comportamento inconsistente entre os back-ends. Por exemplo, back-ends que não são Java podem definir um parâmetro de saída @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 tiver vários tipos de dados para enviar.

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

parcelable User {
    String username;
}

Assim, no futuro, você poderá estender o código da seguinte forma:

parcelable User {
    String username;
    int id;
}

2. Fornecer padrões explicitamente

[-Wexplicit-default, -Wenum-explicit-default] Forneça padrões explícitos para campos.

Parcelables não estruturados

1. Quando usar

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 enumerações

1. Bitfields precisam usar campos constantes

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

2. Enums precisam ser conjuntos fechados.

Enums precisam ser conjuntos fechados. Observação: somente o proprietário da interface pode adicionar elementos de enumeração. Se os fornecedores ou OEMs precisarem estender esses campos, será necessário um mecanismo alternativo. Sempre que possível, é preferível fazer o upstream da funcionalidade do fornecedor. No entanto, em alguns casos, valores personalizados do fornecedor podem ser permitidos embora os fornecedores precisem ter um mecanismo para versionar isso, talvez o próprio 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 enums são versionados, evite valores que indiquem quantos valores estão presentes. Em C++, isso pode ser evitado com enum_range<>. Para Rust, use enum_values(). Em Java, ainda não há uma solução.

Não recomendado:usar valores numerados

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

4. Evite 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 a enumeração

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] O uso de FileDescriptor como argumento ou valor de retorno de um método de interface AIDL é altamente desencorajado. Principalmente quando a AIDL é implementada em Java, isso pode causar vazamento de descritor de arquivo, a menos que seja processado com cuidado. Basicamente, se você aceitar um FileDescriptor, precisará fechá-lo manualmente quando não for mais usado.

Para back-ends nativos, você está seguro porque FileDescriptor é mapeado para unique_fd, que pode ser fechado automaticamente. Mas, independente da linguagem de back-end que você usaria, é recomendável NÃO usar FileDescriptor, porque isso limita sua liberdade de mudar a linguagem de back-end no futuro.

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

Unidades de variáveis

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

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;