É possível usar o formato de arquivo APEX para empacotar e instalar módulos do SO Android de nível inferior. Ele permite a criação e instalação independentes de componentes, como serviços e bibliotecas nativas, implementações HAL, firmware, arquivos de configuração etc.
Os APEXes do fornecedor são instalados automaticamente pelo sistema de build na partição /vendor
e ativados no momento de execução por apexd
, assim como os APEXes em outras
partições.
Casos de uso
Modularização de imagens do fornecedor
Os APEXes facilitam o agrupamento e a modularização naturais das implementações de recursos nas imagens do fornecedor.
Quando as imagens do fornecedor são criadas como uma combinação de APEXes de fornecedores criados de forma independente, os fabricantes de dispositivos podem escolher facilmente as implementações específicas de fornecedores que querem no dispositivo. Os fabricantes podem até criar um novo APEX do fornecedor se nenhum dos APEXs fornecidos atender às necessidades deles ou se eles tiverem um hardware personalizado totalmente novo.
Por exemplo, um OEM pode escolher compor o dispositivo com o APEX de implementação de Wi-Fi do AOSP, o APEX de implementação de Bluetooth do SoC e um APEX de implementação de telefonia OEM personalizado.
Sem os APEXs do fornecedor, uma implementação com tantas dependências entre componentes do fornecedor exige uma coordenação e um acompanhamento cuidadosos. Ao agrupar 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 rapidamente ao desenvolver módulos do fornecedor agrupando uma implementação de recurso inteira, como o HAL do Wi-Fi, dentro de um APEX do fornecedor. Os desenvolvedores podem criar e enviar individualmente o APEX do fornecedor para testar as 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 recurso e querem iterar apenas nessa área de recurso.
O agrupamento natural de uma área de recurso em um APEX também simplifica o processo de criação, envio e teste de mudanças nessa área. Por exemplo, reinstalar um APEX atualiza automaticamente todas as bibliotecas ou arquivos de configuração que o APEX inclui.
O agrupamento de uma área de recurso em um APEX também simplifica a depuração ou reversão quando um comportamento incorreto 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 verificar se o comportamento correto é 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 genéricas 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
faz com que um módulo APEX seja um
APEX do fornecedor.
apex {
..
vendor: true,
..
}
Binários e bibliotecas compartilhadas
Um APEX inclui dependências transitivas dentro do payload do APEX, a menos que tenha interfaces estáveis.
Interfaces nativas estáveis para dependências de APEX do fornecedor incluem cc_library
com
bibliotecas stubs
e LLNDK. Essas dependências são excluídas da
embalagem e são registradas no manifesto do APEX. O manifesto é
processado por linkerconfig
para que as dependências nativas externas estejam
disponíveis durante a execução.
No snippet abaixo, 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 (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
o uso de 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á incluída no APEX.
Implementações de HAL
Para definir uma implementação do HAL, forneça os binários e as bibliotecas correspondentes dentro de um APEX do fornecedor semelhante aos exemplos abaixo:
Para encapsular totalmente a implementação do HAL, o APEX também precisa especificar todos os fragmentos e scripts de inicialização relevantes do VINTF.
Fragmentos VINTF
Os fragmentos 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 VINTF no APEX.
apex {
..
vendor: true,
prebuilts: ["fragment.xml"],
..
}
prebuilt_etc {
name: "fragment.xml",
src: "fragment.xml",
sub_dir: "vintf",
}
Consultar APIs
Quando fragmentos VINTF são adicionados ao APEX, use 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 do HAL for definida no APEX.AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...)
: pega o nome do APEX que define a instância do HAL.AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...)
: use 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 de on <property or event>
.
Verifique se uma definição 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 após a ativação dos APEXes, alguns eventos ou propriedades
não podem ser usados. Use apex.all.ready=true
para acionar ações o mais cedo possível.
Os APEXes 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
, conforme
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 todas as entradas de payload do firmware
corretamente para garantir que esses arquivos sejam acessíveis por ueventd
no momento da execução.
Normalmente, o marcador vendor_file
é suficiente. Exemplo:
(/.*)? u:object_r:vendor_file:s0
Módulos do kernel
Incorpore módulos de kernel em um APEX do fornecedor como módulos pré-criados, conforme mostrado a seguir.
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 de script de inicialização abaixo
na partição do fornecedor mostra a instalação por 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 sobreposições de recursos 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 do fornecedor oferecem suporte a vários outros arquivos de configuração normalmente encontrados na partição do fornecedor como pré-criados dentro dos APEXs do fornecedor, e mais estão sendo adicionados.
Exemplos:
- XMLs de declaração de recursos
- Os sensores apresentam XMLs pré-criados em um APEX do fornecedor de HAL de sensores.
- Arquivos de configuração de entrada
- Configurações de tela touch como pré-criadas em um APEX de fornecedor somente de configuração
APEXes do fornecedor de inicialização
Alguns serviços HAL, como keymint
, precisam estar disponíveis antes que os APEX sejam
ativados. Esses HALs geralmente definem early_hal
na definição do serviço no
script de inicialização. Outro exemplo é a classe animation
, que normalmente é iniciada
antes do evento post-fs-data
. Quando esse serviço HAL inicial é
empacotado no APEX do fornecedor, faça com que o "vendorBootstrap": true
do APEX
seja o principal para que ele possa ser ativado mais cedo. Os APEXes de inicialização só podem ser
ativados no local pré-criado, como /vendor/apex
, e não em
/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 Vulkan é usado por HALs iniciais, torne o APEX Bootstrap APEX e configure esse namespace de linker para ficar visível.
Defina as propriedades do sistema em scripts de inicialização usando o comando
setprop
.
Recursos extras de desenvolvimento
Seleção do APEX na inicialização
Exemplo:
Os desenvolvedores também podem instalar várias versões de APEXes de fornecedores que compartilham o
mesmo nome e chave de APEX e, em seguida, escolher qual versão será ativada durante cada
inicialização usando sysprops persistentes. Para alguns casos de uso de desenvolvedores, isso pode ser
mais simples do que instalar uma nova cópia do APEX usando adb install
.
Exemplos de casos de uso:
- Instalar três versões do APEX do fornecedor do HAL do Wi-Fi:as equipes de controle de qualidade podem executar testes manuais ou automatizados usando uma versão, reinicializar para outra versão e realizar os testes novamente, comparando os resultados finais.
- Instale duas versões do APEX do fornecedor do HAL da câmera, atual e experimental: os dogfooders podem usar a versão experimental sem fazer o download e instalar um arquivo extra, para que possam alternar facilmente.
Durante a inicialização, o apexd
procura sysprops seguindo um formato específico para
ativar a versão APEX correta.
Os formatos esperados para a chave de 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 permanente
- 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 de arquivo do APEX que será ativado.
// 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 o 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 o 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 oferecer suporte à atualização do bootconfig após a atualização flash (por exemplo, com comandos fastboot
oem
), a alteração da propriedade bootconfig para o APEX
instalado várias vezes 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";