Cómo cambiar el valor de los recursos de una app durante el tiempo de ejecución

Una superposición de recursos de entorno de ejecución (RRO) es un paquete que cambia los valores de los recursos de un paquete de destino en el entorno de ejecución. Por ejemplo, una app instalada en la imagen del sistema podría cambiar su comportamiento según el valor de un recurso. En lugar de codificar el valor del recurso en el momento de la compilación, una RRO instalada en una partición diferente puede cambiar los valores de los recursos de la app en el tiempo de ejecución.

Los RRO se pueden habilitar o inhabilitar. Puedes configurar de manera programática el estado de habilitación o inhabilitación para activar o desactivar la capacidad de una RRO de cambiar los valores de los recursos. De forma predeterminada, las RRO están inhabilitadas (sin embargo, las RRO estáticas están habilitadas de forma predeterminada).

Recursos superpuestos

Las superposiciones funcionan mediante la asignación de los recursos definidos en el paquete de superposición a los recursos definidos en el paquete de destino. Cuando una app intenta resolver el valor de un recurso en el paquete de destino, en su lugar se muestra el valor del recurso de superposición al que está asignado el recurso de destino.

Cómo configurar el manifiesto

Un paquete se considera un paquete de RRO si contiene una etiqueta <overlay> como elemento secundario de la etiqueta <manifest>.

  • El valor del atributo android:targetPackage obligatorio especifica el nombre del paquete que la RRO desea superponer.

  • El valor del atributo opcional android:targetName especifica el nombre del subconjunto de recursos superpuestos del paquete de destino que la RRO desea superponer. Si el destino no define un conjunto de recursos que se puede superponer, este atributo no debe estar presente.

En el siguiente código, se muestra un ejemplo de AndroidManifest.xml superpuesta.

<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>

Las superposiciones no pueden superponer el código, por lo que no pueden tener archivos DEX. Además, el atributo android:hasCode de la etiqueta <application> en el manifiesto debe establecerse en false.

Define el mapa de recursos

En Android 11 o versiones posteriores, el mecanismo recomendado para definir el mapa de recursos de superposición es crear un archivo en el directorio res/xml del paquete de superposición, enumerar los recursos de destino que se deben superponer y sus valores de reemplazo, y luego establecer el valor del atributo android:resourcesMap de la etiqueta del manifiesto <overlay> en una referencia al archivo de asignación de recursos.

En el siguiente código, se muestra un archivo res/xml/overlays.xml de ejemplo.

<?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>

El siguiente código muestra un ejemplo de manifiesto de superposición.

<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>

Compila el paquete

Android 11 y las versiones posteriores admiten una regla de compilación de Soong para superposiciones que evitan que Android Asset Packaging Tool 2 (AAPT2) intente anular la duplicación de configuraciones de recursos con el mismo valor (--no-resource-deduping) y quite recursos sin configuraciones predeterminadas (--no-resource-removal). En el siguiente código, se muestra un archivo Android.bp de ejemplo.

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

Resolver recursos

Si un recurso de destino o de superposición tiene varias configuraciones definidas para el recurso que se consulta, el entorno de ejecución de recursos muestra el valor de la configuración que mejor coincide con la configuración del dispositivo. Para determinar cuál es la configuración que mejor coincide, combina el conjunto de configuraciones de recursos de superposición con el conjunto de configuraciones de recursos de destino y, luego, sigue el flujo normal de resolución de recursos (para obtener más información, consulta Cómo Android encuentra el recurso de coincidencia óptima).

Por ejemplo, si una superposición define un valor para la configuración de drawable-en y el destino define un valor para drawable-en-port, drawable-en-port tendrá una mejor coincidencia, de modo que se elija el valor de la configuración de destino drawable-en-port durante el tiempo de ejecución. Para superponer todas las configuraciones de drawable-en, la superposición debe definir un valor para cada configuración de drawable-en que define el objetivo.

Las superposiciones pueden hacer referencia a sus propios recursos, con comportamientos diferentes entre las versiones de Android.

  • En Android 11 o versiones posteriores, cada superposición tiene su propio espacio de ID de recursos reservado que no se superpone con el espacio de ID de recursos de destino ni con otros espacios de ID de recursos de superposición, por lo que las superposiciones que hacen referencia a sus propios recursos funcionan como se espera.

  • En Android 10 o versiones anteriores, las superposiciones y los paquetes de destino comparten el mismo espacio de ID de recurso, lo que puede causar colisiones y comportamientos inesperados cuando intentan hacer referencia a sus propios recursos con la sintaxis @type/name.

Cómo habilitar o inhabilitar superposiciones

Usa la API de OverlayManager para habilitar y deshabilitar superposiciones mutables (recupera la interfaz de la API con Context#getSystemService(Context.OVERLAY_SERVICE)). Solo el paquete al que se orienta o un paquete con el permiso android.permission.CHANGE_OVERLAY_PACKAGES puede habilitar una superposición. Cuando se habilita o inhabilita una superposición, los eventos de cambio de configuración se propagan al paquete de destino y se reinician las actividades de destino.

Restringe los recursos superpuestos

En Android 10 y versiones posteriores, la etiqueta XML <overlayable> expone un conjunto de recursos que los RRO pueden superponer. En el siguiente archivo de ejemplo res/values/overlayable.xml, string/foo y integer/bar son recursos que se usan para aplicar temas a la apariencia del dispositivo. Para superponer estos recursos, una superposición debe dirigirse explícitamente a la colección de recursos superpuestos por su nombre.

<!-- 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>

Un APK puede definir varias etiquetas <overlayable>, pero cada etiqueta debe tener un nombre único dentro del paquete. Por ejemplo, es lo siguiente:

  • Está bien que dos paquetes diferentes definan <overlayable name="foo">.

  • No es correcto que un solo APK tenga dos bloques <overlayable name="foo">.

En el siguiente código, se muestra un ejemplo de una superposición en el archivo 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>

Cuando una app define una etiqueta <overlayable>, se superpone la segmentación con esa app:

  • Se debe especificar targetName.

  • Solo puede superponer los recursos que se enumeran en la etiqueta <overlayable>.

  • Solo puede segmentarse para un nombre <overlayable>.

No puedes habilitar una superposición segmentada para un paquete que exponga recursos superponibles, pero que no use android:targetName para segmentar una etiqueta <overlayable> específica.

Políticas de restricción

Usa la etiqueta <policy> para aplicar restricciones en los recursos superpuestos. El atributo type especifica qué políticas debe cumplir una superposición para anular los recursos incluidos. Entre los tipos admitidos, se incluyen los siguientes.

  • public. Cualquier superposición puede anular el recurso.
  • system. Cualquier superposición en la partición del sistema puede anular los recursos.
  • vendor. Cualquier superposición en la partición del proveedor puede anular los recursos.
  • product. Cualquier superposición en la partición del producto puede anular los recursos.
  • oem. Cualquier superposición en la partición del OEM puede anular los recursos.
  • odm. Cualquier superposición en la partición odm puede anular los recursos.
  • signature. Cualquier superposición firmada con la misma firma que el APK de destino puede reemplazar los recursos.
  • actor. Cualquier superposición firmada con la misma firma que el APK de actor puede anular los recursos. El actor se declara en la etiqueta named-actor en la configuración del sistema.
  • config_signature. Cualquier superposición firmada con la misma firma que el APK de overlay-config puede anular los recursos. La configuración de superposición se declara en la etiqueta overlay-config-signature en la configuración del sistema.

En el siguiente código, se muestra una etiqueta <policy> de ejemplo en el archivo 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 varias políticas, usa barras verticales (|) como caracteres separadores. Cuando se especifican varias políticas, una superposición solo debe cumplir una política para anular los recursos que se enumeran en la etiqueta <policy>.

Cómo configurar superposiciones

Android admite diferentes mecanismos para configurar la mutabilidad, el estado predeterminado y la prioridad de las superposiciones según la versión de actualización de Android.

  • Los dispositivos que ejecutan Android 11 o versiones posteriores pueden usar un archivo OverlayConfig (config.xml) en lugar de atributos del manifiesto. El uso de un archivo de superposición es el método recomendado para las superposiciones.

  • Todos los dispositivos pueden usar atributos de manifiesto (android:isStatic y android:priority) para configurar RRO estáticos.

Usa OverlayConfig

En Android 11 y versiones posteriores, puedes usar OverlayConfig para configurar la mutabilidad, el estado predeterminado y la prioridad de las superposiciones. Para configurar una superposición, crea o modifica el archivo que se encuentra en partition/overlay/config/config.xml, donde partition es la partición de la superposición que se debe configurar. Para configurarse, una superposición debe residir en el directorio overlay/ de la partición en la que se configura. En el siguiente código, se muestra un product/overlay/config/config.xml de ejemplo.

<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>"

La etiqueta <overlay> requiere un atributo package que indique qué paquete de superposición se está configurando. El atributo opcional enabled controla si la superposición está habilitada de forma predeterminada (el valor predeterminado es false). El atributo opcional mutable controla si la superposición es mutable y si se puede cambiar su estado habilitado de forma programática durante el tiempo de ejecución (el valor predeterminado es true). Las superposiciones que no se incluyen en un archivo de configuración son mutables y están inhabilitadas de forma predeterminada.

Prioridad de superposición

Cuando varias superposiciones anulan los mismos recursos, el orden de estas es importante. Una superposición tiene mayor precedencia que las superposiciones con configuraciones que preceden a su propia configuración. El orden de prioridad de las superposiciones en diferentes particiones (de la menor a la mayor prioridad) es el siguiente.

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

Combinar archivos

El uso de etiquetas <merge> permite que se combinen otros archivos de configuración en la posición especificada en el archivo de configuración. El atributo path de la etiqueta representa la ruta del archivo que se combinará en relación con el directorio que contiene los archivos de configuración de superposición.

Usar atributos de manifiesto o RRO estáticas

En Android 10 o versiones anteriores, la inmutabilidad y la prioridad de las superposiciones se configuran con los siguientes atributos de manifiesto.

  • android:isStatic. Cuando el valor de este atributo booleano se establece en true, la superposición se habilita de forma predeterminada y es inmutable, lo que evita que se inhabilite.

  • android:priority. El valor de este atributo numérico (que solo afecta a las superposiciones estáticas) configura la precedencia de la superposición cuando varias superposiciones estáticas se orientan al mismo valor del recurso. Un número más alto indica una mayor precedencia.

En el siguiente código, se muestra un ejemplo 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>

Cambios en Android 11

En Android 11 o versiones posteriores, si un archivo de configuración se encuentra en partition/overlay/config/config.xml, las superposiciones se configuran con ese archivo, y android:isStatic y android:priority no tienen efecto en las superposiciones ubicadas en la partición. La definición de un archivo de configuración de superposición en cualquier partición aplica la prioridad de partición de superposición.

Además, Android 11 o las versiones posteriores quitan la capacidad de usar superposiciones estáticas para afectar los valores de los recursos leídos durante la instalación del paquete. Para el caso de uso común de usar superposiciones estáticas para cambiar el valor de los valores booleanos que configuran el estado habilitado del componente, usa la etiqueta SystemConfig <component-override> (nueva en Android 11).

Superposiciones de depuración

Para habilitar, inhabilitar y volcar superposiciones de forma manual, usa el siguiente comando de shell del administrador de superposiciones.

adb shell cmd overlay

OverlayManagerService usa idmap2 para asignar los IDs de recursos en el paquete de destino a los IDs de recursos en el paquete de superposición. Las asignaciones de ID generadas se almacenan en /data/resource-cache/. Si tu superposición no funciona correctamente, busca el archivo idmap correspondiente en /data/resource-cache/ y, luego, ejecuta el siguiente comando.

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

Este comando imprime la asignación de recursos como se muestra a continuación.

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