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, especialmente quando AIDL é usado para definir uma API ou interagir com superfícies de API.

AIDL pode ser usado para definir uma API quando os aplicativos precisam interagir entre si 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 AIDL estável .

Versionamento

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 da linha principal), você precisa tirar um snapshot 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 os tipos de alterações permitidas, consulte Versionamento de interfaces .

Diretrizes de design de API

Em geral

1. Documente tudo

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

2. Invólucro

Use maiúsculas e minúsculas para tipos e minúsculas para métodos, campos e argumentos. Por exemplo, MyParcelable para um tipo parcelavel e anArgument para um argumento. Para siglas, considere a sigla uma palavra ( NFC -> Nfc ).

[-Wconst-name] Os valores e constantes enum devem ser ENUM_VALUE e CONSTANT_NAME

Interfaces

1. Nomeação

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

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

Prefira 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 - Aproveita que os fichários não podem ser falsificados.

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: 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 unilaterais 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 threading complicado para clientes e servidores. Especificamente, ao ler o código do cliente de uma interface específica, você precisa procurar em cada método 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, uma vez que todos os métodos AIDL possuem 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 Backends AIDL .

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

[-Wout-array] Métodos com parâmetros de saída de array, como void foo(out String[] ret) geralmente são ruins porque o tamanho do array de saída deve ser declarado e alocado pelo cliente em Java e, portanto, o tamanho da saída do array não pode ser escolhido pelo servidor. Esse comportamento indesejável acontece devido ao modo como os arrays funcionam em Java (eles não podem ser realocados). Em vez disso, prefira APIs como String[] foo() .

6. Evite parâmetros inout

[-Winout-parameter] Isso pode confundir os clientes porque até os parâmetros in 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 entre os back-ends. Por exemplo, back-ends não Java podem definir um parâmetro out @nullable como null (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 precisar 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, futuramente, você possa estendê-lo, da seguinte forma:

parcelable User {
    String username;
    int id;
}

2. Forneça padrões explicitamente

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

Parceláveis ​​não estruturados

1. Quando usar

Parceláveis ​​não estruturados estão atualmente disponíveis em Java com @JavaOnlyStableParcelable e no backend NDK com @NdkOnlyStableParcelable . Geralmente, trata-se de parcelas antigas e existentes que não podem ser facilmente estruturadas.

Constantes e Enums

1. Bitfields devem usar campos constantes

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

2. As enumerações devem ser conjuntos fechados.

Enums devem ser conjuntos fechados. Nota: somente o proprietário da interface pode adicionar elementos enum. Se os fornecedores ou OEMs precisarem ampliar esses campos, será necessário um mecanismo alternativo. Sempre que possível, a funcionalidade do fornecedor de upstreaming deve ser preferida. No entanto, em alguns casos, os valores personalizados do fornecedor podem ser permitidos (embora os fornecedores devam ter um mecanismo em vigor para versionar isso, talvez o próprio AIDL, eles não devem poder entrar em conflito entre si e esses valores não devem ser exposto a aplicativos de terceiros).

3. Evite valores como “NUM_ELEMENTS”

Como as enums são versionadas, 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: 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: nomeando diretamente o enum

enum MyStatus {
    GOOD,
    BAD
}

Descritor de arquivo

[-Wfile-descriptor] O uso de FileDescriptor como argumento ou 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 tratado com cuidado. Basicamente, se você aceitar um FileDescriptor , precisará fechá-lo manualmente quando ele 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 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 e hora devem indicar sua referência

Os carimbos de data e hora (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;