Guia de Estilo AIDL

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

A AIDL pode ser usada para definir uma API quando os aplicativos precisam interagir uns com os outros em um processo em segundo plano ou precisam interagir com o sistema. Para obter mais informações sobre o desenvolvimento de interfaces de programação em aplicativos com AIDL, consulte Android Interface Definition Language (AIDL) . Para exemplos de AIDL na prática, consulte AIDL para HALs e Stable AIDL .

Controle de versão

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

Para obter mais detalhes e informações sobre o tipo de alterações permitidas, consulte Interfaces de controle de versão .

Diretrizes de design de API

Em geral

1. Documente tudo

  • Documente cada método para sua semântica, argumentos, uso de exceções internas, exceções específicas de serviço e valor de retorno.
  • Documente cada interface para sua semântica.
  • Documente o significado semântico de enumerações e constantes.
  • Documente tudo o que pode não estar claro para um implementador.
  • Forneça exemplos onde for relevante.

2. Revestimento

Use caixa camel superior para tipos e caixa camel inferior para métodos, campos e argumentos. Por exemplo, MyParcelable para um tipo parcelable e anArgument para um argumento. Para siglas, considere a sigla como uma palavra ( NFC -> Nfc ).

[-Wconst-name] Valores e constantes de enumeração devem ser ENUM_VALUE e CONSTANT_NAME

Interfaces

1. Nomenclatura

[-Winterface-name] Um nome de interface deve começar com I like IFoo .

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

Dê preferência às subinterfaces quando houver muitas chamadas relacionadas a uma API específica. Isso fornece os seguintes benefícios: - Torna o código cliente/servidor mais fácil de entender - Torna o ciclo de vida dos objetos mais simples - Tira vantagem de vinculadores que não podem ser falsificados.

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: subinterfaces 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 com métodos bidirecionais

[-Wmixed-oneway] Não misture métodos unidirecionais com métodos não unidirecionais, pois isso torna o entendimento do modelo de encadeamento complicado para clientes e servidores. Especificamente, ao ler o código do cliente de uma interface específica, você precisa procurar cada método para saber se esse método será bloqueado ou não.

4. Evite retornar códigos de status

Os métodos devem evitar códigos de status como valores de retorno, pois 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. Informações mais detalhadas estão na seção Tratamento de erros de AIDL Backends .

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

[-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 deve ser declarado e alocado pelo cliente em Java e, portanto, o tamanho da saída da matriz não pode ser escolhido pelo servidor. Esse comportamento indesejável ocorre devido à forma como os arrays funcionam em Java (eles não podem ser realocados). Em vez disso, prefira APIs como String[] foo() .

6. Evite parâmetros de entrada e saída

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

7. Evite parâmetros não array out/inout @nullable

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

Parceláveis ​​estruturados

1. Quando usar

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

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

parcelable User {
    String username;
}

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

parcelable User {
    String username;
    int id;
}

2. Forneça padrões explicitamente

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

Parceláveis ​​não estruturados

1. Quando usar

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

Constantes e Enums

1. Bitfields devem usar campos constantes

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

2. Enums devem ser conjuntos fechados.

Enums devem ser conjuntos fechados. Observação: somente o proprietário da interface pode adicionar elementos enum. Se os fornecedores ou OEMs precisarem estender esses campos, será necessário um mecanismo alternativo. Sempre que possível, a funcionalidade upstreaming do fornecedor deve ser preferida. No entanto, em alguns casos, os valores personalizados do fornecedor podem ser permitidos (embora os fornecedores devam ter um mecanismo para controlar a versão disso, talvez o próprio AIDL, eles não devem entrar em conflito uns com os outros e esses valores não devem ser expostos a aplicativos de terceiros).

3. Evite valores como "NUM_ELEMENTS"

Como enums são versionados, valores que indicam quantos valores estão presentes devem ser evitados. Em C++, isso pode ser contornado com enum_range<> . Para Rust, use enum_values() . Em Java, ainda não há solução.

Não recomendado: usando 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: usando um prefixo redundante

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Recomendado: nomear diretamente o enum

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] O uso de FileDescriptor como um argumento ou o valor de retorno de um método de interface AIDL é altamente desencorajado. Especialmente, quando o AIDL é implementado em Java, isso pode causar vazamento do descritor de arquivo, a menos que seja manuseado 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 mapeia para unique_fd , que pode ser fechado automaticamente. Mas, independentemente da linguagem de back-end que você usaria, é aconselhável NÃO usar FileDescriptor porque isso limitará sua liberdade de alterar a linguagem de back-end no futuro.

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

unidades variáveis

Certifique-se de que as unidades variáveis ​​sejam incluídas no nome para que suas unidades sejam bem definidas e compreendidas sem a necessidade de fazer referência à 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 timestamps devem indicar sua referência

Os timestamps (na verdade, todas as unidades!) devem indicar claramente suas unidades e 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;