AIDL estável

O Android 10 adiciona suporte para Android Interface Definition Language (AIDL) estável, uma nova maneira de acompanhar a interface de programação do aplicativo (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. Os pacotes que representam os tipos desejados são criados automaticamente com base em sua definição AIDL e são empacotados e desempacotados automaticamente.
  • As interfaces podem ser declaradas como estáveis ​​(compatíveis com versões anteriores). Quando isso acontece, sua API é rastreada e versionada em um arquivo próximo à interface AIDL.

Definindo uma interface AIDL

Uma definição de aidl_interface se parece com isto:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            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 isso usa. Se uma de suas interfaces AIDL usar 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 é suportada 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 estã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. Este campo foi substituído por versions_with_info para 13 e superior.
  • versions_with_info : Lista de tuplas, cada uma contendo o nome de uma versão congelada e uma lista com versões importadas de outros módulos aidl_interface que esta versão do aidl_interface importou. A definição da versão V de uma interface AIDL IFACE está localizada em aidl_api/ IFACE / V . Este campo foi introduzido no Android 13 e não deve ser modificado diretamente no Android.bp. O campo é adicionado ou atualizado invocando *-update-api ou *-freeze-api . Além disso, os campos versions são migrados automaticamente para versions_with_info quando um usuário invoca *-update-api ou *-freeze-api .
  • stability : o sinalizador opcional para a promessa de estabilidade desta interface. Atualmente suporta apenas "vintf" . Se não for definido, isso corresponde a uma interface com estabilidade dentro desse contexto de compilação (portanto, uma interface carregada aqui só pode ser usada com coisas compiladas juntas, por exemplo em system.img). Se for 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 isso é definido como true , o sistema de construção não cria o despejo de API para a interface nem exige que ele seja atualizado.
  • frozen : O sinalizador opcional que, quando definido como true , significa que a interface não sofreu alterações desde a versão anterior da interface. Isso permite mais verificações de tempo de compilação. Quando definido como false , isso significa que a interface está em desenvolvimento e tem novas alterações, portanto, a execução de foo-freeze-api gerará uma nova versão e alterará automaticamente o valor para true . Introduzido no Android 14 (experimental AOSP).
  • backend.<type>.enabled : Esses sinalizadores alternam cada um dos backends para os quais o compilador AIDL gera código. Atualmente, quatro back-ends são suportados: Java, C++, NDK e Rust. Os back-ends Java, C++ e NDK são ativados por padrão. Se algum desses três back-ends não for necessário, ele precisará ser desabilitado explicitamente. A ferrugem está desativada por padrão.
  • backend.<type>.apex_available : a lista de nomes APEX para os quais a biblioteca 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 esta interface uma parte do VNDK. O padrão é false .
  • backend.java.sdk_version : O sinalizador opcional para especificar a versão do SDK com base na qual a biblioteca stub Java é 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 construídas na API da plataforma em vez do SDK.

Para cada combinação das versões e dos back-ends habilitados, uma biblioteca stub é criada. Para saber como se referir à versão específica da biblioteca stub para um back-end específico, consulte Regras de nomenclatura do módulo .

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, as parcelas eram declaradas a prazo ; em 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 é suportado atualmente (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 que não haja nenhum enumerador zero.

Usando bibliotecas de stub

Depois de adicionar bibliotecas stub como uma dependência ao seu módulo, você pode incluí-las em seus arquivos. Aqui estão exemplos de bibliotecas stub no sistema de construçã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"],
    ...
}
# or
rust_... {
    name: ...,
    rust_libs: ["my-module-name-rust"],
    ...
}

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

Exemplo em ferrugem:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

Interfaces de controle de versão

A declaração de 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 construído, 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. foo-freeze-api também atualiza a versions_with_info para refletir a versão adicional e imports para a versão. Basicamente, imports versions_with_info são copiadas do campo imports . Mas a versão estável mais recente é especificada nas imports versions_with_info para a importação que não possui uma versão explícita. Depois que a versions_with_info é 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 da API da versão 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 no final de uma interface (ou métodos com novos seriais explicitamente definidos)
  • Elementos ao final de um parcelal (requer um padrão a ser adicionado para cada elemento)
  • Valores constantes
  • No Android 11, enumeradores
  • No Android 12, campos ao final de uma união

Nenhuma outra ação é permitida e ninguém mais pode modificar uma interface (caso contrário, há risco de colisão com alterações feitas por um proprietário).

Para testar se todas as interfaces estão congeladas para liberação, você pode criar com as seguintes variáveis ​​de ambiente definidas:

  • AIDL_FROZEN_REL=true m ... - build requer que todas as interfaces AIDL estáveis ​​sejam congeladas sem owner: campo especificado.
  • AIDL_FROZEN_OWNERS="aosp test" - build requer que todas as interfaces AIDL estáveis ​​sejam congeladas com o owner: campo especificado como "aosp" ou "teste".

Estabilidade das importações

Atualizar as versões de importações para versões congeladas de uma interface é compatível com versões anteriores na camada Stable AIDL. No entanto, a atualização destes requer a atualização de todos os servidores e clientes que usam a versão antiga da interface, e alguns aplicativos podem se confundir ao misturar diferentes versões de tipos. Geralmente, para pacotes comuns ou somente tipos, isso é seguro porque o código já precisa ser escrito para lidar com tipos desconhecidos de transações IPC.

No código da plataforma Android android.hardware.graphics.common é o maior exemplo desse tipo de atualização de versão.

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.

  • back-end cpp obtém ::android::UNKNOWN_TRANSACTION .
  • back-end ndk obtém STATUS_UNKNOWN_TRANSACTION .
  • back-end java recebe android.os.RemoteException com uma mensagem dizendo que a API não está implementada.

Para obter estratégias para lidar com isso, consulte consultando versões e usando padrões .

Parceláveis

Quando novos campos são adicionados aos 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 defaults 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 possui o campo definido (consulte consultando versões ).

Enums e constantes

Da mesma forma, clientes e servidores devem rejeitar ou ignorar valores constantes não reconhecidos e enumeradores 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 falha se o destinatário for antigo e não souber sobre o campo. A implementação nunca verá a união com o novo campo. A falha é ignorada se for uma transação unidirecional; caso contrário, o erro é 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 habilitados, um módulo de biblioteca stub é criado automaticamente. Para se referir a um módulo de biblioteca stub específico para vinculação, não use o nome do módulo aidl_interface , mas o nome do módulo de biblioteca stub, que é ifacename - version - backend , onde

  • ifacename : nome do módulo aidl_interface
  • version é qualquer 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)
  • 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,
    • rust para o back-end do Rust.

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|rust)
  • Baseado na versão 2 (a última versão estável)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Com base na versão ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

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 referia à 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|rust).so
  • Baseado na versão 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Baseado na versão ToT: foo-V3-(cpp|ndk|ndk_platform|rust).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 metainterface

O Android 10 adiciona vários métodos de metainterface 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 back-end ndk (e o 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 back-end 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 maneira ( super é usado em vez de IFoo para evitar erros de copiar/colar. A anotação @SuppressWarnings("static") pode ser necessária para desabilitar os avisos, dependendo a configuração javac ):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.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 construído com uma versão mais antiga da interface. Se essa metainterface for implementada na classe compartilhada, ela sempre retornará a versão mais recente. No entanto, ao implementar o método 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 criado 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 13 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 cuja implementação é garantida no lado remoto (porque você tem certeza de que o 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 da sua interface. Para cada pacote do qual a interface depende, determine se o pacote está definido em AIDL estável. Se não for 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 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 controle de versão .