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;