Fornecedor APEX

Você pode usar o formato de arquivo APEX para empacotar e instalar módulos do SO Android de nível inferior. Ele permite a construção e instalação independente de componentes como serviços e bibliotecas nativas, implementações de HAL, firmware, arquivos de configuração, etc.

Os APEXes do fornecedor são instalados pelo sistema de compilação automaticamente na partição /vendor e ativados em tempo de execução pelo apexd assim como os APEXes em outras partições.

Casos de uso

Modularização de imagens de fornecedores

Os APEXes facilitam um agrupamento natural e modularização de implementações de recursos em imagens de fornecedores.

Quando as imagens de fornecedores são criadas como uma combinação de APEXs de fornecedores criados de forma independente, os fabricantes de dispositivos podem escolher facilmente as implementações de fornecedores específicas desejadas em seus dispositivos. Os fabricantes podem até criar um novo APEX de fornecedor se nenhum dos APEX fornecidos atender às suas necessidades ou se eles tiverem um novo hardware personalizado.

Por exemplo, um OEM pode optar por compor seu dispositivo com o APEX de implementação wifi AOSP, o APEX de implementação de Bluetooth SoC e um APEX de implementação de telefonia OEM personalizado.

Sem APEXs de fornecedores, uma implementação com tantas dependências entre componentes de fornecedores requer coordenação e 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 de fornecedores ajudam os desenvolvedores a iterar mais rapidamente enquanto desenvolvem módulos de fornecedores agrupando toda uma implementação de recurso, como o wifi HAL, dentro de um APEX de fornecedor. Os desenvolvedores podem então criar e enviar individualmente o APEX do fornecedor para testar as alterações, em vez de reconstruir toda a imagem do fornecedor.

Isso simplifica e acelera o ciclo de iteração do desenvolvedor para desenvolvedores que trabalham principalmente em uma área de recurso e desejam 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 alterações para essa área de recurso. Por exemplo, a reinstalação de um APEX atualiza automaticamente qualquer biblioteca ou arquivos de configuração incluídos no APEX.

Agrupar uma área de recurso em um APEX também simplifica a depuração ou a reversão quando um mau comportamento do dispositivo é observado. Por exemplo, se a telefonia estiver funcionando mal em uma nova compilação, os desenvolvedores podem tentar instalar um APEX de implementação de telefonia mais antigo em um dispositivo (sem precisar atualizar uma compilação completa) e verificar se o bom comportamento é restaurado.

Fluxo de trabalho de exemplo:

# 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

Fundamentos

Consulte a página principal do formato de arquivo do APEX para obter informações genéricas do APEX, incluindo requisitos do dispositivo, detalhes do formato de arquivo e etapas de instalação.

Em Android.bp , definir a propriedade vendor: true torna um módulo APEX um APEX de fornecedor.

apex {
  ..
  vendor: true,
  ..
}

Binários e bibliotecas compartilhadas

Um APEX inclui dependências transitivas dentro da carga útil do APEX, a menos que tenham interfaces estáveis.

As interfaces nativas estáveis ​​para dependências do APEX do fornecedor incluem cc_library com stubs , ndk_library ou llndk_library . Essas dependências são excluídas do empacotamento e as dependências são registradas no manifesto do APEX. O manifesto é processado pelo linkerconfig para que as dependências nativas externas estejam disponíveis em tempo de execução.

Ao contrário dos APEXes na partição /system , os APEXs do fornecedor geralmente estão vinculados a uma versão específica do VNDK. As bibliotecas VNDK garantem a estabilidade da ABI na versão, portanto, podemos tratar as bibliotecas VNDK como estáveis ​​e reduzir o tamanho dos APEXes de fornecedores excluindo-os dos APEXes usando a propriedade use_vndk_as_stable .

No trecho abaixo, o APEX conterá o binário ( my_service ) e suas dependências não estáveis ​​( arquivos *.so ). Ele não conterá bibliotecas VNDK, mesmo quando my_service for compilado com bibliotecas VNDK como libbase . Em vez disso, em tempo de execução, my_service usará libbase das bibliotecas VNDK fornecidas pelo sistema.

apex {
  ..
  vendor: true,
  use_vndk_as_stable: true,
  binaries: ["my_service"],
  ..
}

No trecho abaixo, o APEX conterá a biblioteca compartilhada my_standalone_lib e qualquer uma de suas dependências não estáveis ​​(conforme descrito acima).

apex {
  ..
  vendor: true,
  use_vndk_as_stable: true,
  native_shared_libs: ["my_standalone_lib"],
  ..
}

Implementações HAL

Para definir uma implementação de HAL, forneça os binários e bibliotecas correspondentes dentro de um APEX de fornecedor semelhante aos exemplos a seguir:

Para encapsular totalmente a implementação HAL, o APEX também deve especificar quaisquer fragmentos VINTF relevantes e scripts de inicialização.

fragmentos VINTF

Os fragmentos VINTF são instalados em /vendor/etc/vintf , em vez de contidos no APEX.

Use a propriedade padrão vintf_fragments :

apex {
  ..
  vendor: true,
  vintf_fragments: ["fragment.xml"],
  ..
}

Scripts de inicialização

Os APEXes podem incluir scripts de inicialização de duas maneiras: (A) um arquivo de texto pré-construído dentro da carga útil do APEX ou (B) um script de inicialização regular em /vendor/etc . Você pode definir ambos para o mesmo APEX.

Script de inicialização no APEX:

prebuilt_etc {
  name: "myinit.rc",
  src: "myinit.rc"
}

apex {
  ..
  vendor: true,
  prebuilts: ["myinit.rc"],
  ..
}

Scripts de inicialização dentro de APEXes só podem ter definições de service , não on <property> . Se o seu serviço precisar depender de algumas condições, você precisará instalar o script init em /vendor .

Script de inicialização em /vendor :

apex {
  ..
  vendor: true,
  init_rc: ["myinit.in.vendor.rc"],
  ..
}

Quaisquer referências ao caminho binário em um script de inicialização precisam refletir o caminho dentro do APEX. Você pode codificar o caminho no arquivo de origem do script init ou substituí-lo de um arquivo de origem existente usando um genrule .

Firmware

Exemplo:

Incorpore o firmware em um APEX de 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. ueventd verifica os diretórios /apex/*/etc/firmware para encontrar módulos de firmware.

O file_contexts do APEX deve rotular corretamente todas as entradas de carga útil de firmware para garantir que esses arquivos sejam acessíveis pelo ueventd em tempo de execução; normalmente, o rótulo vendor_file é suficiente. Por exemplo:

(/.*)? u:object_r:vendor_file:s0

Módulos do kernel

Incorpore módulos de kernel em um APEX de fornecedor como módulos pré-construídos, conforme 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 deve rotular corretamente todas as entradas de carga útil do módulo do kernel. Por exemplo:

/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0

Os módulos do kernel devem ser instalados explicitamente. O seguinte exemplo 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 sobreposições de recursos de tempo de execução em um APEX de 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 suportam vários outros arquivos de configuração normalmente encontrados na partição do fornecedor como pré-compilados dentro dos APEXes do fornecedor, e mais estão sendo adicionados.

Exemplos:

Recursos extras de desenvolvimento

Seleção do APEX na inicialização

Exemplo:

Os desenvolvedores também podem instalar várias versões de APEXs de fornecedores que compartilham o mesmo nome e chave do APEX e, em seguida, escolher qual versão é ativada durante cada inicialização usando sysprops persistentes. Para determinados casos de uso do desenvolvedor, isso pode ser mais simples do que instalar uma nova cópia do APEX usando adb install .

Exemplos de casos de uso:

  • Instale 3 versões do APEX do fornecedor wifi HAL: as equipes de controle de qualidade podem executar testes manuais ou automatizados usando uma versão, reiniciar em outra versão e executar novamente os testes e comparar os resultados finais.
  • Instale 2 versões da câmera HAL do fornecedor APEX, atual e experimental : Os dogfooders podem usar a versão experimental sem baixar e instalar um arquivo adicional, para que possam trocar facilmente.

Durante a inicialização, apexd procura sysprops seguindo um formato específico para ativar a versão correta do APEX.

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>
  • sysprop persistente
    • Usado para alterar o valor padrão, definido em um dispositivo já inicializado.
    • Substitui o valor bootconfig se estiver presente.
    • persist.vendor.apex.<apex name>

O valor da propriedade deve ser o nome do arquivo do APEX que deve ser ativado.

A única alteração nas definições do APEX Android.bp é o campo multi_install_skip_symbol_files , que deve ser true para todas as definições de APEX multiinstaladas não padrão:

// Default version.
apex {
  name: "com.oem.camera.hal.my_apex_default",
  apex_name: "com.oem.camera.hal",
  vendor: true,
  ..
}

// Non-default version.
apex {
  name: "com.oem.camera.hal.my_apex_experimental",
  apex_name: "com.oem.camera.hal",
  vendor: true,
  multi_install_skip_symbol_files: true,
  ..
}

A versão padrão também deve 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, altere a versão ativada configurando 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 suportar a atualização do bootconfig após a atualização (como por meio de comandos fastboot oem ), a alteração da propriedade bootconfig para o APEX multiinstalado também altera a versão ativada na inicialização.

Para dispositivos de referência virtual baseados em Cuttlefish , você pode usar o comando --extra_bootconfig_args para definir a propriedade bootconfig diretamente durante a inicialização. Por exemplo:

launch_cvd --noresume \
  --extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";