Cambiar el valor de los recursos de una aplicación en tiempo de ejecución

Una superposición de recursos en tiempo de ejecución (RRO) es un paquete que cambia los valores de recursos de un paquete de destino en tiempo de ejecución. Por ejemplo, una aplicación 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, un RRO instalado en una partición diferente puede cambiar los valores de los recursos de la aplicación en el tiempo de ejecución.

Los RRO se pueden habilitar o deshabilitar. Puede establecer mediante programación el estado habilitar/deshabilitar para alternar la capacidad de un RRO para cambiar los valores de los recursos. Los RRO están deshabilitados de forma predeterminada (sin embargo, los RRO estáticos están habilitados de forma predeterminada).

Recursos superpuestos

Las superposiciones funcionan asignando recursos definidos en el paquete de superposición a recursos definidos en el paquete de destino. Cuando una aplicación intenta resolver el valor de un recurso en el paquete de destino, se devuelve el valor del recurso superpuesto al que está asignado el recurso de destino.

Configurar el manifiesto

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

  • El valor del atributo android:targetPackage requerido especifica el nombre del paquete que el RRO pretende superponer.

  • El valor del atributo opcional android:targetName especifica el nombre del subconjunto superponible de recursos del paquete de destino que el RRO pretende superponer. Si el objetivo no define un conjunto de recursos superponibles, este atributo no debería estar presente.

El siguiente código muestra una superposición de ejemplo 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:targetName="OverlayableResources"/>
</manifest>

Las superposiciones no pueden superponer 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 .

Definir el mapa de recursos

En Android 11 o superior, 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 deben superponerse y sus valores de reemplazo, luego establecer el valor del android:resourcesMap atributo de la etiqueta de manifiesto <overlay> a una referencia al archivo de asignación de recursos.

El siguiente código 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 superpuesto.

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

Construye el paquete

Android 11 o superior admite una regla de compilación de Soong para superposiciones que evita que Android Asset Packaging Tool 2 (AAPT2) intente deduplicar configuraciones de recursos con el mismo valor ( --no-resource-deduping ) y elimine recursos sin configuraciones predeterminadas ( --no-resource-removal ). El siguiente código muestra un archivo Android.bp de ejemplo.

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

Resolver recursos

Si un recurso de destino o un recurso superpuesto tiene varias configuraciones definidas para el recurso que se está consultando, el tiempo de ejecución de recursos devuelve el valor de la configuración que mejor coincida con la configuración del dispositivo. Para determinar qué configuración es la que mejor coincide, combine el conjunto de configuraciones de recursos superpuestos en el conjunto de configuraciones de recursos de destino y luego siga el flujo de resolución de recursos habitual (para obtener más detalles, consulte Cómo Android encuentra el recurso que mejor coincide ).

Por ejemplo, si una superposición define un valor para la configuración drawable-en y el objetivo define un valor para drawable-en-port , drawable-en-port tiene una mejor coincidencia, por lo que el valor de la configuración de destino drawable-en-port es elegido en tiempo de ejecución. Para superponer todas las configuraciones drawable-en , la superposición debe definir un valor para cada configuración drawable-en que define el objetivo.

Las superposiciones pueden hacer referencia a sus propios recursos, con diferentes comportamientos 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 al espacio de ID de recursos de destino ni a otros espacios de ID de recursos superpuestos, por lo que las superposiciones que hacen referencia a sus propios recursos funcionan como se esperaba.

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

Activar/desactivar superposiciones

Utilice la API OverlayManager para habilitar y deshabilitar superposiciones mutables (recupere la interfaz API usando Context#getSystemService(Context.OVERLAY_SERVICE) ). Una superposición solo puede ser habilitada por el paquete al que se dirige o por un paquete con el permiso android.permission.CHANGE_OVERLAY_PACKAGES . Cuando se habilita o deshabilita una superposición, los eventos de cambio de configuración se propagan al paquete de destino y se reinician las actividades de destino.

Restringir recursos superpuestos

En Android 10 o superior, la etiqueta XML <overlayable> expone un conjunto de recursos que los RRO pueden superponer. En el siguiente ejemplo, el archivo res/values/overlayable.xml , string/foo y integer/bar son recursos utilizados para tematizar la apariencia del dispositivo; Para superponer estos recursos, una superposición debe apuntar explícitamente a la colección de recursos superponibles por 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:

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

  • No está bien que un solo APK tenga dos bloques <overlayable name="foo"> .

El siguiente código 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 aplicación define una etiqueta <overlayable> , las superposiciones dirigidas a esa aplicación:

  • Debe especificar targetName .

  • Solo se pueden superponer los recursos enumerados en la etiqueta <overlayable> .

  • Solo puede apuntar a un nombre <overlayable> .

No puede habilitar una superposición dirigida a un paquete que expone recursos superponibles pero que no usa android:targetName para apuntar a una etiqueta <overlayable> específica.

Restringir políticas

Utilice la etiqueta <policy> para imponer restricciones a los recursos superponibles. El atributo type especifica qué políticas debe cumplir una superposición para anular los recursos incluidos. Los tipos admitidos 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 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 anular los recursos.
  • actor . Cualquier superposición firmada con la misma firma que el APK del actor puede anular los recursos. El actor se declara en la etiqueta de actor con nombre en la configuración del sistema.
  • config_signature . Cualquier superposición firmada con la misma firma que la aplicación overlay-config puede anular los recursos. La configuración overlay se declara en la etiqueta overlay-config-signature en la configuración del sistema.

El siguiente código 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, utilice barras verticales (|) como caracteres separadores. Cuando se especifican varias políticas, una superposición debe cumplir solo una política para anular los recursos enumerados dentro de la etiqueta <policy> .

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 lanzamiento de Android.

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

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

Usar OverlayConfig

En Android 11 o superior, puedes usar OverlayConfig para configurar la mutabilidad, el estado predeterminado y la prioridad de las superposiciones. Para configurar una superposición, cree o modifique el archivo ubicado en partition/overlay/config/config.xml , donde partition es la partición de la superposición que se va a configurar. Para configurarse, una superposición debe residir en el directorio overlay/ de la partición en la que está configurada la superposición. El siguiente código muestra un ejemplo 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>"

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

Prioridad de superposición

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

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

Fusionar archivos

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

Utilice atributos de manifiesto/RRO estáticos

En Android 10 o versiones anteriores, la inmutabilidad y la precedencia de la superposición se configuran mediante los siguientes atributos de manifiesto.

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

  • android:priority . El valor de este atributo numérico (que afecta solo a las superposiciones estáticas) configura la precedencia de la superposición cuando varias superposiciones estáticas tienen como objetivo el mismo valor de recurso. Un número más alto indica una prioridad más alta.

El siguiente código muestra un ejemplo 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 superior, si un archivo de configuración se encuentra en partition/overlay/config/config.xml , las superposiciones se configuran usando 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 impone la prioridad de la partición de superposición.

Además, Android 11 o superior elimina la posibilidad de utilizar 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, use la etiqueta SystemConfig <component-override> (nueva en Android 11).

Superposiciones de depuración

Para habilitar, deshabilitar y volcar superposiciones manualmente, use el siguiente comando de shell del administrador de superposiciones.

adb shell cmd overlay

OverlayManagerService usa idmap2 para asignar los ID de recursos en el paquete de destino a los ID de recursos en el paquete de superposición. Las asignaciones de ID generadas se almacenan en /data/resource-cache/ . Si su superposición no funciona correctamente, busque el archivo idmap correspondiente a su superposición en /data/resource-cache/ y luego ejecute 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