O Android 10 adiciona suporte para Android Interface Definition Language (AIDL) estável, uma nova maneira de acompanhar a interface do programa de aplicativos (API)/interface binária do aplicativo (ABI) fornecida pelas interfaces AIDL. O AIDL estável tem as seguintes diferenças principais do AIDL:
- As interfaces são definidas no sistema de compilação com
aidl_interfaces
. - As interfaces podem conter apenas dados estruturados. Parcelables que representam os tipos desejados são criados automaticamente com base em sua definição AIDL e são automaticamente empacotados e desempacotados.
- As interfaces podem ser declaradas como estáveis (compatíveis com versões anteriores). Quando isso acontece, sua API é rastreada e controlada em um arquivo próximo à interface AIDL.
Definindo uma interface AIDL
Uma definição de aidl_interface
se parece com isso:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions: ["1", "2"],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
},
}
-
name
: O nome do módulo de interface AIDL que identifica exclusivamente uma interface AIDL. -
srcs
: A lista de arquivos de origem AIDL que compõem a interface. O caminho para um tipo AIDLFoo
definido em um pacotecom.acme
deve estar em<base_path>/com/acme/Foo.aidl
, onde<base_path>
pode ser qualquer diretório relacionado ao diretório ondeAndroid.bp
está. No exemplo acima,<base_path>
ésrcs/aidl
. -
local_include_dir
: O caminho de onde o nome do pacote começa. Corresponde ao<base_path>
explicado acima. -
imports
: Uma lista de módulosaidl_interface
que este usa. Se uma de suas interfaces AIDL usa uma interface ou parcelable de outraaidl_interface
, coloque seu nome aqui. Pode ser o próprio nome, para se referir à versão mais recente, ou o nome com o sufixo da versão (como-V1
) para se referir a uma versão específica. A especificação de uma versão tem suporte desde o Android 12 -
versions
: as versões anteriores da interface que estão congeladas emapi_dir
, a partir do Android 11, asversions
são congeladas emaidl_api/ name
. Se não houver versões congeladas de uma interface, isso não deve ser especificado e não haverá verificações de compatibilidade. -
stability
: O sinalizador opcional para a promessa de estabilidade desta interface. Atualmente suporta apenas"vintf"
. Se não estiver definido, isso corresponde a uma interface com estabilidade dentro deste contexto de compilação (portanto, uma interface carregada aqui só pode ser usada com coisas compiladas juntas, por exemplo em system.img). Se estiver definido como"vintf"
, isso corresponde a uma promessa de estabilidade: a interface deve ser mantida estável enquanto for usada. -
gen_trace
: O sinalizador opcional para ativar ou desativar o rastreamento. O padrão éfalse
. -
host_supported
: O sinalizador opcional que, quando definido comotrue
, disponibiliza as bibliotecas geradas para o ambiente do host. -
unstable
: O sinalizador opcional usado para marcar que esta interface não precisa ser estável. Quando definido comotrue
, o sistema de compilação não cria o dump da API para a interface nem exige que ele seja atualizado. -
backend.<type>.enabled
: Esses sinalizadores alternam cada um dos backends para os quais o compilador AIDL gerará código. Atualmente, três back-ends são suportados:java
,cpp
endk
. Os back-ends são todos habilitados por padrão. Quando um back-end específico não é necessário, ele precisa ser desabilitado explicitamente. -
backend.<type>.apex_available
: a lista de nomes APEX para os quais a biblioteca de stub gerada está disponível. -
backend.[cpp|java].gen_log
: O sinalizador opcional que controla se deve gerar código adicional para coletar informações sobre a transação. -
backend.[cpp|java].vndk.enabled
: O sinalizador opcional para tornar essa interface parte do VNDK. O padrão éfalse
. -
backend.java.platform_apis
: O sinalizador opcional que controla se a biblioteca de stub Java é construída em relação às APIs privadas da plataforma. Isso deve ser definido como"true"
quando astability
estiver definida como"vintf"
. -
backend.java.sdk_version
: O sinalizador opcional para especificar a versão do SDK na qual a biblioteca Java stub é construída. O padrão é"system_current"
. Isso não deve ser definido quandobackend.java.platform_apis
for verdadeiro. -
backend.java.platform_apis
: o sinalizador opcional que deve ser definido comotrue
quando as bibliotecas geradas precisam ser compiladas na API da plataforma em vez do SDK.
Para cada combinação das versions
e dos back-ends habilitados, uma biblioteca de stub é criada. Consulte Regras de nomenclatura de módulo para saber como fazer referência à versão específica da biblioteca de stub para um back-end específico.
Escrevendo arquivos AIDL
As interfaces em AIDL estável são semelhantes às interfaces tradicionais, com a exceção de que não podem usar parcelables não estruturados (porque não são estáveis!). A principal diferença no AIDL estável é como os parcelables são definidos. Anteriormente, os parcelables eram declarados para frente ; no AIDL estável, os campos e variáveis parcelables são definidos explicitamente.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Um padrão é atualmente suportado (mas não obrigatório) para boolean
, char
, float
, double
, byte
, int
, long
e String
. No Android 12, os padrões para enumerações definidas pelo usuário também são compatíveis. Quando um padrão não é especificado, um valor semelhante a 0 ou vazio é usado. Enumerações sem um valor padrão são inicializadas como 0, mesmo se não houver um enumerador zero.
Usando bibliotecas de stub
Depois de adicionar bibliotecas de stub como uma dependência ao seu módulo, você pode incluí-las em seus arquivos. Aqui estão exemplos de bibliotecas de stub no sistema de compilação (o Android.mk
também pode ser usado para definições de módulos legados):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if desire is to load a library and share
// it among multiple users or if you only need access to constants
static_libs: ["my-module-name-java"],
...
}
Exemplo em C++:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
Exemplo em Java:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
Interfaces de versão
Declarar um módulo com o nome foo também cria um destino no sistema de compilação que você pode usar para gerenciar a API do módulo. Quando compilado, foo-freeze-api adiciona uma nova definição de API em api_dir
ou aidl_api/ name
, dependendo da versão do Android, e adiciona um arquivo .hash
, ambos representando a versão recém-congelada da interface. Construir isso também atualiza a propriedade de versions
para refletir a versão adicional. Depois que a propriedade de versions
é especificada, o sistema de compilação executa verificações de compatibilidade entre as versões congeladas e também entre o Top of Tree (ToT) e a versão congelada mais recente.
Além disso, você precisa gerenciar a definição de API da versão do ToT. Sempre que uma API for atualizada, execute foo-update-api para atualizar aidl_api/ name /current
que contém a definição da API da versão ToT.
Para manter a estabilidade de uma interface, os proprietários podem adicionar novos:
- Métodos para o final de uma interface (ou métodos com novos seriais definidos explicitamente)
- Elementos ao final de um parcelable (requer que um padrão seja adicionado para cada elemento)
- Valores constantes
- No Android 11, os enumeradores
- No Android 12, campos até o final de uma união
Nenhuma outra ação é permitida e ninguém mais pode modificar uma interface (caso contrário, eles correm o risco de colidir com as alterações feitas por um proprietário).
Para testar se todas as interfaces estão congeladas para lançamento, você pode compilar com as seguintes variáveis de ambiente definidas:
-
AIDL_FROZEN_REL=true m ...
- a compilação requer que todas as interfaces AIDL estáveis sejam congeladas que não tenhamowner:
campo especificado. -
AIDL_FROZEN_OWNERS="aosp test"
- a compilação requer que todas as interfaces AIDL estáveis sejam congeladas com oowner:
campo especificado como "aosp" ou "test".
Usando interfaces com versão
Métodos de interface
Em tempo de execução, ao tentar chamar novos métodos em um servidor antigo, novos clientes obtêm um erro ou uma exceção, dependendo do back-end.
-
cpp
recebe::android::UNKNOWN_TRANSACTION
. -
ndk
backend obtémSTATUS_UNKNOWN_TRANSACTION
. -
java
backend obtémandroid.os.RemoteException
com uma mensagem dizendo que a API não está implementada.
Para estratégias para lidar com isso, consulte consultar versões e usar padrões .
Parceláveis
Quando novos campos são adicionados a parcelables, clientes e servidores antigos os descartam. Quando novos clientes e servidores recebem parcelables antigos, os valores padrão para novos campos são preenchidos automaticamente. Isso significa que os padrões precisam ser especificados para todos os novos campos em um parcelable.
Os clientes não devem esperar que os servidores usem os novos campos, a menos que saibam que o servidor está implementando a versão que tem o campo definido (consulte consultar versões ).
Enums e constantes
Da mesma forma, clientes e servidores devem rejeitar ou ignorar valores constantes e enumeradores não reconhecidos conforme apropriado, pois mais podem ser adicionados no futuro. Por exemplo, um servidor não deve abortar quando recebe um enumerador que não conhece. Ele deve ignorá-lo ou retornar algo para que o cliente saiba que não há suporte nesta implementação.
Sindicatos
Tentar enviar uma união com um novo campo falhará se o receptor for antigo e não souber sobre o campo. A implementação nunca verá a união com o novo campo. A falha será ignorada se for uma transação de sentido único; caso contrário, o erro será BAD_VALUE
(para o back-end C++ ou NDK) ou IllegalArgumentException
(para o back-end Java). O erro é recebido se o cliente estiver enviando um conjunto de união para o novo campo para um servidor antigo ou quando for um cliente antigo recebendo a união de um novo servidor.
Regras de nomenclatura do módulo
No Android 11, para cada combinação de versões e back-ends ativados, um módulo de biblioteca de stub é criado automaticamente. Para se referir a um módulo de biblioteca de stub específico para vinculação, não use o nome do módulo aidl_interface
, mas o nome do módulo de biblioteca de stub, que é ifacename - version - backend , onde
-
ifacename
: nome do móduloaidl_interface
- a
version
é um dos-
V version-number
para as versões congeladas -
V latest-frozen-version-number + 1
para a versão da ponta da árvore (ainda a ser congelada)
-
- back-
backend
é um dos-
java
para o back-end Java, -
cpp
para o back-end C++, -
ndk
oundk_platform
para o back-end do NDK. O primeiro é para aplicativos e o último é para uso da plataforma.
-
Suponha que haja um módulo com o nome foo e sua versão mais recente seja 2 , e ele suporte NDK e C++. Neste caso, AIDL gera estes módulos:
- Baseado na versão 1
-
foo-V1-(java|cpp|ndk|ndk_platform)
-
- Baseado na versão 2 (a última versão estável)
-
foo-V2-(java|cpp|ndk|ndk_platform)
-
- Baseado na versão ToT
-
foo-V3-(java|cpp|ndk|ndk_platform)
-
Em comparação com o Android 11,
-
foo- backend
, que se refere à última versão estável torna-sefoo- V2 - backend
-
foo-unstable- backend
, que se refere à versão ToT torna-sefoo- V3 - backend
Os nomes dos arquivos de saída são sempre iguais aos nomes dos módulos.
- Baseado na versão 1:
foo-V1-(cpp|ndk|ndk_platform).so
- Baseado na versão 2:
foo-V2-(cpp|ndk|ndk_platform).so
- Baseado na versão do ToT:
foo-V3-(cpp|ndk|ndk_platform).so
Observe que o compilador AIDL não cria um módulo de versão unstable
ou um módulo sem versão para uma interface AIDL estável. A partir do Android 12, o nome do módulo gerado a partir de uma interface AIDL estável sempre inclui sua versão.
Novos métodos de meta interface
O Android 10 adiciona vários métodos de meta interface para o AIDL estável.
Consultando a versão da interface do objeto remoto
Os clientes podem consultar a versão e o hash da interface que o objeto remoto está implementando e comparar os valores retornados com os valores da interface que o cliente está usando.
Exemplo com o back-end cpp
:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
Exemplo com o ndk
(e ndk_platform
):
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
Exemplo com o backend java
:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
Para a linguagem Java, o lado remoto DEVE implementar getInterfaceVersion()
e getInterfaceHash()
da seguinte forma:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return IFoo.VERSION; }
@Override
public final String getInterfaceHash() { return IFoo.HASH; }
}
Isso ocorre porque as classes geradas ( IFoo
, IFoo.Stub
, etc.) são compartilhadas entre o cliente e o servidor (por exemplo, as classes podem estar no classpath de inicialização). Quando as classes são compartilhadas, o servidor também é vinculado à versão mais recente das classes, embora possa ter sido criada com uma versão mais antiga da interface. Se essa meta interface for implementada na classe compartilhada, ela sempre retornará a versão mais recente. No entanto, implementando o método como acima, o número da versão da interface é incorporado no código do servidor (porque IFoo.VERSION
é um static final int
que é embutido quando referenciado) e, portanto, o método pode retornar a versão exata em que o servidor foi construído com.
Lidando com interfaces mais antigas
É possível que um cliente seja atualizado com a versão mais recente de uma interface AIDL, mas o servidor esteja usando a interface AIDL antiga. Nesses casos, chamar um método em uma interface antiga retorna UNKNOWN_TRANSACTION
.
Com AIDL estável, os clientes têm mais controle. No lado do cliente, você pode definir uma implementação padrão para uma interface AIDL. Um método na implementação padrão é invocado apenas quando o método não é implementado no lado remoto (porque foi construído com uma versão mais antiga da interface). Como os padrões são definidos globalmente, eles não devem ser usados em contextos potencialmente compartilhados.
Exemplo em C++ no Android T (AOSP experimental) e posterior:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
Exemplo em Java:
IFoo.Stub.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
Você não precisa fornecer a implementação padrão de todos os métodos em uma interface AIDL. Os métodos que são garantidos para serem implementados no lado remoto (porque você tem certeza de que o controle remoto foi construído quando os métodos estavam na descrição da interface AIDL) não precisam ser substituídos na classe impl
padrão.
Convertendo AIDL existente em AIDL estruturado/estável
Se você tiver uma interface AIDL existente e um código que a utilize, use as etapas a seguir para converter a interface em uma interface AIDL estável.
Identifique todas as dependências de sua interface. Para cada pacote do qual a interface depende, determine se o pacote está definido em AIDL estável. Se não estiver definido, o pacote deve ser convertido.
Converta todos os parcelables em sua interface em parcelables estáveis (os próprios arquivos de interface podem permanecer inalterados). Faça isso expressando sua estrutura diretamente em arquivos AIDL. As classes de gerenciamento devem ser reescritas para usar esses novos tipos. Isso pode ser feito antes de você criar um pacote
aidl_interface
(abaixo).Crie um pacote
aidl_interface
(conforme descrito acima) que contenha o nome do seu módulo, suas dependências e qualquer outra informação necessária. Para torná-lo estabilizado (não apenas estruturado), ele também precisa ser versionado. Para obter mais informações, consulte Interfaces de versão.