O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.
Esta página foi traduzida pela API Cloud Translation.
Switch to English

AIDL estável

O Android 10 adiciona suporte para Android Interface Definition Language (AIDL) estável, uma nova maneira de acompanhar a interface do programa do aplicativo (API) / interface binária do aplicativo (ABI) fornecida pelas interfaces AIDL. O AIDL estável tem as seguintes diferenças principais do AIDL:

  • As interfaces são definidas no sistema de construção com aidl_interfaces .
  • As interfaces podem conter apenas dados estruturados. Os pacotes que representam os tipos desejados são criados automaticamente com base em sua definição AIDL e são empacotados e desempacotados automaticamente.
  • As interfaces podem ser declaradas como estáveis ​​(compatíveis com versões anteriores). Quando isso acontece, sua API é rastreada e versionada em um arquivo próximo à interface AIDL.

Definindo uma interface AIDL

Uma definição de aidl_interface parece com isto:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
    },

}
  • name : O nome do módulo de interface AIDL que identifica exclusivamente uma interface AIDL.
  • srcs : A lista de arquivos de origem AIDL que compõem a interface. O caminho para um tipo AIDL Foo definido em um pacote com.acme deve estar em <base_path>/com/acme/Foo.aidl , onde <base_path> pode ser qualquer diretório relacionado ao diretório onde está Android.bp . 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 : A lista opcional de outros módulos de interface AIDL que esta interface deseja importar. Os tipos AIDL definidos nas interfaces AIDL importadas são acessíveis com a instrução import .
  • versions : as versões anteriores da interface que são congeladas em api_dir , começando no Android 11, as versions são congeladas em aidl_api/ name . Se não houver versões congeladas de uma interface, isso não deve ser especificado e não haverá verificações de compatibilidade.
  • stability : O sinalizador opcional para a promessa de estabilidade desta interface. Atualmente suporta apenas "vintf" . Se não estiver definido, isso corresponde a uma interface com estabilidade dentro deste contexto de compilação (portanto, uma interface carregada aqui só pode ser usada com coisas compiladas juntas, por exemplo em system.img). Se for definido como "vintf" , isso corresponde a uma promessa de estabilidade: a interface deve ser mantida estável enquanto for usada.
  • gen_trace : O sinalizador opcional para ativar ou desativar o rastreamento. O padrão é false .
  • host_supported : O sinalizador opcional que, quando definido como true torna as bibliotecas geradas disponíveis para o ambiente do host.
  • unstable : o sinalizador opcional usado para marcar que esta interface não precisa ser estável. Quando definido como true , o sistema de compilação não cria o dump da API para a interface nem requer que seja atualizado.
  • backend.<type>.enabled : Essas sinalizações alternam cada um dos back-ends para os quais o compilador AIDL gerará código. Atualmente, três back-ends são suportados: java , cpp e ndk . Os back-ends são todos habilitados por padrão. Quando um back-end específico não é necessário, ele precisa ser desativado explicitamente.
  • 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.java.platform_apis : O sinalizador opcional que controla se a biblioteca stub Java é construída em relação às APIs privadas da plataforma. Isso deve ser definido como "true" quando a stability for definida como "vintf" .
  • backend.java.sdk_version : O sinalizador opcional para especificar a versão do SDK em que 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 com base na API da plataforma em vez do SDK.

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

Gravando arquivos AIDL

As interfaces em AIDL estável são semelhantes às interfaces tradicionais, com a exceção de que não têm permissão para usar pacotes não estruturados (porque eles não são estáveis!). A principal diferença no AIDL estável é como os parcelables são definidos. Anteriormente, as encomendas eram declaradas antecipadamente ; em AIDL estável, os campos e variáveis ​​parcelables são definidos explicitamente.

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

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

Um padrão é atualmente suportado (mas não obrigatório) para boolean , char , float , double , byte , int , long e String . No Android S (AOSP experimental), os padrões para enumerações definidas pelo usuário também são suportados. Quando um padrão não é especificado, um valor igual a 0 ou vazio é usado. Enumerações sem um valor padrão são inicializadas com 0 mesmo se não houver um 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ódulo legado):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}

Exemplo em C ++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Exemplo em Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Interfaces de controle de versão

Declarar um módulo com o nome foo também cria um destino no sistema de construção que você pode usar para gerenciar a API do módulo. Quando construído, foo-freeze-api adiciona uma nova definição de API em api_dir ou aidl_api/ name , dependendo da versão do Android, para a próxima versão da interface. Construir isso também atualiza a propriedade de versions para refletir a versão adicional. Depois que a propriedade de versions é especificada, o sistema de compilação executa verificações de compatibilidade entre as versões congeladas e também entre a parte superior do tronco (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 é 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 parcelável (requer que um padrão seja adicionado para cada elemento)
  • Valores constantes
  • No Android 11, enumeradores
  • No Android S (AOSP experimental), campos para o final de uma união

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

Em tempo de execução, ao tentar chamar novos métodos em um servidor antigo, novos clientes obtêm UNKNOWN_TRANSACTION automaticamente. Para estratégias para lidar com isso, consulte consultar versões e usar padrões . Quando novos campos são adicionados aos parcelables, os clientes / servidores antigos os descartam. Quando novos clientes / servidores recebem pacotes antigos, os valores padrão para novos campos são preenchidos automaticamente. Isso significa que os padrões devem ser especificados para todos os novos campos em um pacote. Da mesma forma, os clientes / servidores devem rejeitar ou ignorar valores constantes não reconhecidos e enumeradores conforme apropriado, uma vez que mais podem ser adicionados no futuro. Tentar enviar um sindicato com um novo campo falha se o receptor é antigo e não conhece o campo. A falha é ignorada se for uma transação de mão única; caso contrário, o erro é BAD_VALUE (para o back-end C ++ ou NDK) ou IllegalArgumentException (para o back-end Java).

Regras de nomenclatura de módulos

No 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 é uma das
    • V version-number para as versões congeladas
    • unstable para a versão ponta da árvore (ainda a ser congelada)
  • backend é qualquer um dos
    • java para o back-end Java,
    • cpp para o back-end C ++,
    • ndk ou ndk_platform para o back-end NDK. O primeiro é para aplicativos e o último é para uso de plataforma.

Para a versão congelada mais recente, omita o campo de versão, exceto o destino Java. Em outras palavras, a module-V latest-frozen-version -(cpp|ndk|ndk_platform) não é gerada.

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

  • Baseado na versão 1
    • foo-V1-(java|cpp|ndk|ndk_platform)
  • Com base na versão 2 (a versão estável mais recente)
    • foo-(java|cpp|ndk|ndk_platform)
    • foo-V2-java (o conteúdo é idêntico ao foo-java)
  • Com base na versão ToT
    • foo-unstable-(java|cpp|ndk|ndk_platform)

Na maioria dos casos, os nomes dos arquivos de saída são iguais aos nomes dos módulos. No entanto, para a versão ToT de um módulo C ++ ou NDK, o nome do arquivo de saída é diferente do nome do módulo.

Por exemplo, o nome do arquivo de saída de foo-unstable-cpp é foo-V3-cpp.so , não foo-unstable-cpp.so como mostrado abaixo.

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

Novos métodos de meta interface

O Android 10 adiciona vários métodos de meta interface para o AIDL estável.

Consultando a versão da interface do objeto remoto

Os clientes podem consultar a versão e o hash da interface que o objeto remoto está implementando e comparar os valores retornados com os valores da interface que o cliente está usando.

Exemplo com o back-end cpp :

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Exemplo com o 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 DEVE implementar getInterfaceVersion() e getInterfaceHash() seguinte maneira:

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

    @Override
    public final String getInterfaceHash() { return IFoo.HASH; }
}

Isso ocorre porque as classes geradas ( IFoo , IFoo.Stub , etc.) são compartilhadas entre o cliente e o servidor (por exemplo, as classes podem estar no classpath de inicialização). Quando as classes são compartilhadas, o servidor também é vinculado à versão mais recente das classes, embora possa ter sido criado com uma versão mais antiga da interface. Se esta meta interface 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 é embutido 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 está 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 é chamado apenas 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 ++:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(std::unique_ptr<IFoo>(MyDefault));

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Exemplo em Java:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

Você não precisa fornecer a implementação padrão de todos os métodos em uma interface AIDL. Os métodos cuja implementação é garantida no lado remoto (porque você tem certeza de que o remoto é construído quando os métodos estavam na descrição da interface AIDL) não precisam ser substituídos na classe impl padrão.

Converter AIDL existente em AIDL estruturado / estável

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

  1. Identifique todas as dependências de sua interface. Para cada pacote do qual a interface depende, determine se o pacote está definido em AIDL estável. Se não for definido, o pacote deve ser convertido.

  2. Converta todos os pacotes em sua interface em pacotes estáveis ​​(os próprios arquivos de interface podem permanecer inalterados). Faça isso expressando sua estrutura diretamente em arquivos AIDL. As classes de gerenciamento devem ser reescritas para usar esses novos tipos. Isso pode ser feito antes de criar um pacote aidl_interface (abaixo).

  3. Crie um pacote aidl_interface (como descrito acima) que contém o nome do seu módulo, suas dependências e qualquer outra informação que você precisar. Para torná-lo estabilizado (não apenas estruturado), ele também precisa ser versionado. Para obter mais informações, consulte Interfaces de controle de versão .