O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

AIDL estável

O Android 10 adiciona suporte para Android Interface Definition Language (AIDL) estável, uma nova maneira de controlar 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:

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

Definindo uma interface AIDL

A definição de aidl_interface parece com isso:

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 exclusivamente identica 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 deve ser de <base_path>/com/acme/Foo.aidl , onde <base_path> pode ser qualquer diretório relacionado ao diretório onde Android.bp é. No exemplo acima, <base_path> é srcs/aidl .
  • local_include_dir : O caminho a partir de onde o nome do pacote é iniciada. Corresponde a <base_path> explicado acima.
  • imports : Uma lista de aidl_interface módulos que este usos. Se uma das suas interfaces AIDL usa uma interface ou um parcelable de outro aidl_interface , coloque seu nome aqui. Este pode ser o nome por si só, para se referir à versão mais recente, ou o nome com a versão sufixo (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 são congelados sob api_dir , a partir de Android 11, as versions são congelados sob 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 : A bandeira opcional para a promessa 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 (então uma interface carregada aqui só pode ser usada com coisas compiladas juntas, por exemplo em system.img). Se isso for definido como "vintf" , isto corresponde a uma promessa de estabilidade: a interface deve ser mantido estável, enquanto ele é usado.
  • gen_trace : A bandeira opcional para ligar o traçado ligado ou desligado. O padrão é false .
  • host_supported : A bandeira opcional que quando definido como true marcas das bibliotecas geradas disponíveis para o ambiente de host.
  • unstable : A bandeira opcional usado para marcar que esta interface não precisa ser estável. Quando isso for definido como true , o sistema de compilação não cria o despejo API para a interface, nem requer que ele seja atualizado.
  • backend.<type>.enabled : Esses sinalizadores alternar a cada um dos backends que o compilador AIDL irá gerar o código para. Atualmente, três backends são suportados: java , cpp , e ndk . Os back-ends estã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 que a biblioteca stub gerado está disponível para.
  • backend.[cpp|java].gen_log : A bandeira opcional que controla se para gerar o código adicional para reunir informações sobre a transação.
  • backend.[cpp|java].vndk.enabled : A bandeira opcional para tornar esta interface parte VNDK. O padrão é false .
  • backend.java.platform_apis : A bandeira opcional que controla se a biblioteca Java stub é construída contra as APIs privadas da plataforma. Este deve ser definido como "true" quando stability é definida como "vintf" .
  • backend.java.sdk_version : A bandeira opcional para especificar a versão do SDK que a biblioteca Java stub é construída contra. O padrão é "system_current" . Isso não deve ser definido quando backend.java.platform_apis é verdade.
  • backend.java.platform_apis : A bandeira opcional que deve ser definido como true quando as bibliotecas geradas necessidade de construir contra a API plataforma ao invés do SDK.

Para cada combinação das versions e os back-ends habilitados, uma biblioteca de stub é criado. Veja Módulo regras de nomenclatura para saber como se referir à versão específica da biblioteca de stub para um backend 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, parcelables foram para a frente declarado; em AIDL estável, os campos e variáveis ​​parcelables são definidos explicitamente.

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

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

Um padrão é suportado (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 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 exemplos de bibliotecas stub no sistema de compilação ( Android.mk também pode ser usado para as definições do módulo de 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

Declarando um módulo com o nome foo também cria um alvo no sistema de compilação que você pode usar para gerenciar a API do módulo. Quando construído, foo-freeze-api adiciona uma nova definição API sob api_dir ou aidl_api/ name , dependendo da versão do Android, e acrescenta um .hash arquivo, ambos representando a nova versão congelada da interface. Construir este também atualiza o versions propriedade para refletir a versão adicional. Uma vez que o versions propriedade é especificada, o sistema de compilação é executado verificações de compatibilidade entre as versões congeladas e também entre Topo da Árvore (TOT) e a versão mais recente congelado.

Além disso, você precisa gerenciar a definição da API da versão do ToT. Sempre que uma API é atualizado, executar foo-update-api para atualização aidl_api/ name /current que contém definição API do ToT versão.

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 12, 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).

Usando interfaces com versão

Métodos de interface

Em tempo de execução, ao tentar chamar novos métodos em um servidor antigo, novos clientes obtêm automaticamente UNKNOWN_TRANSACTION . Para estratégias para lidar com isso ver consultando versões e usando os padrões .

Parcelables

Quando novos campos são adicionados aos parcelables, clientes e servidores antigos os descartam. Quando novos clientes e servidores recebem pacotes 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 pacote.

Os clientes não devem esperar servidores para usar os novos campos, a menos que eles sabem o servidor está implementando a versão que tem o campo definido (ver versões consultando ).

Enums e constantes

Da mesma forma, clientes e servidores devem rejeitar ou ignorar valores constantes não reconhecidos e enumeradores conforme apropriado, uma vez que mais podem ser adicionados no futuro. Por exemplo, um servidor não deve abortar quando recebe um enumerador que não conhece. Ele deve ignorá-lo ou retornar algo para que o cliente saiba que não há suporte para esta implementação.

Sindicatos

Tentar enviar um sindicato com um novo campo falha se o receptor é antigo e não conhece o campo. A implementação nunca verá a união com o novo campo. A falha é ignorada se for uma transação de mão única; caso contrário, o erro é BAD_VALUE (para o C ++ ou NDK back-end) ou IllegalArgumentException (para o servidor Java). O erro é recebido se o cliente está enviando um conjunto de união para o novo campo para um servidor antigo, ou quando é um cliente antigo recebendo o conjunto de união de um novo servidor.

Regras de nomenclatura de módulo

No Android 11, para cada combinação de versões e back-ends habilitados, um módulo de biblioteca stub é criado automaticamente. Para se referir a um módulo de biblioteca stub específica para ligar, não use o nome do aidl_interface módulo, mas o nome do módulo de biblioteca stub, que é ifacename - version - backend , onde

  • ifacename : nome do aidl_interface módulo
  • version é qualquer um
    • V version-number para a versão congelados
    • V latest-frozen-version-number + 1 para a versão de ponta-de-árvore (ainda-a-ser-congelado)
  • backend ou é de
    • java para o backend Java,
    • cpp para backend C ++,
    • ndk ou ndk_platform para o backend NDK. O primeiro é para aplicativos e o último é para uso de plataforma.

Suponha que há um módulo com o nome foo e sua versão mais recente é 2, e suporta tanto NDK e C ++. Neste 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-V2-(java|cpp|ndk|ndk_platform)
  • Com base na versão ToT
    • foo-V3-(java|cpp|ndk|ndk_platform)

Comparado ao Android 11,

  • foo- backend , que se referiu à última versão estável torna-se foo- V2 - backend
  • foo-unstable- backend , que se refere à 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).so
  • Com base na versão 2: foo-V2-(cpp|ndk|ndk_platform).so
  • Com base na versão ToT: foo-V3-(cpp|ndk|ndk_platform).so

Note que o compilador AIDL não cria tanto uma unstable módulo versão, ou um módulo sem número de versão para uma interface estável AIDL. 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 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 cpp backend:

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 ndk (e o ndk_platform ) backend:

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 java backend:

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Para a linguagem Java, o lado remoto deve implementar getInterfaceVersion() e getInterfaceHash() da seguinte forma:

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 compartilhados entre o cliente eo 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 essa meta interface for implementada na classe compartilhada, ela sempre retornará a versão mais recente. No entanto, através da aplicação do método acima, o número da versão da interface está embutido no código do servidor (porque IFoo.VERSION é um static final int que é inlined quando referenciados) e, portanto, o método pode retornar a versão exata do 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 um velho de interface 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. Métodos que são garantidos para ser implementado no lado remoto (porque você tem certeza de que o controle remoto é construído quando os métodos estavam na descrição da interface AIDL) não precisa ser substituído na default impl classe.

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 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. Isso pode ser feito antes de criar um aidl_interface pacote (abaixo).

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