É possível usar o formato de arquivo APEX para empacotar e instalar módulos de nível mais baixo do SO Android. Ele permite a criação e instalação independentes de componentes como serviços e bibliotecas nativas, implementações de HAL, firmware, arquivos de configuração etc.
Os APEXs do fornecedor são instalados automaticamente pelo sistema de build na partição /vendor
e ativados no tempo de execução por apexd
, assim como os APEXs em outras partições.
Casos de uso
Modularização de imagens de fornecedores
Os APEXs facilitam um agrupamento e uma modularização naturais de implementações de recursos em imagens de fornecedores.
Quando as imagens do fornecedor são criadas como uma combinação de APEXes do fornecedor criados de forma independente, os fabricantes de dispositivos podem escolher facilmente as implementações específicas do fornecedor que querem no dispositivo. Os fabricantes podem até criar um novo APEX de fornecedor se nenhum dos APEXes fornecidos atender às necessidades deles ou se tiverem um hardware personalizado totalmente novo.
Por exemplo, um OEM pode optar por compor o dispositivo com o APEX de implementação Wi-Fi do AOSP, o APEX de implementação Bluetooth do SoC e um APEX de implementação de telefonia personalizada do OEM.
Sem os APEXes do fornecedor, uma implementação com tantas dependências entre componentes do fornecedor exige coordenação e rastreamento cuidadosos. Ao encapsular todos os componentes (incluindo arquivos de configuração e bibliotecas extras) em APEXes com interfaces claramente definidas em qualquer ponto de comunicação entre recursos, os diferentes componentes se tornam intercambiáveis.
Iteração do desenvolvedor
Os APEXs do fornecedor ajudam os desenvolvedores a iterar mais rápido ao desenvolver módulos do fornecedor, agrupando uma implementação completa de recursos, como o HAL Wi-Fi, em um APEX do fornecedor. Em seguida, os desenvolvedores podem criar e enviar individualmente o APEX do fornecedor para testar mudanças, em vez de recriar toda a imagem do fornecedor.
Isso simplifica e acelera o ciclo de iteração para desenvolvedores que trabalham principalmente em uma área de recursos e querem iterar apenas nessa área.
O agrupamento natural de uma área de recursos em um APEX também simplifica o processo de criação, envio e teste de mudanças nessa área. Por exemplo, a reinstalação de um APEX atualiza automaticamente qualquer biblioteca ou arquivo de configuração incluído no APEX.
Agrupar uma área de recursos em um APEX também simplifica a depuração ou a reversão quando um comportamento inadequado do dispositivo é observado. Por exemplo, se a telefonia estiver funcionando mal em um novo build, os desenvolvedores poderão tentar instalar um APEX de implementação de telefonia mais antigo em um dispositivo (sem precisar atualizar um build completo) e ver se o bom comportamento é restaurado.
Exemplo de fluxo de trabalho:
# Build the entire device and flash. OR, obtain an already-flashed device.
source build/envsetup.sh && lunch oem_device-userdebug
m
fastboot flashall -w
# Test the device.
... testing ...
# Check previous behavior using a vendor APEX from one week ago, downloaded from
# your continuous integration build.
... download command ...
adb install <path to downloaded APEX>
adb reboot
... testing ...
# Edit and rebuild just the APEX to change and test behavior.
... edit APEX source contents ...
m <apex module name>
adb install out/<path to built APEX>
adb reboot
... testing ...
Exemplos
Noções básicas
Consulte a página principal Formato de arquivo APEX para informações gerais sobre o APEX, incluindo requisitos do dispositivo, detalhes do formato de arquivo e etapas de instalação.
Em Android.bp
, definir a propriedade vendor: true
transforma um módulo APEX em um
APEX do fornecedor.
apex {
..
vendor: true,
..
}
Binários e bibliotecas compartilhadas
Um APEX inclui dependências transitivas no payload, a menos que elas tenham interfaces estáveis.
As interfaces nativas estáveis para dependências do APEX do fornecedor incluem cc_library
com
bibliotecas stubs
e LLNDK. Essas dependências são excluídas do
pacote, e as dependências são registradas no manifesto do APEX. O manifesto é
processado pelo linkerconfig
para que as dependências nativas externas fiquem
disponíveis durante a execução.
No snippet a seguir, o APEX contém o binário (my_service
) e as dependências
não estáveis (arquivos *.so
).
apex {
..
vendor: true,
binaries: ["my_service"],
..
}
No snippet a seguir, o APEX contém a biblioteca compartilhada
my_standalone_lib
e qualquer uma das dependências não estáveis dela (conforme descrito acima).
apex {
..
vendor: true,
native_shared_libs: ["my_standalone_lib"],
..
}
Diminuir o APEX
O APEX pode ficar maior porque agrupa dependências não estáveis. Recomendamos
usar a vinculação estática. Bibliotecas comuns, como libc++.so
e libbase.so
, podem ser vinculadas estaticamente a binários HAL. Criar uma dependência para fornecer uma interface estável pode ser outra opção. A dependência não será agrupada no APEX.
Implementações de HAL
Para definir uma implementação de HAL, forneça os binários e as bibliotecas correspondentes em um APEX do fornecedor semelhante aos exemplos a seguir:
Para encapsular totalmente a implementação da HAL, o APEX também precisa especificar todos os fragmentos VINTF e scripts de inicialização relevantes.
Fragmentos do VINTF
Os fragmentos do VINTF podem ser veiculados de um APEX do fornecedor quando estão localizados em
etc/vintf
do APEX.
Use a propriedade prebuilts
para incorporar os fragmentos do VINTF no APEX.
apex {
..
vendor: true,
prebuilts: ["fragment.xml"],
..
}
prebuilt_etc {
name: "fragment.xml",
src: "fragment.xml",
sub_dir: "vintf",
}
APIs de consulta
Quando os fragmentos VINTF são adicionados ao APEX, use as APIs libbinder_ndk
para receber os
mapeamentos de interfaces HAL e nomes APEX.
AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default")
:true
se a instância HAL estiver definida no APEX.AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...)
: recebe o nome do APEX que define a instância da HAL.AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...)
: use isso para abrir um HAL de passagem.
Scripts de inicialização
Os APEXs podem incluir scripts de inicialização de duas maneiras: (A) um arquivo de texto pré-criado no payload do APEX ou (B) um script de inicialização regular em /vendor/etc
. É possível definir os dois para o mesmo APEX.
Script de inicialização no APEX:
prebuilt_etc {
name: "myinit.rc",
src: "myinit.rc"
}
apex {
..
vendor: true,
prebuilts: ["myinit.rc"],
..
}
Os scripts de inicialização em APEXes de fornecedores podem ter definições de service
e
diretivas on <property or event>
.
Verifique se uma definição de service
aponta para um binário no mesmo APEX.
Por exemplo, o com.android.foo
APEX pode definir um serviço chamado foo-service
.
on foo-service /apex/com.android.foo/bin/foo
...
Tenha cuidado ao usar diretivas on
. Como os scripts de inicialização em APEXes são analisados e executados depois que os APEXes são ativados, alguns eventos ou propriedades não podem ser usados. Use apex.all.ready=true
para acionar ações o mais rápido possível.
Os APEXs de inicialização podem usar on init
, mas não on early-init
.
Firmware
Exemplo:
Incorpore o firmware em um APEX do fornecedor com o tipo de módulo prebuilt_firmware
, como
a seguir.
prebuilt_firmware {
name: "my.bin",
src: "path_to_prebuilt_firmware",
vendor: true,
}
apex {
..
vendor: true,
prebuilts: ["my.bin"], // installed inside APEX as /etc/firmware/my.bin
..
}
Os módulos prebuilt_firmware
são instalados no diretório <apex name>/etc/firmware
do APEX. O ueventd
verifica os diretórios /apex/*/etc/firmware
para
encontrar módulos de firmware.
O file_contexts
do APEX precisa rotular corretamente todas as entradas de payload de firmware para garantir que esses arquivos sejam acessíveis pelo ueventd
no momento da execução. Normalmente, o rótulo vendor_file
é suficiente. Exemplo:
(/.*)? u:object_r:vendor_file:s0
Módulos do kernel
Incorpore módulos do kernel em um APEX do fornecedor como módulos pré-criados, da seguinte maneira.
prebuilt_etc {
name: "my.ko",
src: "my.ko",
vendor: true,
sub_dir: "modules"
}
apex {
..
vendor: true,
prebuilts: ["my.ko"], // installed inside APEX as /etc/modules/my.ko
..
}
O file_contexts
do APEX precisa rotular corretamente todas as entradas de payload do módulo do kernel. Exemplo:
/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0
Os módulos do kernel precisam ser instalados explicitamente. O exemplo a seguir de script de inicialização
na partição do fornecedor mostra a instalação via insmod
:
my_init.rc
:
on early-boot
insmod /apex/myapex/etc/modules/my.ko
..
Sobreposições de recursos de tempo de execução
Exemplo:
Incorpore substituições de recursos de tempo de execução em um APEX do fornecedor
usando a propriedade rros
.
runtime_resource_overlay {
name: "my_rro",
soc_specific: true,
}
apex {
..
vendor: true,
rros: ["my_rro"], // installed inside APEX as /overlay/my_rro.apk
..
}
Outros arquivos de configuração
Os APEXs de fornecedor oferecem suporte a vários outros arquivos de configuração normalmente encontrados na partição do fornecedor como pré-compilados dentro dos APEXs de fornecedor, e mais estão sendo adicionados.
Exemplos:
- XMLs de declaração de recursos
- Os sensores apresentam XMLs como pré-compilados em um APEX do fornecedor da HAL de sensor.
- Arquivos de configuração de entrada
- Configurações de tela sensível ao toque como pré-criados em um APEX do fornecedor somente de configuração
APEXes de fornecedor de inicialização
Alguns serviços HAL, como keymint
, precisam estar disponíveis antes da ativação dos APEXs. Essas HALs geralmente definem early_hal
na definição de serviço no
script de inicialização. Outro exemplo é a classe animation
, que geralmente é iniciada
antes do evento post-fs-data
. Quando um serviço HAL inicial é
empacotado em um APEX do fornecedor, faça com que o apex "vendorBootstrap": true
no manifesto
APEX possa ser ativado antes. Os APEXs de inicialização só podem ser
ativados do local pré-criado, como /vendor/apex
, e não de
/data/apex
.
Propriedades do sistema
Estas são as propriedades do sistema que o framework lê para oferecer suporte a APEXes do fornecedor:
input_device.config_file.apex=<apex name>
: quando definido, os arquivos de configuração de entrada (*.idc
,*.kl
e*.kcm
) são pesquisados no diretório/etc/usr
do APEX.ro.vulkan.apex=<apex name>
: quando definido, o driver Vulkan é carregado do APEX. Como o driver do Vulkan é usado por HALs iniciais, crie o Bootstrap APEX do APEX e configure esse namespace do vinculador como visível.
Defina as propriedades do sistema em scripts de inicialização usando o comando setprop
.
Recursos extras
Seleção do APEX na inicialização
Exemplo:
Os APEXs do fornecedor podem ser ativados opcionalmente durante a inicialização.
Se você especificar um nome de arquivo usando a propriedade do sistema
ro.vendor.apex.<apex name>
, somente o APEX correspondente ao nome do arquivo será
ativado para o <apex name>
específico.
O APEX com <apex name>
é ignorado (não ativado) se essa propriedade do sistema
for definida como none
. Você pode usar esse recurso para instalar várias cópias de
um APEX com o mesmo nome. Se houver várias versões do mesmo APEX, elas vão precisar compartilhar a mesma chave.
Exemplos de casos de uso:
- Instale três versões do APEX do fornecedor do HAL Wi-Fi:as equipes de controle de qualidade podem executar testes manuais ou automatizados usando uma versão, reiniciar em outra versão e executar os testes novamente, comparando os resultados finais.
- Instale duas versões do APEX do fornecedor HAL da câmera, atual e experimental: os usuários internos podem usar a versão experimental sem baixar e instalar um arquivo adicional, facilitando a troca de volta.
Durante a inicialização, o apexd
procura sysprops seguindo um formato específico para
ativar a versão correta do APEX.
Os formatos esperados para a chave da propriedade são:
- Bootconfig
- Usado para definir o valor padrão em
BoardConfig.mk
. androidboot.vendor.apex.<apex name>
- Usado para definir o valor padrão em
- Sysprop persistente
- Usado para mudar o valor padrão, definido em um dispositivo já inicializado.
- Substitui o valor de bootconfig, se presente.
persist.vendor.apex.<apex name>
O valor da propriedade precisa ser o nome do arquivo do APEX que será
ativado ou none
para desativar o APEX.
// Default version.
apex {
name: "com.oem.camera.hal.my_apex_default",
vendor: true,
..
}
// Non-default version.
apex {
name: "com.oem.camera.hal.my_apex_experimental",
vendor: true,
..
}
A versão padrão também precisa ser configurada usando bootconfig em
BoardConfig.mk
:
# Example for APEX "com.oem.camera.hal" with the default above:
BOARD_BOOTCONFIG += \
androidboot.vendor.apex.com.oem.camera.hal=com.oem.camera.hal.my_apex_default
Depois que o dispositivo for inicializado, mude a versão ativada definindo a sysprop persistente:
$ adb root;
$ adb shell setprop \
persist.vendor.apex.com.oem.camera.hal \
com.oem.camera.hal.my_apex_experimental;
$ adb reboot;
Se o dispositivo for compatível com a atualização do bootconfig após a atualização (por exemplo, usando comandos fastboot
oem
), mudar a propriedade do bootconfig para o APEX
multi-instalado também mudará a versão ativada na inicialização.
Para dispositivos de referência virtuais baseados no Cuttlefish,
use o comando --extra_bootconfig_args
para definir a propriedade bootconfig
diretamente durante a inicialização. Exemplo:
launch_cvd --noresume \
--extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";