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 a AIDL é usada para definir uma API ou interagir com as superfícies da API.

A AIDL pode ser usada para definir uma API quando os apps precisam interagir entre si em um processo em segundo plano ou precisam interagir 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 de AIDL na prática, 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 fazer um snapshot, execute m <module-name>-freeze-api. Sempre que um cliente ou servidor da API for lançado (por exemplo, em um trem Mainline), você precisará fazer uma captura de tela e criar uma nova versão. Para APIs do sistema para o fornecedor, isso acontece com a revisão anual da plataforma.

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

Diretrizes de design da API

Geral

1. Documente tudo

  • Documente todos os métodos para a semântica, argumentos, uso de exceções incorporadas, 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 tipos enumerados e constantes.
  • Documente tudo o que pode não estar claro para um implementador.
  • Dê exemplos quando for relevante.

2. Carcaça

Use letras maiúsculas para tipos e letras minúsculas para métodos, campos e argumentos. Por exemplo, MyParcelable para um tipo parcelável e anArgument para um argumento. Para acrônimos, considere o acrônimo como uma palavra (NFC -> Nfc).

[-Wconst-name] Os valores e constantes de enumeração 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" com base 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 que os vinculamentos 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: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 métodos não unidirecionais, 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, é necessário procurar se cada método será bloqueado ou não.

4. Evite 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. Confira informações mais detalhadas na seção de tratamento de erros dos back-ends da AIDL.

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

[-Wout-array] Os métodos que têm 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. 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 e saída

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

7. Evite parâmetros não de matriz @nullable de saída e entrada

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

Parceláveis estruturados

1. Quando usar

Use parceláveis estruturados quando você tiver vários tipos de dados para enviar.

Ou quando você tem um único tipo de dados, mas espera que ele seja estendido no futuro. Por exemplo, não use String username. Use um parcelável extensível, como este:

parcelable User {
    String username;
}

Assim, no futuro, você poderá fazer a extensão da seguinte maneira:

parcelable User {
    String username;
    int id;
}

2. Fornecer padrões explicitamente

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

Parceláveis não estruturados

1. Quando usar

Os parceláveis não estruturados estão disponíveis no Java com @JavaOnlyStableParcelable e no back-end do NDK com @NdkOnlyStableParcelable. Geralmente, são parceláveis antigos e existentes que não podem ser estruturados.

Constantes e enumerações

1. Os campos de bits precisam usar campos constantes

Os campos de bits precisam usar campos 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 um mecanismo alternativo. Sempre que possível, a funcionalidade do fornecedor upstream precisa ser preferida. No entanto, em alguns casos, os valores personalizados do fornecedor podem ser permitidos. No entanto, os fornecedores precisam ter um mecanismo para fazer a versão, 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 tipos enumerados são versionados, evite valores que indiquem quantos valores estão presentes. Em C++, isso pode ser resolvido com enum_range<>. Para Rust, use enum_values(). No 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 o tipo enumerado

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. Principalmente quando o AIDL é implementado em Java, isso pode causar vazamento de descritor de arquivo, a menos que seja cuidadosamente processado. Basicamente, se você aceitar uma FileDescriptor, será necessário fechá-la manualmente quando ela não for mais usada.

Para back-ends nativos, você está seguro porque FileDescriptor é mapeado para unique_fd, que pode ser fechado automaticamente. No entanto, independentemente do idioma de back-end que você usaria, é recomendável NÃO usar FileDescriptor, porque isso limita sua liberdade de mudar o idioma de back-end no futuro.

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

Unidades variáveis

As unidades variáveis precisam ser incluídas no nome para que as unidades 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

As marcas de tempo precisam indicar a referência

As marcas de tempo (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;