Guia de estilo da 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 o desenvolvimento de 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 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 mais detalhes e informações sobre o tipo de mudanças permitido, consulte Controle de versões de interfaces.

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 a semântica dela.
  • Documenta 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:

  • Torna o código do cliente ou do servidor mais fácil de entender
  • 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 complica 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 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é 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 isso, o 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 parcelables estruturados quando você tem 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.

Parcelables 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. Normalmente, são parcelables antigos e atuais 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 usar um mecanismo alternativo. Sempre que possível, a funcionalidade de upstreaming do fornecedor precisa ser preferida. 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 são versionados, é preciso evitar 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 arquivos, 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 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;