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 de aplicativo (API)/interface binária de aplicativo (ABI) fornecida pelas interfaces AIDL. O AIDL estável tem as seguintes diferenças importantes do AIDL:

  • As interfaces são definidas no sistema de construção com aidl_interfaces .
  • As interfaces podem conter apenas dados estruturados. Parceláveis ​​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 versionada em um arquivo próximo à interface AIDL.

AIDL estruturado versus estável

AIDL estruturado refere-se a tipos definidos puramente em AIDL. Por exemplo, uma declaração parcelavel (uma parcelable personalizada) não é AIDL estruturada. Os parcelables com seus campos definidos em AIDL são chamados de parcelables estruturados .

O AIDL estável requer AIDL estruturado para que o sistema de compilação e o compilador possam entender se as alterações feitas nos parcelables são compatíveis com versões anteriores. No entanto, nem todas as interfaces estruturadas são estáveis. Para ser estável, uma interface deve usar apenas tipos estruturados e também deve usar os seguintes recursos de controle de versão. Por outro lado, uma interface não é estável se o sistema de compilação principal for usado para construí-la ou se unstable:true estiver definido.

Definindo uma interface AIDL

Uma definição de aidl_interface é assim:

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 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 de versão (como -V1 ) para se referir a uma versão específica. A especificação de uma versão é compatível 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 deverá ser especificado e não haverá verificações de compatibilidade. Este campo foi substituído por versions_with_info para versões 13 e superiores.
  • versions_with_info : Lista de tuplas, cada uma contendo o nome de uma versão congelada e uma lista com importações de versões 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 estiver definido, corresponde a uma interface com estabilidade dentro deste contexto de compilação (portanto, uma interface carregada aqui só pode ser usada com itens compilados juntos, 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. A partir do Android 14, o padrão é true para back-ends cpp e java .
  • host_supported : o sinalizador opcional que, quando definido como true , disponibiliza as bibliotecas geradas para o ambiente 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 ela seja atualizada.
  • 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 em tempo de construçã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.
  • backend.<type>.enabled : esses sinalizadores alternam cada um dos back-ends para os quais o compilador AIDL gera código. Atualmente, quatro backends são suportados: Java, C++, NDK e Rust. Os back-ends Java, C++ e NDK são habilitados por padrão. Se algum desses três back-ends não for necessário, ele precisará ser desabilitado explicitamente. Rust está desabilitado 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 parte do VNDK. O padrão é false .
  • backend.[cpp|ndk].additional_shared_libraries : introduzido no Android 14, este sinalizador adiciona dependências às bibliotecas nativas. Este sinalizador é útil com ndk_header e cpp_header .
  • backend.java.sdk_version : o sinalizador opcional para especificar a versão do SDK 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 de versões e backends habilitados, uma biblioteca stub é criada. Para saber como consultar a versão específica da biblioteca stub para um back-end específico, consulte Regras de nomenclatura de módulos .

Gravando 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! consulte AIDL estruturado versus estável ). A principal diferença no AIDL estável é como os parcelables são definidos. Anteriormente, os parcelables eram declarados a termo ; em AIDL estável (e, portanto, estruturado), campos e variáveis ​​​​parceláveis ​​são definidos explicitamente.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

Atualmente há suporte para um padrão (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 valor padrão são inicializadas com 0 mesmo se não houver enumerador zero.

Usando bibliotecas stub

Depois de adicionar bibliotecas stub como uma dependência ao seu módulo, você pode incluí-las em seus arquivos. Aqui estão alguns exemplos de bibliotecas stub no sistema de compilaçã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: ...,
    rustlibs: ["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 versionamento

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. foo-freeze-api também atualiza a propriedade versions_with_info para refletir a versão adicional e imports da versão. Basicamente, imports em versions_with_info são copiadas do campo imports . Mas a versão estável mais recente é especificada nas imports em versions_with_info para a importação que não possui uma versão explícita. Depois que a propriedade versions_with_info for especificada, o sistema de compilação executa verificações de compatibilidade entre 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 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 até o final de uma interface (ou métodos com novas séries explicitamente definidas)
  • Elementos no final de um parcelable (requer que um padrão seja adicionado para cada elemento)
  • Valores constantes
  • No Android 11, recenseadores
  • 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, há o risco de colisão com as alterações feitas pelo proprietário).

Para testar se todas as interfaces estão congeladas para lançamento, você pode criar com o seguinte conjunto de variáveis ​​ambientais:

  • AIDL_FROZEN_REL=true m ... - build requer que todas as interfaces AIDL estáveis ​​sejam congeladas e que não tenham owner: campo especificado.
  • AIDL_FROZEN_OWNERS="aosp test" - a construção requer que todas as interfaces AIDL estáveis ​​sejam congeladas com o owner: campo especificado como "aosp" ou "test".

Estabilidade das importações

A atualização das versões de importações para versões congeladas de uma interface é compatível com versões anteriores na camada AIDL estável. No entanto, atualizá-los requer a atualização de todos os servidores e clientes que usam a versão antiga da interface, e alguns aplicativos podem ficar confusos ao misturar diferentes versões de tipos. Geralmente, para pacotes somente de tipos ou comuns, isso é seguro porque o código já precisa estar 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 versionadas

Métodos de interface

Em tempo de execução, ao tentar chamar novos métodos em um servidor antigo, os novos clientes recebem um erro ou uma exceção, dependendo do backend.

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

Para 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 parcelas antigas, 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 uma parcela.

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 e enumeradores não reconhecidos, conforme apropriado, uma vez que mais poderão 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 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 será ignorada se for uma transação unilateral; caso contrário, o erro será BAD_VALUE (para backend C++ ou NDK) ou IllegalArgumentException (para backend 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.

Desenvolvimento baseado em bandeiras

Interfaces em desenvolvimento (descongeladas) não podem ser usadas em dispositivos de lançamento, porque não há garantia de compatibilidade com versões anteriores.

AIDL oferece suporte ao tempo de execução para essas bibliotecas de interface descongeladas para que o código seja escrito na versão descongelada mais recente e ainda seja usado em dispositivos de lançamento. O comportamento compatível com versões anteriores dos clientes é semelhante ao comportamento existente e, com o fallback, as implementações também precisam seguir esses comportamentos. Consulte Usando interfaces versionadas .

Sinalizador de construção AIDL

O sinalizador que controla esse comportamento é RELEASE_AIDL_USE_UNFROZEN definido em build/release/build_flags.bzl . true significa que a versão descongelada da interface é usada em tempo de execução e false significa que todas as bibliotecas das versões descongeladas se comportam como sua última versão congelada. Você pode substituir o sinalizador como true para desenvolvimento local, mas deve revertê-lo para false antes do lançamento. Normalmente, o desenvolvimento é feito com uma configuração que possui o sinalizador definido como true .

Matriz de compatibilidade e manifestos

Os objetos de interface do fornecedor (objetos VINTF) definem quais versões são esperadas e quais versões são fornecidas em cada lado da interface do fornecedor.

A maioria dos dispositivos não Cuttlefish tem como alvo a matriz de compatibilidade mais recente somente depois que as interfaces são congeladas, portanto não há diferença nas bibliotecas AIDL baseadas em RELEASE_AIDL_USE_UNFROZEN .

Matrizes

As interfaces de propriedade do parceiro são adicionadas a matrizes de compatibilidade específicas do dispositivo ou do produto que o dispositivo visa durante o desenvolvimento. Portanto, quando uma versão nova e descongelada de uma interface é adicionada a uma matriz de compatibilidade, as versões congeladas anteriores precisam permanecer para RELEASE_AIDL_USE_UNFROZEN=false . Você pode lidar com isso usando diferentes arquivos de matriz de compatibilidade para diferentes configurações RELEASE_AIDL_USE_UNFROZEN ou permitindo ambas as versões em um único arquivo de matriz de compatibilidade que é usado em todas as configurações.

Por exemplo, ao adicionar uma versão 4 descongelada, use <version>3-4</version> .

Quando a versão 4 está congelada, você pode remover a versão 3 da matriz de compatibilidade porque a versão 4 congelada é usada quando RELEASE_AIDL_USE_UNFROZEN é false .

Manifestos

No Android 15 (AOSP experimental), uma alteração no libvintf é introduzida para modificar os arquivos de manifesto no momento da compilação com base no valor de RELEASE_AIDL_USE_UNFROZEN .

Os manifestos e os fragmentos de manifesto declaram qual versão de uma interface um serviço implementa. Ao usar a versão descongelada mais recente de uma interface, o manifesto deve ser atualizado para refletir esta nova versão. Quando RELEASE_AIDL_USE_UNFROZEN=false as entradas do manifesto são ajustadas pela libvintf para refletir a mudança na biblioteca AIDL gerada. A versão é modificada da versão descongelada, N , para a última versão congelada N - 1 . Portanto, os usuários não precisam gerenciar vários manifestos ou fragmentos de manifestos para cada um dos seus serviços.

Mudanças no cliente HAL

O código do cliente HAL deve ser compatível com versões anteriores congeladas com suporte. Quando RELEASE_AIDL_USE_UNFROZEN é false , os serviços sempre se parecem com a última versão congelada ou anterior (por exemplo, chamar novos métodos descongelados retorna UNKNOWN_TRANSACTION ou novos campos parcelable têm seus valores padrão). Os clientes da estrutura Android precisam ser compatíveis com versões anteriores adicionais, mas esse é um novo detalhe para clientes fornecedores e clientes de interfaces de propriedade de parceiros.

Mudanças na implementação de HAL

A maior diferença no desenvolvimento HAL com o desenvolvimento baseado em flag é o requisito para que as implementações HAL sejam compatíveis com versões anteriores da última versão congelada para funcionar quando RELEASE_AIDL_USE_UNFROZEN for false . Considerar a compatibilidade com versões anteriores em implementações e código de dispositivo é um exercício novo. Consulte Usando interfaces versionadas .

As considerações de compatibilidade com versões anteriores são geralmente as mesmas para clientes e servidores, e para código de estrutura e código de fornecedor, mas há diferenças sutis das quais você precisa estar ciente, já que agora você está implementando efetivamente duas versões que usam o mesmo código-fonte (a versão atual descongelada).

Exemplo: Uma interface possui três versões congeladas. A interface é atualizada com um novo método. O cliente e o serviço foram atualizados para usar a nova biblioteca da versão 4. Como a biblioteca V4 é baseada em uma versão descongelada da interface, ela se comporta como a última versão congelada, versão 3, quando RELEASE_AIDL_USE_UNFROZEN é false e impede o uso do novo método.

Quando a interface é congelada, todos os valores de RELEASE_AIDL_USE_UNFROZEN usam essa versão congelada e o código que trata da compatibilidade com versões anteriores pode ser removido.

Ao chamar métodos em retornos de chamada, você deve lidar normalmente com o caso quando UNKNOWN_TRANSACTION é retornado. Os clientes podem estar implementando duas versões diferentes de um retorno de chamada com base na configuração da versão, portanto, você não pode presumir que o cliente enviará a versão mais recente e novos métodos poderão retornar isso. Isso é semelhante ao modo como os clientes AIDL estáveis ​​mantêm a compatibilidade com versões anteriores com servidores, descrito em Usando interfaces versionadas .

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

Novos campos em tipos existentes ( parcelable , enum , union ) podem não existir ou conter seus valores padrão quando RELEASE_AIDL_USE_UNFROZEN for false e os valores de novos campos que um serviço tenta enviar são descartados na saída do processo.

Novos tipos adicionados nesta versão descongelada não podem ser enviados ou recebidos pela interface.

A implementação nunca recebe uma chamada para novos métodos de nenhum cliente quando RELEASE_AIDL_USE_UNFROZEN é false .

Tenha o cuidado de usar novos enumeradores apenas com a versão em que foram introduzidos, e não com a versão anterior.

Normalmente, você usa foo->getInterfaceVersion() para ver qual versão a interface remota está usando. No entanto, com o suporte de controle de versão baseado em sinalizadores, você está implementando duas versões diferentes, portanto, talvez queira obter a versão da interface atual. Você pode fazer isso obtendo a versão da interface do objeto atual, por exemplo, this->getInterfaceVersion() ou os outros métodos para my_ver . Consulte Consultando a versão da interface do objeto remoto para obter mais informações.

Novas interfaces VINTF estáveis

Quando um novo pacote de interface AIDL é adicionado, não há uma última versão congelada, portanto, não há comportamento para o qual recorrer quando RELEASE_AIDL_USE_UNFROZEN for false . Não use essas interfaces. Quando RELEASE_AIDL_USE_UNFROZEN for false , o Service Manager não permitirá que o serviço registre a interface e os clientes não a encontrarão.

Você pode adicionar os serviços condicionalmente com base no valor do sinalizador RELEASE_AIDL_USE_UNFROZEN no makefile do dispositivo:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

Se o serviço fizer parte de um processo maior e você não puder adicioná-lo ao dispositivo condicionalmente, poderá verificar se o serviço foi declarado com IServiceManager::isDeclared() . Se for declarado e não for registrado, interrompa o processo. Se não for declarado, espera-se que não seja registrado.

O choco como ferramenta de desenvolvimento

Todos os anos, após o VINTF ser congelado, ajustamos o target-level da matriz de compatibilidade de estrutura (FCM) e o PRODUCT_SHIPPING_API_LEVEL do Cuttlefish para que reflitam o lançamento dos dispositivos com o lançamento do próximo ano. Ajustamos target-level e PRODUCT_SHIPPING_API_LEVEL para garantir que haja algum dispositivo de lançamento testado e que atenda aos novos requisitos para o lançamento do próximo ano.

Quando RELEASE_AIDL_USE_UNFROZEN for true , Cuttlefish será usado para desenvolvimento de versões futuras do Android. Ele tem como alvo o nível FCM e PRODUCT_SHIPPING_API_LEVEL da versão Android do próximo ano, exigindo que ele satisfaça os requisitos de software do fornecedor (VSR) da próxima versão.

Quando RELEASE_AIDL_USE_UNFROZEN é false , Cuttlefish tem o target-level anterior e PRODUCT_SHIPPING_API_LEVEL para refletir um dispositivo de liberação. No Android 14 e versões anteriores, essa diferenciação seria realizada com diferentes ramificações do Git que não captam a alteração no target-level do FCM, no nível da API de remessa ou em qualquer outro código direcionado à próxima versão.

Regras de nomenclatura de módulos

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 back-end do Rust.

Suponha que exista um módulo com o nome foo e sua versão mais recente seja 2 e 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 versão estável mais recente)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Baseado na versão ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Em comparação com o Android 11,

  • foo- backend , que se referia à ú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 backend 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 backend 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 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() como segue ( super é usado em vez de IFoo para evitar erros de copiar/colar. A anotação @SuppressWarnings("static") pode ser necessária para desabilitar avisos, dependendo de 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, mesmo que possa ter sido construído com uma versão mais antiga da interface. Se esta 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 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 somente 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 que têm garantia de implementação 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 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 deverá ser convertido.

  2. Converta todos os parcelables da sua interface em parcelables estáveis ​​(os próprios arquivos da 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. Isto 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 versionamento .