AIDL estável

O Android 10 adiciona suporte à Linguagem de definição de interface do Android (AIDL, na sigla em inglês) estável, uma nova maneira de acompanhar a interface de programação do aplicativo (API)/interface binária do aplicativo (ABI) fornecida pelas interfaces de AIDL. A AIDL estável tem as seguintes diferenças principais da AIDL:

  • As interfaces são definidas no sistema de build com aidl_interfaces.
  • As interfaces só podem conter dados estruturados. Parcelables que representam os tipos desejados são criados automaticamente com base na definição da AIDL e são organizados e desmarcados automaticamente.
  • As interfaces podem ser declaradas como estáveis (compatíveis com versões anteriores). Quando isso acontece, a API é rastreada e controlada em um arquivo ao lado da interface AIDL.

AIDL estruturada e estável

AIDL estruturada refere-se aos tipos definidos puramente na AIDL. Por exemplo, uma declaração de parcelable (uma parcelable personalizada) não é estruturada como AIDL. Os Parcelables com os campos definidos na AIDL são chamados de parcelas estruturadas (link em inglês).

A AIDL estável requer AIDL estruturada para que o sistema de build e o compilador possam entender se as mudanças feitas em 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 precisa usar apenas tipos estruturados, além dos seguintes recursos de controle de versão. Por outro lado, uma interface não será estável se o sistema de build principal for usado para criá-la ou se unstable:true estiver definido.

Definição de uma interface AIDL

Uma definição de aidl_interface é semelhante a esta:

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 de AIDL Foo definido em um pacote com.acme precisa estar em <base_path>/com/acme/Foo.aidl, em que <base_path> pode ser qualquer diretório relacionado ao diretório em que Android.bp está. No exemplo acima, <base_path> é srcs/aidl.
  • local_include_dir: o caminho em que o nome do pacote começa. Ele corresponde a <base_path> explicado acima.
  • imports: uma lista de módulos aidl_interface usados. Se uma das interfaces AIDL usar uma interface ou parcelable de outra aidl_interface, coloque o nome dela 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. Especificar uma versão tem suporte desde o Android 12
  • versions: as versões anteriores da interface que estavam 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 precisará ser especificado e não haverá verificações de compatibilidade. Este campo foi substituído por versions_with_info no 13 e versões mais recentes.
  • 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 importados por essa versão da aidl_interface. A definição da versão V de uma interface AIDL IFACE está localizada em aidl_api/IFACE/V. Esse campo foi introduzido no Android 13 e não precisa 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: a sinalização opcional para a promessa de estabilidade dessa interface. No momento, ele só é compatível com "vintf". Se stability não for definido, o sistema de build vai verificar se a interface é compatível com versões anteriores, a menos que unstable seja especificado. Não definir essa configuração corresponde a uma interface com estabilidade nesse contexto de compilação, ou seja, tudo o que for relacionado ao sistema, por exemplo, itens em system.img e partições relacionadas ou todos os itens do fornecedor, como itens em vendor.img e partições relacionadas. Se stability for definido como "vintf", isso corresponderá a uma promessa de estabilidade: a interface precisará ser mantida estável enquanto for usada.
  • gen_trace: a sinalização opcional para ativar ou desativar o rastreamento. No Android 14 e versões mais recentes, o padrão é true para os back-ends cpp e java.
  • host_supported: a sinalização opcional que, quando definida como true, disponibiliza as bibliotecas geradas para o ambiente do host.
  • unstable: a sinalização opcional usada para marcar que essa interface não precisa ser estável. Quando ele é definido como true, o sistema de build não cria o despejo da API para a interface nem exige que ele seja atualizado.
  • frozen: a sinalização opcional que, quando definida como true, significa que a interface não sofreu mudanças desde a versão anterior. Isso permite mais verificações de tempo de build. Quando definido como false, isso significa que a interface está em desenvolvimento e tem novas mudanças. Portanto, executar foo-freeze-api vai gerar uma nova versão e alterar automaticamente o valor para true. Introduzido no Android 14.
  • backend.<type>.enabled: essas sinalizações alternam cada um dos back-ends para os quais o compilador da AIDL gera código. Atualmente, há suporte a quatro back-ends: Java, C++, NDK e Rust. Os back-ends de 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 desativado explicitamente. O Rust fica desativado por padrão até o Android 15 (AOSP experimental).
  • backend.<type>.apex_available: a lista de nomes APEX para os quais a biblioteca de stubs gerada está disponível.
  • backend.[cpp|java].gen_log: a sinalização opcional que controla se é necessário gerar outro código para coletar informações sobre a transação.
  • backend.[cpp|java].vndk.enabled: a sinalização opcional para tornar essa interface parte do VNDK. O padrão é false.
  • backend.[cpp|ndk].additional_shared_libraries: introduzida no Android 14, essa flag adiciona dependências às bibliotecas nativas. Essa sinalização é útil com ndk_header e cpp_header.
  • backend.java.sdk_version: a sinalização opcional para especificar a versão do SDK em que a biblioteca de stubs Java é criada. O padrão é "system_current". Isso não deve ser definido quando backend.java.platform_apis for verdadeiro.
  • backend.java.platform_apis: a sinalização opcional que precisa ser definida como true quando as bibliotecas geradas precisam ser criadas com base na API da plataforma em vez do SDK.

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

Como gravar arquivos AIDL

As interfaces na 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 estruturada versus estável. A principal diferença na AIDL estável é a forma como os parcelables são definidos. Anteriormente, parcelas eram declaradas para encaminhamento. Na AIDL estável (e, portanto, estruturada), os campos e variáveis de parcelables são definidos explicitamente.

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

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

No momento, um padrão é compatível, mas não obrigatório, com boolean, char, float, double, byte, int, long e String. No Android 12, os padrões para enumerações definidas pelo usuário também têm suporte. Quando um padrão não é especificado, é usado um valor vazio ou semelhante a 0. Enumerações sem um valor padrão são inicializadas como 0, mesmo que não haja um enumerador zero.

Como usar bibliotecas de stubs

Depois de adicionar bibliotecas de stub como dependência ao seu módulo, você poderá incluí-las nos seus arquivos. Confira exemplos de bibliotecas de stub no sistema de build. A Android.mk também pode ser usada 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 no Rust:

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

Interfaces de controle de versão

Declarar um módulo com o nome foo também cria um destino no sistema de build que você pode usar para gerenciar a API do módulo. Quando criado, 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 extra e imports para a versão. Basicamente, imports em versions_with_info é copiado do campo imports. No entanto, a versão estável mais recente é especificada em imports em versions_with_info para a importação que não tem uma versão explícita. Depois que a propriedade versions_with_info é especificada, o sistema de build 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 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 novas:

  • Métodos para o final de uma interface (ou métodos com novos serials explicitamente definidos)
  • Elementos no final de um parcelable (exige 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, há o risco de colisão com alterações feitas por um proprietário.

Para testar se todas as interfaces são congeladas para lançamento, crie com as seguintes variáveis de ambiente definidas:

  • AIDL_FROZEN_REL=true m ...: o build exige que todas as interfaces AIDL estáveis sejam congeladas e não têm um campo owner: especificado.
  • AIDL_FROZEN_OWNERS="aosp test": o build exige que todas as interfaces AIDL estáveis sejam congeladas com o campo owner: 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, isso exige 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 apenas de tipos ou comuns, isso é seguro porque o código já precisa estar escrito para processar tipos desconhecidos de transações de IPC.

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

Como usar interfaces com controle de versão

Métodos de interface

Durante a execução, ao tentar chamar novos métodos em um servidor antigo, os novos clientes recebem um erro ou uma exceção, dependendo do back-end.

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

Para saber mais sobre estratégias para lidar com isso, consulte consultar versões e como usar os padrões.

Parcelables

Quando novos campos são adicionados a parcelables, eles são descartados pelos clientes e servidores antigos. Quando novos clientes e servidores recebem parcelables antigos, os valores padrão dos 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.

Enumerações e constantes

Da mesma forma, clientes e servidores precisam rejeitar ou ignorar enumeradores e valores constantes não reconhecidos, conforme apropriado, já que mais valores podem ser adicionados no futuro. Por exemplo, um servidor não pode ser cancelado quando recebe um enumerador que ele não conhece. Ele deve ignorá-lo ou retornar algo para que o cliente saiba que ele não é compatível com essa implementação.

União

A tentativa de 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 unidirecional. Caso contrário, o erro será BAD_VALUE(para o back-end do C++ ou NDK) ou IllegalArgumentException(para o back-end do Java). O erro será recebido se o cliente estiver enviando uma união definida como o novo campo para um servidor antigo ou quando for um cliente antigo recebendo a união de um novo servidor.

Desenvolvimento baseado em flags

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

A AIDL oferece suporte ao substituto do ambiente de execução dessas 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 de clientes compatível com versões anteriores é semelhante ao comportamento atual e, com o substituto, as implementações também precisam seguir esses comportamentos. Consulte Como usar interfaces com controle de versão.

Sinalização de build da AIDL

A sinalização que controla esse comportamento é RELEASE_AIDL_USE_UNFROZEN definida em build/release/build_flags.bzl. true significa que a versão descongelada da interface é usada no ambiente de execução e false significa que todas as bibliotecas das versões descongeladas se comportam como a última versão congelada. É possível substituir a sinalização para true para desenvolvimento local, mas é preciso revertê-la para false antes do lançamento. Normalmente, o desenvolvimento é feito com uma configuração que tem a flag definida como true.

Matriz de compatibilidade e manifestos

Os objetos da interface do fornecedor (objetos VINTF) definem quais versões são esperadas e quais são fornecidas em ambos os lados da interface do fornecedor.

A maioria dos dispositivos que não são Cuttlefish são direcionados à matriz de compatibilidade mais recente somente depois que as interfaces são congeladas. Por isso, 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 segmenta durante o desenvolvimento. Portanto, quando uma nova versão descongelada de uma interface é adicionada a uma matriz de compatibilidade, as versões congeladas anteriores precisam permanecer para RELEASE_AIDL_USE_UNFROZEN=false. Isso pode ser feito usando diferentes arquivos de matriz de compatibilidade para diferentes configurações de RELEASE_AIDL_USE_UNFROZEN ou permitindo ambas as versões em um único arquivo de matriz de compatibilidade 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, é possível remover a versão 3 da matriz de compatibilidade, porque a versão congelada 4 é usada quando RELEASE_AIDL_USE_UNFROZEN é false.

Manifestos

No Android 15 (AOSP experimental), uma mudança no libvintf é introduzida para modificar os arquivos de manifesto no tempo de build com base no valor de RELEASE_AIDL_USE_UNFROZEN.

Os manifestos e os fragmentos declaram qual versão de uma interface um serviço implementa. Ao usar a versão descongelada mais recente de uma interface, o manifesto precisa ser atualizado para refletir essa nova versão. Quando RELEASE_AIDL_USE_UNFROZEN=false, as entradas do manifesto são ajustadas por libvintf para refletir a alteração 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 manifesto para cada um dos serviços.

Mudanças no cliente HAL

O código do cliente HAL precisa ser compatível com as versões congeladas anteriores com suporte. Quando RELEASE_AIDL_USE_UNFROZEN é false, os serviços sempre têm a aparência da ú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 de framework do Android precisam ser compatíveis com versões anteriores de outras versões, mas esse é um novo detalhe para clientes de fornecedores e clientes de interfaces de propriedade do parceiro.

Mudanças na implementação de HAL

A maior diferença no desenvolvimento da HAL com o desenvolvimento baseado em sinalizações é a exigência de que as implementações dela 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 nas implementações e no código do dispositivo é um novo exercício. Consulte Como usar interfaces com controle de versão.

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

Exemplo: uma interface tem três versões congeladas. A interface é atualizada com um novo método. O cliente e o serviço são atualizados para usar a nova versão 4 da biblioteca. 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 processa a compatibilidade com versões anteriores pode ser removido.

Ao chamar métodos em callbacks, processe o caso quando UNKNOWN_TRANSACTION for retornado. Os clientes podem implementar duas versões diferentes de um callback com base na configuração de lançamento. Portanto, não suponha que o cliente enviará a versão mais recente, e novos métodos podem retornar isso. Isso é semelhante à maneira como os clientes da AIDL estáveis mantêm a compatibilidade com versões anteriores dos servidores descrita em Como usar interfaces com controle de versão.

// 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 os valores padrão quando RELEASE_AIDL_USE_UNFROZEN for false, e os valores dos novos campos que um serviço tenta enviar são descartados ao sair do processo.

Novos tipos adicionados nessa versão descongelada não podem ser enviados ou recebidos por meio da interface.

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

Tenha cuidado para usar novos enumeradores somente 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 a controle de versão baseado em sinalização, você está implementando duas versões diferentes. Portanto, talvez seja conveniente ter a versão da interface atual. Para fazer isso, acesse a versão da interface do objeto atual, por exemplo, this->getInterfaceVersion() ou os outros métodos para my_ver. Para mais informações, consulte Como consultar a versão da interface do objeto remoto.

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 voltar quando RELEASE_AIDL_USE_UNFROZEN é 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.

É possível adicionar os serviços condicionalmente com base no valor da flag 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 não for possível adicioná-lo condicionalmente ao dispositivo, confira se ele foi declarado com IServiceManager::isDeclared(). Se ela tiver sido declarada e houver falha no registro, cancele o processo. Se ela não for declarada, ocorre uma falha no registro.

Cuttlefish como ferramenta de desenvolvimento

Todos os anos após o VINTF ser congelado, ajustamos a matriz de compatibilidade do framework (FCM, na sigla em inglês) target-level e o PRODUCT_SHIPPING_API_LEVEL do Cuttlefish para que reflitam os dispositivos que serão lançados com a versão do próximo ano. Ajustamos target-level e PRODUCT_SHIPPING_API_LEVEL para garantir que algum dispositivo de lançamento seja testado e atenda aos novos requisitos do lançamento do ano que vem.

Quando RELEASE_AIDL_USE_UNFROZEN for true, o Cuttlefish será usado para o desenvolvimento de versões futuras do Android. Ele é destinado ao nível do FCM e PRODUCT_SHIPPING_API_LEVEL da versão do Android no próximo ano, exigindo que ela atenda aos requisitos de software do fornecedor (VSR, na sigla em inglês) da próxima versão.

Quando RELEASE_AIDL_USE_UNFROZEN for false, o Cuttlefish vai ter o target-level e o PRODUCT_SHIPPING_API_LEVEL anteriores para refletir um dispositivo de lançamento. No Android 14 e versões anteriores, essa diferenciação seria realizada com diferentes ramificações do Git que não detectam a mudança no target-level do FCM, no nível da API de envio ou em qualquer outro código direcionado à próxima versão.

Regras de nomenclatura dos módulos

No Android 11, para cada combinação de versões e back-ends ativados, um módulo de biblioteca de stubs é criado automaticamente. Para se referir a um módulo de biblioteca de stubs específico para vinculação, não use o nome do módulo aidl_interface, mas sim o nome do módulo da biblioteca de stubs, que é ifacename-version-backend, em que

  • ifacename: nome do módulo aidl_interface.
  • version pode ser
    • Vversion-number para as versões congeladas
    • Vlatest-frozen-version-number + 1 para a versão "tip-of-tree" (ainda não congelada)
  • backend pode ser
    • java para o back-end Java;
    • cpp para o back-end de C++;
    • ndk ou ndk_platform para o back-end do NDK. O primeiro é para apps e o último é para uso da plataforma,
    • rust para back-end do Rust.

Suponha que haja um módulo com o nome foo, que a versão mais recente dele seja 2 e que ele seja compatível com NDK e C++. Nesse caso, a AIDL gera estes módulos:

  • Com base na versão 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Com base na versão 2 (a versão estável mais recente)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Com base na versão dos ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Em comparação com o Android 11,

  • foo-backend, que faz referência à versão estável mais recente, se torna foo-V2-backend.
  • foo-unstable-backend, que faz referência à versão ToT, se torna foo-V3-backend

Os nomes dos arquivos de saída são sempre iguais aos nomes dos módulos.

  • Com base na versão 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Com base na versão 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Com base na versão do ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

Observe que o compilador AIDL não cria um módulo de versão unstable nem um módulo sem controle de versão para uma interface AIDL estável. No Android 12 e versões mais recentes, o nome do módulo gerado em uma interface AIDL estável sempre inclui a versão.

Novos métodos de meta interface

O Android 10 adiciona vários métodos de metainterface para a AIDL estável.

Consultar 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 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 PRECISA implementar getInterfaceVersion() e getInterfaceHash() desta maneira (super é usado em vez de IFoo para evitar erros de copiar/colar. A anotação @SuppressWarnings("static") pode ser necessária para desativar avisos, dependendo da 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 caminho de classe de inicialização). Quando as classes são compartilhadas, o servidor também é vinculado à versão mais recente das classes, mesmo que ele possa ter sido criado com uma versão mais antiga da interface. Se essa interface meta for implementada na classe compartilhada, ela sempre vai retornar a versão mais recente. No entanto, ao implementar o método conforme mostrado acima, o número da versão da interface é incorporado ao código do servidor (porque IFoo.VERSION é um static final int que está embutido quando referenciado) e, assim, o método pode retornar a versão exata com que o servidor foi criado.

Lidar 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 a AIDL estável, os clientes têm mais controle. No lado do cliente, defina 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 criado 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 versões mais recentes:

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(...);

Não é necessário fornecer a implementação padrão de todos os métodos em uma interface AIDL. Os métodos com garantia de implementação no lado remoto (porque você tem certeza de que o controle remoto é criado quando os métodos estavam na descrição da interface AIDL) não precisam ser substituídos na classe impl padrão.

converter a AIDL em uma AIDL estruturada/estável

Se você tiver uma interface AIDL e um código que a usem, use as etapas a seguir para converter a interface em uma interface AIDL estável.

  1. Identifique todas as dependências da interface. Para cada pacote de que a interface depende, determine se ele está definido na AIDL estável. Se não estiver definido, o pacote precisará ser convertido.

  2. Converta todos os parcelables na sua interface em parcelables estáveis (os arquivos de interface podem permanecer inalterados). Faça isso expressando a estrutura deles diretamente em arquivos AIDL. As classes de gerenciamento precisam 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 módulo, as dependências dele e qualquer outra informação necessária. Para estabilizar (e não apenas estruturar), ele também precisa de controle de versões. Para mais informações, consulte Interfaces de controle de versão.