Armazenamento em cache de APK

Este documento descreve o design de uma solução de armazenamento em cache de APK para a instalação rápida de apps pré-carregados em dispositivos compatíveis com partições A/B.

Os OEMs podem alocar pré-carregamentos e os apps favoritos no cache do APK armazenado na partição B, que normalmente fica vazia, em novos dispositivos com particionamento A/B, sem afetar o espaço de dados do usuário. Ao ter um cache de APK disponível no dispositivo, os dispositivos novos ou redefinidos para a configuração original recentemente ficam prontos para uso quase imediatamente, sem precisar fazer o download de arquivos APK do Google Play.

Casos de uso

  • Armazenar apps pré-carregados na partição B para uma configuração mais rápida
  • Armazenar apps favoritos na partição B para uma restauração mais rápida

Pré-requisitos

Para usar esse recurso, o dispositivo precisa:

  • Ter a versão do Android 8.1 (O MR1) instalada
  • Ter a partição A/B implementada

O conteúdo pré-carregado só pode ser copiado durante a primeira inicialização. Isso ocorre porque, em dispositivos que oferecem suporte a atualizações do sistema A/B, a partição B não armazena arquivos de imagem do sistema, mas conteúdo pré-carregado, como recursos de demonstração de varejo, arquivos OAT e o cache de APK. Depois que os recursos são copiados para a partição /data (isso acontece na primeira inicialização), a partição B é usada por atualizações over-the-air (OTA) para fazer o download de versões atualizadas da imagem do sistema.

Portanto, o cache de APK não pode ser atualizado por OTA. Ele só pode ser pré-carregado em uma fábrica. A redefinição de fábrica afeta apenas a partição /data. A partição B do sistema ainda tem o conteúdo pré-carregado até que a imagem OTA seja baixada. Após a redefinição de fábrica, o sistema passará pela primeira inicialização novamente. Isso significa que o armazenamento em cache de APK não estará disponível se a imagem OTA for baixada para a partição B e o dispositivo for redefinido para a configuração original.

Implementação

Abordagem 1. Conteúdo na partição system_other

Prós: o conteúdo pré-carregado não é perdido após a redefinição de fábrica. Ele será copiado da partição B após uma reinicialização.

Contras: requer espaço na partição B. A inicialização após a redefinição de fábrica exige mais tempo para copiar o conteúdo pré-carregado.

Para que os pré-carregamentos sejam copiados durante a primeira inicialização, o sistema chama um script em /system/bin/preloads_copy.sh. O script é chamado com um único argumento (caminho para o ponto de montagem somente leitura da partição system_b):

Para implementar esse recurso, faça estas mudanças específicas do dispositivo. Confira um exemplo de Marlin:

  1. Adicione o script que faz a cópia ao device-common.mk arquivo (neste caso, device/google/marlin/device-common.mk), desta forma:
    # Script that copies preloads directory from system_other to data partition
    PRODUCT_COPY_FILES += \
        device/google/marlin/preloads_copy.sh:system/bin/preloads_copy.sh
    
    Encontre o código de script de exemplo em: device/google/marlin/preloads_copy.sh
  2. Edite o arquivo init.common.rc para que ele crie o diretório e os subdiretórios /data/preloads necessários:
    mkdir /data/preloads 0775 system system
    mkdir /data/preloads/media 0775 system system
    mkdir /data/preloads/demo 0775 system system
    
    Encontre o código de arquivo init de exemplo em: device/google/marlin/init.common.rc
  3. Defina um novo domínio SELinux no arquivo preloads_copy.te:
    type preloads_copy, domain, coredomain;
    type preloads_copy_exec, exec_type, vendor_file_type, file_type;
    
    init_daemon_domain(preloads_copy)
    
    allow preloads_copy shell_exec:file rx_file_perms;
    allow preloads_copy toolbox_exec:file rx_file_perms;
    allow preloads_copy preloads_data_file:dir create_dir_perms;
    allow preloads_copy preloads_data_file:file create_file_perms;
    allow preloads_copy preloads_media_file:dir create_dir_perms;
    allow preloads_copy preloads_media_file:file create_file_perms;
    
    # Allow to copy from /postinstall
    allow preloads_copy system_file:dir r_dir_perms;
    
    Encontre um arquivo de domínio SELinux de exemplo em: /device/google/marlin/+/android17-release/sepolicy/preloads_copy.te
  4. Registre o domínio em um novo arquivo /sepolicy/file_contexts:
    /system/bin/preloads_copy\.sh     u:object_r:preloads_copy_exec:s0
    
    Encontre um arquivo de contextos SELinux de exemplo em: device/google/marlin/sepolicy/preloads_copy.te
  5. No tempo de build, o diretório com conteúdo pré-carregado precisa ser copiado para a partição system_other:
    # Copy contents of preloads directory to system_other partition
    PRODUCT_COPY_FILES += \
        $(call find-copy-subdir-files,*,vendor/google_devices/marlin/preloads,system_other/preloads)
    
    Este é um exemplo de uma mudança em um Makefile que permite copiar recursos de cache de APK do repositório Git do fornecedor (no nosso caso, era vendor/google_devices/marlin/preloads) para o local na partição system_other que será copiado para /data/preloads quando o dispositivo for inicializado pela primeira vez. Esse script é executado no tempo de build para preparar a imagem system_other. Ele espera que o conteúdo pré-carregado esteja disponível em vendor/google_devices/marlin/preloads. O OEM pode escolher o nome/caminho do repositório real.
  6. O cache de APK está localizado em /data/preloads/file_cache e tem este layout:
    /data/preloads/file_cache/
        app.package.name.1/
              file1
              fileN
        app.package.name.N/
    
    Essa é a estrutura de diretório final nos dispositivos. Os OEMs podem escolher qualquer abordagem de implementação, desde que a estrutura de arquivos final replique a descrita acima.

Abordagem 2. Conteúdo na imagem de dados do usuário transferida na fábrica

Essa abordagem alternativa pressupõe que o conteúdo pré-carregado já esteja incluído em o diretório /data/preloads na partição /data.

Prós: funciona imediatamente. Não é necessário fazer personalizações no dispositivo para copiar arquivos na primeira inicialização. O conteúdo já está na partição /data.

Contras: o conteúdo pré-carregado é perdido após uma redefinição de fábrica. Embora isso possa ser aceitável para alguns, nem sempre funciona para OEMs que redefinem os dispositivos para a configuração original após fazer inspeções de controle de qualidade.

Um novo método @SystemApi, getPreloadsFileCache(), foi adicionado a android.content.Context. Ele retorna um caminho absoluto para um diretório específico do app no cache pré-carregado.

Um novo método, IPackageManager.deletePreloadsFileCache, foi adicionado para permitir a exclusão do diretório de pré-carregamentos para recuperar todo o espaço. O método só pode ser chamado por apps com SYSTEM_UID, ou seja, servidor do sistema ou configurações.

Preparação do app

Somente apps privilegiados podem acessar o diretório de cache de pré-carregamentos. Para esse acesso, os apps precisam ser instalados no diretório /system/priv-app.

Validação

  • Após a primeira inicialização, o dispositivo precisa ter conteúdo no diretório /data/preloads/file_cache.
  • O conteúdo no diretório file_cache/ precisa ser excluído se o dispositivo ficar com pouco armazenamento.

Use o app de exemplo ApkCacheTest para testar o cache de APK.

  1. Crie o app executando este comando no diretório raiz:
    make ApkCacheTest
    
  2. Instale o app como um app privilegiado. Lembre-se de que apenas apps privilegiados podem acessar o cache de APK. Isso requer um dispositivo com acesso root:
    adb root && adb remount
    adb shell mkdir /system/priv-app/ApkCacheTest
    adb push $ANDROID_PRODUCT_OUT/data/app/ApkCacheTest/ApkCacheTest.apk /system/priv-app/ApkCacheTest/
    adb shell stop && adb shell start
    
  3. Simule o diretório de cache de arquivos e o conteúdo dele, se necessário (também requer privilégios de root):
    adb shell mkdir -p /data/preloads/file_cache/com.android.apkcachetest
    adb shell restorecon -r /data/preloads
    adb shell "echo "Test File" > /data/preloads/file_cache/com.android.apkcachetest/test.txt"
    
  4. Teste o app. Depois de instalar o app e criar o diretório file_cache de teste, abra o app ApkCacheTest. Ele vai mostrar um arquivo test.txt e o conteúdo dele. Consulte esta captura de tela para ver como esses resultados aparecem na interface do usuário.

    Figura 1. Resultados do ApkCacheTest.