AIDL estável

O Android 10 adiciona suporte à Linguagem de definição de interface do Android (AIDL) estável, uma nova maneira de acompanhar a interface de programação do aplicativo (API) e a interface binária do aplicativo (ABI) fornecidas pelas interfaces de AIDL. A AIDL estável funciona exatamente como a AIDL, mas o sistema de build rastreia a compatibilidade da interface, e há restrições sobre o que você pode fazer:

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

AIDL estruturada x estável

AIDL estruturado se refere a tipos definidos puramente em AIDL. Por exemplo, uma declaração parcelable (um parcelable personalizado) não é AIDL estruturada. Os Parcelables com campos definidos em AIDL são chamados de Parcelables estruturados.

A AIDL estável exige a 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 e os seguintes recursos de controle de versões. Por outro lado, uma interface não é estável se o sistema de build principal for usado para criá-la ou se unstable:true estiver definido.

Definir 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 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 anterior, <base_path> é srcs/aidl.
  • local_include_dir: o caminho de onde 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 suas interfaces AIDL usar uma interface ou um parcelable de outro aidl_interface, coloque o nome dele aqui. Pode ser o nome por si só, 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 é 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 será especificado, e não haverá verificações de compatibilidade. Esse campo foi substituído por versions_with_info no Android 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ão de outros módulos aidl_interface que essa versão da aidl_interface importou. 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 deve ser modificado diretamente em Android.bp. O campo é adicionado ou atualizado ao invocar *-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 flag opcional para a promessa de estabilidade dessa interface. Isso só é compatível com "vintf". Se stability não estiver definido, o sistema de build vai verificar se a interface é compatível com versões anteriores, a menos que unstable seja especificado. Não definido corresponde a uma interface com estabilidade nesse contexto de compilação (ou seja, todas as coisas do sistema, por exemplo, coisas em system.img e partições relacionadas, ou todas as coisas do fornecedor, por exemplo, coisas em vendor.img e partições relacionadas). Se stability estiver definido como "vintf", isso corresponderá a uma promessa de estabilidade: a interface precisa ser mantida estável enquanto for usada.
  • gen_trace: a flag 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 flag opcional que, quando definida como true, disponibiliza as bibliotecas geradas para o ambiente host.
  • unstable: a flag opcional usada para marcar que essa interface não precisa ser estável. Quando isso é 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 flag opcional que, quando definida como true, significa que a interface não teve mudanças desde a versão anterior. Isso permite mais verificações no momento da build. Quando definido como false, isso significa que a interface está em desenvolvimento e tem novas mudanças. Portanto, executar foo-freeze-api gera uma nova versão e muda automaticamente o valor para true. Introduzido no Android 14.
  • backend.<type>.enabled: essas flags alternam cada um dos back-ends para os quais o compilador AIDL gera código. Quatro back-ends são compatíveis: 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 desativado explicitamente. O Rust fica desativado por padrão até o Android 15.
  • backend.<type>.apex_available: a lista de nomes do APEX para os quais a biblioteca de stub gerada está disponível.
  • backend.[cpp|java].gen_log: a flag opcional que controla se é necessário gerar código adicional para coletar informações sobre a transação.
  • backend.[cpp|java].vndk.enabled: a flag opcional para tornar essa interface parte do VNDK. O padrão é false.
  • backend.[cpp|ndk].additional_shared_libraries: lançada no Android 14, essa flag adiciona dependências às bibliotecas nativas. Essa flag é útil com ndk_header e cpp_header.
  • backend.java.sdk_version: a flag opcional para especificar a versão do SDK com que a biblioteca stub Java é criada. O padrão é "system_current". Não defina esse campo quando backend.java.platform_apis for true.
  • backend.java.platform_apis: a flag opcional que deve ser definida como true quando as bibliotecas geradas precisam ser criadas com base na API da plataforma em vez do SDK.

Para cada combinação das versões e dos back-ends ativados, 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 de módulos.

Escrever 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 eles não são estáveis. Consulte AIDL estruturada comparada à estável. A principal diferença no AIDL estável é como os parcelables são definidos. Antes, os parcelables eram declarados de encaminhamento. Na AIDL estável (e, portanto, estruturada), os campos e as 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 é aceito (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 vazio ou semelhante a zero é usado. Enumerações sem um valor padrão são inicializadas como 0, mesmo que não haja um enumerador zero.

Usar bibliotecas stub

Depois de adicionar bibliotecas stub como uma dependência ao seu módulo, você pode incluí-las nos seus arquivos. Confira exemplos de bibliotecas stub no sistema de build. O Android.mk também pode ser usado para definições de módulos legados. Nesses exemplos, a versão não está presente, o que representa o uso de uma interface instável. No entanto, os nomes de interfaces com versões incluem informações adicionais. Consulte Interfaces de controle de versões.

cc_... {
    name: ...,
    // use `shared_libs:` to load your library and its transitive dependencies
    // dynamically
    shared_libs: ["my-module-name-cpp"],
    // use `static_libs:` to include the library in this binary and drop
    // transitive dependencies
    static_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // use `static_libs:` to add all jars and classes to this jar
    static_libs: ["my-module-name-java"],
    // use `libs:` to make these classes available during build time, but
    // not add them to the jar, in case the classes are already present on the
    // boot classpath (such as if it's in framework.jar) or another jar.
    libs: ["my-module-name-java"],
    // use `srcs:` with `-java-sources` if you want to add classes in this
    // library jar directly, but you get transitive dependencies from
    // somewhere else, such as the boot classpath or another jar.
    srcs: ["my-module-name-java-source", ...],
    ...
}
# 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 Rust:

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

Interfaces de controle de versões

Declarar um módulo com o nome foo também cria um destino no sistema de build que pode ser usado 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 adicional e imports para a versão. Basicamente, imports em versions_with_info é copiado do campo imports. Mas 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 versões congeladas e também entre o Top of Tree (ToT) e a versão congelada mais recente.

Além disso, é necessário 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:

  • Métodos até o final de uma interface (ou métodos com novos seriais explicitamente definidos)
  • Elementos ao 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á risco de conflito com as mudanças feitas por um proprietário).

Para testar se todas as interfaces estã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 tenham 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 da AIDL estável. No entanto, para atualizar esses elementos, é necessário atualizar todos os servidores e clientes que usam uma versão anterior da interface. Além disso, alguns apps podem ficar confusos ao misturar diferentes versões de tipos. Em geral, para pacotes somente de tipos ou comuns, isso é seguro porque o código já precisa ser escrito para processar tipos desconhecidos de transações IPC.

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

Usar interfaces com controle de versão

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 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 foi implementada.

Para estratégias de como lidar com isso, consulte consultar versões e usar padrões.

Parcelables

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 precisam rejeitar ou ignorar valores constantes e enumeradores não reconhecidos, conforme apropriado, já que mais podem ser adicionados no futuro. Por exemplo, um servidor não deve ser interrompido quando recebe um enumerador desconhecido. O servidor precisa ignorar o enumerador ou retornar algo para que o cliente saiba que ele não é compatível com esta implementação.

União

Tentar enviar uma união com um novo campo falha se o receptor for antigo e não conhecer o campo. A implementação nunca vai ver a união com o novo campo. A falha é ignorada se for uma transação unidirecional. 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 definido para o novo campo a um servidor antigo ou quando um cliente antigo estiver recebendo a união de um novo servidor.

Gerenciar várias versões

Um namespace de vinculador no Android pode ter apenas uma versão de uma interface aidl específica para evitar situações em que os tipos aidl gerados têm várias definições. O C++ tem a Regra de uma definição, que exige apenas uma definição de cada símbolo.

O build do Android gera um erro quando um módulo depende de diferentes versões da mesma biblioteca aidl_interface. O módulo pode depender dessas bibliotecas direta ou indiretamente por meio de dependências das dependências. Esses erros mostram o gráfico de dependência do módulo com falha para as versões conflitantes da biblioteca aidl_interface. Todas as dependências precisam ser atualizadas para incluir a mesma versão (geralmente a mais recente) dessas bibliotecas.

Se a biblioteca de interface for usada por muitos módulos diferentes, pode ser útil criar cc_defaults, java_defaults e rust_defaults para qualquer grupo de bibliotecas e processos que precisem usar a mesma versão. Ao introduzir uma nova versão da interface, esses padrões podem ser atualizados, e todos os módulos que os usam são atualizados juntos, garantindo que não estejam usando versões diferentes da interface.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

Quando os módulos aidl_interface importam outros módulos aidl_interface, isso cria dependências adicionais que exigem o uso de versões específicas juntas. Essa situação pode ficar difícil de gerenciar quando há módulos aidl_interface comuns importados em vários módulos aidl_interface usados juntos nos mesmos processos.

O aidl_interfaces_defaults pode ser usado para manter uma definição das versões mais recentes de dependências para um aidl_interface que pode ser atualizado em um único lugar e usado por todos os módulos aidl_interface que querem importar essa interface comum.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Desenvolvimento baseado em flags

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

O AIDL oferece suporte a fallback de tempo de execução para essas bibliotecas de interface não congeladas para que o código seja gravado na versão não congelada mais recente e ainda seja usado em dispositivos de lançamento. O comportamento compatível com versões anteriores dos clientes é semelhante ao comportamento atual. Com o fallback, as implementações também precisam seguir esses comportamentos. Consulte Usar interfaces com controle de versão.

Flag de build da AIDL

A flag que controla esse comportamento é RELEASE_AIDL_USE_UNFROZEN definida em build/release/build_flags.bzl. true significa que a versão não congelada da interface é usada no tempo de execução, e false significa que as bibliotecas das versões não congeladas se comportam como a última versão congelada. É possível substituir a flag por true para desenvolvimento local, mas ela precisa ser revertida 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 não Cuttlefish tem como destino a matriz de compatibilidade mais recente somente depois que as interfaces são congeladas. Portanto, não há diferença nas bibliotecas AIDL com base 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. Assim, quando uma nova versão não congelada de uma interface é adicionada a uma matriz de compatibilidade, as versões congeladas anteriores precisam permanecer por RELEASE_AIDL_USE_UNFROZEN=false. Para resolver isso, use diferentes arquivos de matriz de compatibilidade para diferentes configurações de RELEASE_AIDL_USE_UNFROZEN ou permita as duas versões em um único arquivo de matriz de compatibilidade usado em todas as configurações.

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

Quando a versão 4 for congelada, você poderá remover a versão 3 da matriz de compatibilidade, porque a versão 4 congelada será usada quando RELEASE_AIDL_USE_UNFROZEN for false.

Manifestos

No Android 15, uma mudança em libvintf foi introduzida para modificar os arquivos de manifesto no momento da compilação com base no valor de RELEASE_AIDL_USE_UNFROZEN.

Os manifestos e fragmentos de manifesto declaram qual versão de uma interface um serviço implementa. Ao usar a versão mais recente não congelada de uma interface, o manifesto precisa ser atualizado para refletir essa nova versão. Quando RELEASE_AIDL_USE_UNFROZEN=false as entradas de manifesto são ajustadas por libvintf para refletir a mudança na biblioteca AIDL gerada. A versão é modificada da versão não congelada, 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 versões anteriores de cada versão congelada compatível. Quando RELEASE_AIDL_USE_UNFROZEN é false, os serviços sempre parecem a última versão congelada ou anterior. Por exemplo, chamar novos métodos descongelados retorna UNKNOWN_TRANSACTION, ou novos campos parcelable têm os valores padrão. Os clientes do framework Android precisam ser compatíveis com versões anteriores adicionais, mas esse é um novo detalhe para clientes de fornecedores e clientes de interfaces pertencentes a parceiros.

Mudanças na implementação da HAL

A maior diferença no desenvolvimento de HAL com base em flags é a necessidade de as implementações de HAL serem compatíveis com a última versão congelada para funcionar quando RELEASE_AIDL_USE_UNFROZEN é false. Considerar a compatibilidade com versões anteriores em implementações e código de dispositivo é um novo exercício. Consulte Usar interfaces com versões.

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

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 biblioteca da versão 4. Como a biblioteca V4 é baseada em uma versão não congelada da interface, ela se comporta como a última versão congelada, a 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 normalmente o caso em que UNKNOWN_TRANSACTION é retornado. Os clientes podem estar implementando duas versões diferentes de um callback com base na configuração de lançamento. Portanto, não é possível presumir que o cliente envie a versão mais recente, e novos métodos podem retornar isso. Isso é semelhante a como os clientes AIDL estáveis mantêm a compatibilidade com versões anteriores com servidores, conforme descrito em Usar interfaces com versões.

// 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();
  }
}

Os novos campos em tipos atuais (parcelable, enum, union) podem não existir ou conter os valores padrão quando RELEASE_AIDL_USE_UNFROZEN é false e os valores dos novos campos que um serviço tenta enviar são descartados ao sair do processo.

Os novos tipos adicionados nessa versão não congelada não podem ser enviados nem recebidos pela interface.

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

Use novos enumeradores apenas com a versão em que eles foram introduzidos, não com a versão anterior.

Normalmente, você usa foo->getInterfaceVersion() para saber qual versão a interface remota está usando. No entanto, com o suporte ao controle de versões baseado em flags, você implementa duas versões diferentes. Por isso, talvez seja necessário acessar a versão da interface atual. Para isso, extraia a versão da interface do objeto atual, por exemplo, this->getInterfaceVersion() ou os outros métodos para my_ver. Consulte Consultar a versão da interface do objeto remoto para mais informações.

Novas interfaces estáveis do VINTF

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

É 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 puder ser adicionado ao dispositivo condicionalmente, verifique se ele foi declarado com IServiceManager::isDeclared(). Se ele for declarado e não for registrado, o processo será interrompido. Se ele não for declarado, a expectativa é que o registro falhe.

Novas interfaces de extensão estáveis do VINTF

As novas interfaces de extensão não têm uma versão anterior para usar como alternativa e, como não são registradas com ServiceManager ou declaradas em manifestos VINTF, IServiceManager::isDeclared() não pode ser usado para determinar quando anexar a interface de extensão a outra interface.

A variável RELEASE_AIDL_USE_UNFROZEN pode ser usada para determinar se é necessário anexar a nova interface de extensão descongelada à interface atual para evitar o uso dela em dispositivos lançados. A interface precisa ser congelada para ser usada em dispositivos lançados.

Os testes do VTS vts_treble_vintf_vendor_test e vts_treble_vintf_framework_test detectam quando uma interface de extensão não congelada é usada em um dispositivo lançado e geram um erro.

Se a interface da extensão não for nova e tiver uma versão congelada anteriormente, ela vai voltar para essa versão, e não serão necessárias etapas extras.

Cuttlefish como ferramenta de desenvolvimento

Todos os anos, depois que a VINTF é congelada, ajustamos a matriz de compatibilidade de framework (FCM) target-level e o PRODUCT_SHIPPING_API_LEVEL do Cuttlefish para que reflitam os dispositivos lançados com a versão do ano seguinte. Ajustamos target-level e PRODUCT_SHIPPING_API_LEVEL para garantir que haja um dispositivo de lançamento testado e que atenda aos novos requisitos para a versão do ano que vem.

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

Quando RELEASE_AIDL_USE_UNFROZEN é false, o Cuttlefish tem 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 ramificações Git diferentes que não captam a mudança para o FCM target-level, o nível da API de envio ou qualquer outro código destinado à próxima versão.

Regras de nomenclatura de módulos

No Android 11, para cada combinação das versões e dos back-ends ativados, 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, em que

  • ifacename: nome do módulo aidl_interface
  • version é um dos seguintes:
    • Vversion-number para as versões congeladas
    • Vlatest-frozen-version-number + 1 para a versão tip-of-tree (ainda não congelada)
  • backend é um dos seguintes:
    • 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 apps, e o segundo é para uso da plataforma até o Android 13. No Android 13 e versões mais recentes, use apenas ndk.
    • rust para o backend do Rust.

Suponha que haja um módulo com o nome foo e que a versão mais recente 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 do ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Em comparação com o Android 11:

  • foo-backend, que se referia à versão estável mais recente, passa a ser foo-V2-backend
  • foo-unstable-backend, que se referia à versão do ToT, passa a ser foo-V3-backend

Os nomes dos arquivos de saída são sempre os mesmos 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

O compilador AIDL não cria um módulo de versão unstable nem um módulo sem versão para uma interface AIDL estável. No Android 12, o nome do módulo gerado de uma interface AIDL estável sempre inclui a versão.

Novos métodos de interface de metadados

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() da seguinte maneira (super é usado em vez de IFoo para evitar erros de copiar e 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 acontece 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 delas, mesmo que tenha 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 como acima, o número da versão da interface é incorporado ao código do servidor (porque IFoo.VERSION é um static final int que é inlinado quando referenciado) e, portanto, 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, é possível definir uma implementação padrão para uma interface AIDL. Um método na implementação padrão é invocado apenas quando 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 que têm implementação garantida no lado remoto (porque você tem certeza de que o 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 AIDL atual em AIDL estruturada ou estável

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

  1. Identifique todas as dependências da sua interface. Para cada pacote de que a interface depende, determine se ele está definido em AIDL estável. Se não for 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. Para isso, expresse a estrutura diretamente nos 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 (como descrito acima) que contenha o nome do módulo, as dependências dele e outras informações necessárias. Para que ele seja estabilizado (e não apenas estruturado), também é necessário ter controle de versão. Para mais informações, consulte Interfaces de controle de versões.