Alterar o valor dos recursos de um aplicativo no tempo de execução

Uma sobreposição de recursos de ambiente de execução (RRO, na sigla em inglês) é um pacote que muda os valores de recurso de um pacote de destino no ambiente de execução. Por exemplo, um app instalado na imagem do sistema pode mudar o comportamento com base no valor de um recurso. Em vez de codificar o valor do recurso no tempo de build, uma RRO instalada em uma partição diferente pode mudar os valores dos recursos do app no momento da execução.

As RROs podem ser ativadas ou desativadas. É possível definir programaticamente o estado ativar/desativar para alternar a capacidade de uma RRO de mudar os valores de recursos. As RROs são desativadas por padrão (no entanto, as RROs estáticas são ativadas por padrão).

Recursos de sobreposição

As sobreposições funcionam mapeando os recursos definidos no pacote de sobreposição para os recursos definidos no pacote de destino. Quando um app tenta resolver o valor de um recurso no pacote de destino, o valor do recurso de sobreposição para o qual o recurso de destino é mapeado é retornado.

Configurar o manifesto

Um pacote é considerado RRO quando contém uma tag <overlay> como filha da tag <manifest>.

  • O valor do atributo android:targetPackage obrigatório especifica o nome do pacote que a RRO pretende sobrepor.

  • O valor do atributo opcional android:targetName especifica o nome do subconjunto de recursos sobrepostos do pacote de destino que a RRO pretende sobrepor. Se o destino não definir um conjunto de recursos sobreposto, esse atributo não estará presente.

O código a seguir mostra um exemplo de AndroidManifest.xml de sobreposição.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"/>
</manifest>

As sobreposições não podem sobrepor código, por isso não podem ter arquivos DEX. Além disso, o atributo android:hasCode da tag <application> no manifesto precisa ser definido como false.

Definir o mapa de recursos

No Android 11 ou versões mais recentes, o mecanismo recomendado para definir o mapa de recursos de sobreposição é criar um arquivo no diretório res/xml do pacote de sobreposição, enumerar os recursos de destino que precisam ser sobrepostos e os valores de substituição e, em seguida, definir o valor do atributo android:resourcesMap da tag de manifesto <overlay> como uma referência ao arquivo de mapeamento de recursos.

O código a seguir mostra um exemplo de arquivo res/xml/overlays.xml.

<?xml version="1.0" encoding="utf-8"?>
<overlay xmlns:android="http://schemas.android.com/apk/res/android" >
    <!-- Overlays string/config1 and string/config2 with the same resource. -->
    <item target="string/config1" value="@string/overlay1" />
    <item target="string/config2" value="@string/overlay1" />

    <!-- Overlays string/config3 with the string "yes". -->
    <item target="string/config3" value="@android:string/yes" />

    <!-- Overlays string/config4 with the string "Hardcoded string". -->
    <item target="string/config4" value="Hardcoded string" />

    <!-- Overlays integer/config5 with the integer "42". -->
    <item target="integer/config5" value="42" />
</overlay>

O código a seguir mostra um exemplo de manifesto de sobreposição.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:targetName="OverlayableResources"
                   android:resourcesMap="@xml/overlays"/>
</manifest>

Criar o pacote

O Android 11 ou versões mais recentes oferece suporte a uma regra de build Soong para sobreposições que impede a Android Asset Packaging Tool 2 (AAPT2) de tentar deducar configurações de recursos com o mesmo valor (--no-resource-deduping) e remover recursos sem configurações padrão (--no-resource-removal). O código abaixo mostra um exemplo de arquivo Android.bp.

runtime_resource_overlay {
    name: "ExampleOverlay",
    sdk_version: "current",
}

Resolver recursos

Se um recurso de destino ou de sobreposição tiver várias configurações definidas para o recurso que está sendo consultado, o ambiente de execução do recurso vai retornar o valor da configuração que melhor corresponde à configuração do dispositivo. Para determinar qual configuração é a melhor correspondência, mescle o conjunto de configurações de recursos de sobreposição no conjunto de configurações de recursos alvo e siga o fluxo de resolução de recursos normal. Para mais detalhes, consulte Como o Android encontra o recurso ideal.

Por exemplo, se uma sobreposição definir um valor para a configuração drawable-en e o destino definir um valor para drawable-en-port, drawable-en-port terá uma correspondência melhor para que o valor da configuração de destino drawable-en-port seja escolhido no momento da execução. Para sobrepor todas as configurações de drawable-en, a sobreposição precisa definir um valor para cada configuração de drawable-en definida pelo destino.

As sobreposições podem fazer referência aos próprios recursos, com comportamentos diferentes entre as versões do Android.

  • No Android 11 ou versões mais recentes, cada sobreposição tem o próprio espaço de ID de recurso reservado que não se sobrepõe ao espaço de ID de recurso de destino ou outros espaços de ID de recurso de sobreposição. Assim, as sobreposições que fazem referência aos próprios recursos funcionam conforme o esperado.

  • No Android 10 ou versões anteriores, os pacotes de destino e sobreposições compartilham o mesmo espaço de ID de recurso, o que pode causar colisões e comportamentos inesperados quando eles tentam referenciar os próprios recursos usando a sintaxe @type/name.

Ativar/desativar sobreposições

Use a API OverlayManager para ativar e desativar sobreposições mutáveis (recuperar a interface da API usando Context#getSystemService(Context.OVERLAY_SERVICE)). Uma sobreposição só pode ser ativada pelo pacote de destino ou por um pacote com a permissão android.permission.CHANGE_OVERLAY_PACKAGES. Quando uma sobreposição é ativada ou desativada, os eventos de mudança de configuração são propagados para o pacote de destino e as atividades de destino são reiniciadas.

Restringir recursos sobrepostos

No Android 10 ou versões mais recentes, a tag XML <overlayable> expõe um conjunto de recursos que as RROs podem sobrepor. No arquivo res/values/overlayable.xml de exemplo abaixo, string/foo e integer/bar são recursos usados para definir temas na aparência do dispositivo. Para sobrepor esses recursos, uma sobreposição precisa segmentar explicitamente a coleção de recursos sobrepostos pelo nome.

<!-- The collection of resources for theming the appearance of the device -->
<overlayable name="ThemeResources">
       <policy type="public">
               <item type="string" name="foo/" />
               <item type="integer" name="bar/" />
       </policy>
       ...
</overlayable>

Um APK pode definir várias tags <overlayable>, mas cada uma precisa ter um nome exclusivo no pacote. Por exemplo, ele é:

  • OK para dois pacotes diferentes definirem <overlayable name="foo">.

  • Não é permitido que um único APK tenha dois blocos <overlayable name="foo">.

O código a seguir mostra um exemplo de sobreposição no arquivo AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.my.theme.overlay">
       <application android:hasCode="false" />
       <!-- This overlay will override the ThemeResources resources -->
       <overlay android:targetPackage="android" android:targetName="ThemeResources">
</manifest>

Quando um app define uma tag <overlayable>, a segmentação desse app é sobreposta:

  • É necessário especificar targetName.

  • Pode sobrepor apenas os recursos listados na tag <overlayable>.

  • É possível segmentar apenas um nome <overlayable>.

Não é possível ativar uma sobreposição direcionada a um pacote que expõe recursos sobrepostos, mas não usa android:targetName para segmentar uma tag <overlayable> específica.

Políticas de restrição

Use a tag <policy> para aplicar restrições a recursos que podem ser sobrepostos. O atributo type especifica quais políticas uma sobreposição precisa atender para substituir os recursos incluídos. Os tipos com suporte incluem:

  • public. Qualquer sobreposição pode substituir o recurso.
  • system. Qualquer sobreposição na partição do sistema pode substituir os recursos.
  • vendor. Qualquer sobreposição na partição do fornecedor pode substituir os recursos.
  • product. Qualquer sobreposição na partição do produto pode substituir os recursos.
  • oem. Qualquer sobreposição na partição de OEM pode substituir os recursos.
  • odm. Qualquer sobreposição na partição odm pode substituir os recursos.
  • signature. Qualquer overlay assinado com a mesma assinatura do APK de destino pode substituir os recursos.
  • actor. Qualquer sobreposição assinada com a mesma assinatura do APK do ator pode substituir os recursos. O ator é declarado na tag names-actor da configuração do sistema.
  • config_signature. Qualquer sobreposição assinada com a mesma assinatura do APK overlay-config pode substituir os recursos. A overlay-config é declarada na tag overlay-config-signature na configuração do sistema.

O código a seguir mostra um exemplo de tag <policy> no arquivo res/values/overlayable.xml.

<overlayable name="ThemeResources">
   <policy type="vendor" >
       <item type="string" name="foo" />
   </policy>
   <policy type="product|signature"  >
       <item type="string" name="bar" />
       <item type="string" name="baz" />
   </policy>
</overlayable>

Para especificar várias políticas, use barras verticais ("|") como caracteres de separação. Quando várias políticas são especificadas, uma sobreposição precisa atender apenas a uma política para substituir os recursos listados na tag <policy>.

Configurar sobreposições

O Android oferece suporte a diferentes mecanismos para configurar a mutabilidade, o estado padrão e a prioridade das sobreposições, dependendo da versão de lançamento do Android.

  • Dispositivos com o Android 11 ou versões mais recentes podem usar um arquivo OverlayConfig (config.xml) em vez de atributos de manifesto. O uso de um arquivo de sobreposição é o método recomendado para sobreposições.

  • Todos os dispositivos podem usar atributos de manifesto (android:isStatic e android:priority) para configurar RROs estáticos.

Usar o OverlayConfig

No Android 11 ou versões mais recentes, você pode usar OverlayConfig para configurar a mutabilidade, o estado padrão e a prioridade das sobreposições. Para configurar uma sobreposição, crie ou modifique o arquivo localizado em partition/overlay/config/config.xml, em que partition é a partição da sobreposição a ser configurada. Para ser configurada, uma sobreposição precisa estar no diretório overlay/ da partição em que ela está configurada. O código abaixo mostra um exemplo de product/overlay/config/config.xml.

<config>
    <merge path="OEM-common-rros-config.xml" />
    <overlay package="com.oem.overlay.device" mutable="false" enabled="true" />
    <overlay package="com.oem.green.theme" enabled="true" />
</config>"

A tag <overlay> requer um atributo package que indique qual pacote de sobreposição está sendo configurado. O atributo opcional enabled controla se a sobreposição é ativada ou não por padrão (o padrão é false). O atributo opcional mutable controla se a sobreposição é mutável ou não e pode mudar o estado ativado de forma programática durante a execução (o padrão é true). As sobreposições não listadas em um arquivo de configuração são mutáveis e desativadas por padrão.

Precedência da sobreposição

Quando várias sobreposições substituem os mesmos recursos, a ordem delas é importante. Uma sobreposição tem precedência maior do que as sobreposições com configurações anteriores à própria configuração. A ordem de precedência das sobreposições em diferentes partições (da menor para a maior precedência) é a seguinte.

  • system
  • vendor
  • odm
  • oem
  • product
  • system_ext

Mesclar arquivos

O uso de tags <merge> permite que outros arquivos de configuração sejam mesclados na posição especificada no arquivo de configuração. O atributo path da tag representa o caminho do arquivo a ser mesclado em relação ao diretório que contém os arquivos de configuração de sobreposição.

Usar atributos de manifesto/RROs estáticos

No Android 10 ou versões anteriores, a imutabilidade e a precedência de sobreposição são configuradas usando os atributos de manifesto abaixo.

  • android:isStatic. Quando o valor desse atributo booleano é definido como true, a sobreposição é ativada por padrão e é imutável, o que impede que ela seja desativada.

  • android:priority. O valor desse atributo numérico (que afeta apenas sobreposições estáticas) configura a precedência da sobreposição quando várias sobreposições estáticas têm como destino o mesmo valor de recurso. Um número maior indica uma precedência maior.

O código a seguir mostra um exemplo de AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.overlay">
    <application android:hasCode="false" />
    <overlay android:targetPackage="com.example.target"
                   android:isStatic="true"
                   android:priority="5"/>
</manifest>

Mudanças no Android 11

No Android 11 ou versões mais recentes, se um arquivo de configuração estiver localizado em partition/overlay/config/config.xml, as sobreposições serão configuradas usando esse arquivo, e android:isStatic e android:priority não terão efeito nas sobreposições localizadas na partição. Definir um arquivo de configuração de sobreposição em qualquer partição aplica a precedência da partição de sobreposição.

Além disso, o Android 11 ou versões mais recentes removem a capacidade de usar sobreposições estáticas para afetar os valores de recursos lidos durante a instalação do pacote. Para o caso de uso comum de usar sobreposições estáticas para mudar o valor de booleanos que configuram o estado ativado do componente, use a tag <component-override> SystemConfig (nova no Android 11).

Depurar sobreposições

Para ativar, desativar e despejar sobreposições manualmente, use o comando shell do gerenciador de sobreposições abaixo.

adb shell cmd overlay

OverlayManagerService usa idmap2 para mapear IDs de recursos no pacote de destino para IDs de recursos no pacote de sobreposição. Os mapeamentos de ID gerados são armazenados em /data/resource-cache/. Se a sobreposição não estiver funcionando corretamente, encontre o arquivo idmap correspondente para a sobreposição em /data/resource-cache/ e execute o comando a seguir.

adb shell idmap2 dump --idmap-path [file]

Esse comando imprime o mapeamento de recursos conforme mostrado abaixo.

[target res id] - > [overlay res id] [resource name]
0x01040151 -> 0x01050001 string/config_dozeComponent
0x01040152 -> 0x01050002 string/config_dozeDoubleTapSensorType
0x01040153 -> 0x01050003 string/config_dozeLongPressSensorType