AIDL estável

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 AIDL Foo definido em um pacote com.acme deve estar em <base_path>/com/acme/Foo.aidl , onde <base_path> pode ser qualquer diretório relacionado ao diretório onde Android.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ódulos aidl_interface que este usa. Se uma de suas interfaces AIDL usa uma interface ou parcelable de outra aidl_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 em api_dir , a partir do Android 11, as versions são congeladas em aidl_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 como true , 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 como true , 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 e ndk . 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 a stability 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 quando backend.java.platform_apis for verdadeiro.
  • backend.java.platform_apis : o sinalizador opcional que deve ser definido como true 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 tenham owner: campo especificado.
  • AIDL_FROZEN_OWNERS="aosp test" - a compilação requer que todas as interfaces AIDL estáveis ​​sejam congeladas com o owner: 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ém STATUS_UNKNOWN_TRANSACTION .
  • java backend obtém android.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ódulo aidl_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 ou ndk_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-se foo- V2 - backend
  • foo-unstable- backend , que se refere à versão ToT torna-se foo- 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.

  1. 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.

  2. 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).

  3. 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.