Cómo fusionar varios archivos de manifiesto

Tu archivo de APK o Android App Bundle puede contener solo un archivo AndroidManifest.xml, pero tu proyecto de Android Studio puede contener varios, ya que el conjunto de fuentes principal, las variantes de las compilaciones y las bibliotecas importadas pueden proporcionarlos. Por lo tanto, cuando creas tu app, la compilación de Gradle fusiona todos los archivos de manifiesto en un solo archivo de manifiesto empaquetado en tu app.

La herramienta de combinación de manifiesto combina todos los elementos XML de cada archivo mediante una heurística de fusión y de acuerdo con las preferencias de fusión definidas con atributos XML especiales. En esta página, se describe el funcionamiento de la combinación de manifiesto y cómo aplicar preferencias de combinación para resolver conflictos.

Sugerencia: Usa la vista Merged Manifest para obtener una vista previa de los resultados de tu manifiesto combinado y detectar errores de conflicto.

Prioridades de fusión

La herramienta de combinación combina todos los archivos de manifiesto en un solo archivo. Para ello, fusiona los archivos de manifiesto de manera secuencial según la prioridad de cada uno. Por ejemplo, si tienes tres archivos de manifiesto, el manifiesto de menor prioridad se fusiona con el de prioridad superior que sigue, y a su vez estos archivos fusionados se vuelven a fusionar con el manifiesto de mayor prioridad, como se ilustra en la figura 1.

Figura 1: Proceso de fusión de tres archivos de manifiesto, de menor prioridad (izquierda) a mayor prioridad (derecha)

Existen tres tipos básicos de archivos de manifiesto que pueden combinarse. A continuación, se indican sus prioridades de combinación (la mayor prioridad se muestra primero):

  1. Archivo de manifiesto para tu variante de compilación

    Si tienes varios conjuntos de fuentes para tu variante, las prioridades de manifiesto son las siguientes:

    1. Manifiesto de variantes de compilación (como src/demoDebug/)
    2. Manifiesto de tipos de compilación (como src/debug/)
    3. Manifiesto de variantes de productos (como src/demo/)

      Si usas dimensiones de tipo, las prioridades de manifiesto corresponden al orden en que se enumera cada dimensión en la propiedad flavorDimensions (la mayor prioridad se muestra primero).

  2. Archivo de manifiesto principal para el módulo de la app
  3. Archivo de manifiesto de una biblioteca incluida

    Si tienes varias bibliotecas, las prioridades de manifiestos de estas coinciden con el orden de dependencia (el orden en que aparecen en tu bloque dependencies de Gradle).

Por ejemplo, un manifiesto de biblioteca se combina en el manifiesto principal y, luego, este se combina con el manifiesto de variantes de compilación.

Importante: Las configuraciones de compilación del archivo build.gradle anulan cualquier atributo coincidente en el archivo de manifiesto combinado. Por ejemplo, el minSdkVersion del archivo build.gradle anula el atributo coincidente del elemento de manifiesto <uses-sdk>. Para evitar confusiones, deja afuera el elemento <uses-sdk> y define estas propiedades solamente en el archivo build.gradle. Para obtener información detallada, consulta Cómo configurar tu compilación.

Heurística de conflictos de combinación

La herramienta de combinación puede unir de manera lógica cada elemento XML de un manifiesto con un elemento coincidente del otro manifiesto. (Para obtener detalles sobre el funcionamiento del mecanismo de coincidencia, consulta el apéndice sobre políticas de fusión).

Si un elemento del manifiesto de prioridad inferior no coincide con ningún elemento del manifiesto de prioridad superior, se agregará al manifiesto combinado. Sin embargo, si hay un elemento coincidente, la herramienta de combinación intenta combinar todos los atributos de cada uno en el mismo elemento. Si la herramienta detecta que ambos manifiestos contienen el mismo atributo con diferentes valores, se produce un conflicto de fusión.

En la tabla 1, se ilustran los posibles resultados que pueden generarse cuando una herramienta de combinación intenta combinar todos los atributos en el mismo elemento.

Tabla 1: Comportamiento de fusión predeterminado para valores de atributo

Atributo de prioridad alta Atributo de prioridad baja Resultado fusionado del atributo
Sin valor Sin valor Sin valor (usar valor predeterminado)
Valor B Valor B
Valor A Sin valor Valor A
Valor A Valor A
Valor B Error de conflicto: Debes agregar un marcador de regla de combinación.

Sin embargo, existen algunas situaciones en las que la herramienta de combinación se comporta de manera diferente para evitar conflictos de fusión:

  • Nunca se combinan los atributos del elemento <manifest>; solo se usan los atributos del manifiesto de mayor prioridad.
  • El atributo android:required de los elementos <uses-feature> y <uses-library> usa una combinacón OR, de modo que si hay un conflicto, se aplica "true" y siempre se incluye la función o biblioteca requerida por un manifiesto.
  • Los atributos del elemento <uses-sdk> siempre usan el valor del manifiesto de prioridad superior, excepto en las siguientes situaciones:
    • Cuando el manifiesto de prioridad inferior tiene un valor minSdkVersion que es mayor, se produce un error, a menos que apliques la regla de fusión overrideLibrary.
    • Cuando el manifiesto de prioridad inferior tiene un valor de targetSdkVersion inferior, la herramienta de combinación usa el valor del manifiesto de prioridad superior, pero también agrega los permisos del sistema necesarios para garantizar que la biblioteca importada continúe funcionando correctamente (para casos en que la versión superior de Android tenga mayores restricciones de permisos). Para obtener más información sobre este comportamiento, consulta la sección sobre permisos implícitos del sistema.
  • El elemento <intent-filter> nunca coincide entre diferentes manifiestos. Cada uno recibe tratamiento exclusivo y se agrega al elemento principal común en el manifiesto combinado.

Para todos los demás conflictos entre atributos, aparecerá un error y deberás indicar a la herramienta de combinación la manera de corregirlo. Para ello, debes agregar un atributo especial en el archivo de manifiesto de prioridad superior (consulta la siguiente sección sobre marcadores de reglas de combinación).

No dependas de los valores de atributos predeterminados. Como todos los atributos únicos se combinan en el mismo elemento, es posible que se produzcan resultados inesperados si el manifiesto de prioridad superior en realidad depende del valor predeterminado de un atributo sin declararlo. Por ejemplo, si el manifiesto de prioridad superior no declara el atributo android:launchMode, se usará el valor predeterminado "standard"; pero si el manifiesto de prioridad inferior declara este atributo con un valor diferente, se aplicará ese valor al manifiesto combinado (y se anulará el valor predeterminado). Debes definir cómo deseas que sea cada atributo de manera explícita. (Los valores predeterminados de cada atributo se documentan en la referencia del manifiesto).

Marcadores de reglas de fusión

Un marcador de reglas de fusión es un atributo XML que puedes usar para expresar tu preferencia a fin de resolver conflictos de fusión o quitar elementos y atributos no deseados. Puedes aplicar un marcador a un elemento entero o solo a atributos específicos de un elemento.

Cuando se fusionan dos archivos de manifiesto, la herramienta de combinación busca estos marcadores en el archivo de manifiesto de prioridad superior.

Todos los marcadores pertenecen al espacio de nombres tools de Android; por ello, primero debes declarar este espacio de nombres en el elemento <manifest>, como se observa a continuación:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp"
    xmlns:tools="http://schemas.android.com/tools">

Marcadores de nodos

Para aplicar una regla de combinación a un elemento XML completo (a todos los atributos de un elemento de manifiesto específico y a todas sus etiquetas secundarias), usa los siguientes atributos:

tools:node="merge"
Combina todos los atributos de esta etiqueta y todos los elementos anidados cuando no hay conflictos usando la heurística de conflictos de combinación. Este es el comportamiento predeterminado de los elementos.

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:node="merge">
</activity>

Resultado del manifiesto combinado:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
tools:node="merge-only-attributes"
Fusiona solo los atributos de esta etiqueta, no fusiones los elementos anidados.

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <data android:type="image/*" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:node="merge-only-attributes">
</activity>

Resultado del manifiesto combinado:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
</activity>
tools:node="remove"
Quita este elemento del manifiesto combinado. Aunque parezca que debes borrar este elemento, cuando descubras en tu manifiesto combinado un elemento que no necesites y que se haya proporcionado a través de un archivo de manifiesto de prioridad inferior que esté fuera de tu control (como una biblioteca importada), deberás usar esta opción.

Manifiesto de prioridad baja:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      android:value="@string/moo"/>
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>

Manifiesto de prioridad alta:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      tools:node="remove"/>
</activity-alias>

Resultado del manifiesto combinado:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>
tools:node="removeAll"
Similar a tools:node="remove", pero quita todos los elementos que coinciden con este tipo de elemento (dentro del mismo elemento principal).

Manifiesto de prioridad baja:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      android:value="@string/moo"/>
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>

Manifiesto de prioridad alta:

<activity-alias android:name="com.example.alias">
  <meta-data tools:node="removeAll"/>
</activity-alias>

Resultado del manifiesto combinado:

<activity-alias android:name="com.example.alias">
</activity-alias>
tools:node="replace"
Reemplaza el elemento de prioridad inferior por completo. Es decir, si hay un elemento coincidente en el manifiesto de prioridad inferior, ignóralo y usa este elemento exactamente como aparece en este manifiesto.

Manifiesto de prioridad baja:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="cow"
      android:value="@string/moo"/>
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>

Manifiesto de prioridad alta:

<activity-alias android:name="com.example.alias"
    tools:node="replace">
  <meta-data android:name="fox"
      android:value="@string/dingeringeding"/>
</activity-alias>

Resultado del manifiesto combinado:

<activity-alias android:name="com.example.alias">
  <meta-data android:name="fox"
      android:value="@string/dingeringeding"/>
</activity-alias>
tools:node="strict"
Genera una falla de compilación cada vez que este elemento del manifiesto de prioridad inferior no coincida exactamente con el elemento del manifiesto de prioridad superior (salvo que se resuelva a través de otros marcadores de reglas de combinación). De esta manera, se anula la heurística de conflictos de fusión. Por ejemplo, si en el manifiesto de prioridad inferior simplemente se incluye un atributo adicional, la compilación falla (mientras que el comportamiento predeterminado agrega el atributo adicional al manifiesto combinado).

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:node="strict">
</activity>

De esta manera, se crea un error de fusión de manifiestos. Los dos elementos de manifiestos no pueden diferir de ninguna manera en el modo estricto. Por ello, debes aplicar otros marcadores de reglas de fusión para resolver estas diferencias. (Generalmente, estos dos se fusionarán de manera correcta, como se observa en el ejemplo anterior para tools:node="merge").

Marcadores de atributos

Para aplicar una regla de fusión solo a atributos específicos en una etiqueta de manifiesto, usa los siguientes atributos. Cada atributo acepta uno o más nombres (incluido el espacio de nombres de los atributos), separados por coma.

tools:remove="attr, ..."
Quita los atributos especificados del manifiesto combinado. Aunque parezca que podrías borrar estos atributos, cuando el archivo de prioridad inferior incluye estos atributos y quieres asegurarte de que no se incluyan en el manifiesto combinado, debes usar esta opción.

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:windowSoftInputMode="stateUnchanged">

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:remove="android:windowSoftInputMode">

Resultado del manifiesto combinado:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait">
tools:replace="attr, ..."
Reemplaza los atributos especificados en el manifiesto de menor prioridad por los de este manifiesto. Es decir, conserva siempre los valores de los manifiestos de prioridad superior.

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:theme="@oldtheme"
    android:exported="false"
    android:windowSoftInputMode="stateUnchanged">

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    tools:replace="android:theme,android:exported">

Resultado del manifiesto combinado:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
tools:strict="attr, ..."
Genera una falla de compilación cada vez que estos elementos del manifiesto de prioridad inferior no coincidan exactamente con el elemento del manifiesto de prioridad superior. Este es el comportamiento predeterminado de todos los atributos, excepto aquellos con comportamientos especiales, como se describe en la heurística de conflictos de combinación.

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="landscape">
</activity>

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:strict="android:screenOrientation">
</activity>

De esta manera, se crea un error de fusión de manifiestos. Para resolver este conflicto, debes aplicar otros marcadores de reglas de fusión. (Recuerda: Este es el comportamiento predeterminado, por ello, el ejemplo anterior tiene el mismo resultado si quitas tools:strict="screenOrientation").

También puedes aplicar varios marcadores a un elemento, como se describe a continuación.

Manifiesto de prioridad baja:

<activity android:name="com.example.ActivityOne"
    android:theme="@oldtheme"
    android:exported="false"
    android:allowTaskReparenting="true"
    android:windowSoftInputMode="stateUnchanged">

Manifiesto de prioridad alta:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    tools:replace="android:theme,android:exported"
    tools:remove="android:windowSoftInputMode">

Resultado del manifiesto combinado:

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:allowTaskReparenting="true"
    android:screenOrientation="portrait">

Selector de marcadores

Si deseas aplicar los marcadores de reglas de fusión únicamente a una biblioteca importada específica, agrega el atributo tools:selector con el nombre del paquete de la biblioteca.

Por ejemplo, con el siguiente manifiesto, solo se aplica la regla de fusión remove cuando el archivo de manifiesto de prioridad inferior pertenece a la biblioteca com.example.lib1.

<permission android:name="permissionOne"
    tools:node="remove"
    tools:selector="com.example.lib1">

Si el manifiesto de prioridad inferior pertenece a otra fuente, se ignora la regla de fusión remove.

Nota: Si usas este procedimiento con uno de los marcadores de atributos, este se aplicará a todos los atributos especificados en el marcador.

Cómo anular <uses-sdk> para las bibliotecas importadas

En forma predeterminada, si importas una biblioteca con un valor de minSdkVersion superior al del archivo de manifiesto principal, se produce un error y no es posible importar la biblioteca. Para que la herramienta de combinación ignore este conflicto e importe la biblioteca al mismo tiempo que mantiene el valor de minSdkVersion inferior de tu app, agrega el atributo overrideLibrary a la etiqueta <uses-sdk>. El valor del atributo puede ser uno o más nombres de paquetes de bibliotecas (separados por comas), que indiquen las bibliotecas que pueden anular el valor minSdkVersion del manifiesto principal.

Por ejemplo, si el manifiesto principal de tu app aplica overrideLibrary como se indica a continuación:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.app"
          xmlns:tools="http://schemas.android.com/tools">
  <uses-sdk tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
...

El siguiente manifiesto se puede fusionar sin errores relacionados con la etiqueta <uses-sdk> y el manifiesto combinado mantiene el valor minSdkVersion="2" del manifiesto de la app.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lib1">
   <uses-sdk android:minSdkVersion="4" />
...

Permisos del sistema implícitos

Algunas API de Android que solían ser de libre acceso por medio de las apps ahora están restringidas por permisos del sistema en versiones recientes de Android. Para evitar daños en apps que buscan acceso a estas API, las últimas versiones de Android permiten que las apps continúen accediendo a estas API sin permiso si establecieron targetSdkVersion en un valor inferior al de la versión en la que se agregó la restricción. Este comportamiento efectivamente otorga a la app un permiso implícito para acceder a las API. De esta manera, los manifiestos combinados que tienen diferentes valores de targetSdkVersion pueden resultar afectados, como se indica a continuación.

Si el archivo de manifiesto de menor prioridad tiene un valor de targetSdkVersion inferior que le otorga un permiso implícito, y el manifiesto de prioridad superior no tiene el mismo permiso implícito (porque su targetSdkVersion es igual o superior al de la versión en la que se agregó la restricción), la herramienta de combinación agrega de manera explícita el permiso del sistema al manifiesto combinado.

Por ejemplo, si tu app establece targetSdkVersion en 4 o un valor superior e importa una biblioteca con targetSdkVersion establecido en 3 o un valor inferior, la herramienta de combinación agrega el permiso WRITE_EXTERNAL_STORAGE al manifiesto combinado. En la tabla 2, se enumeran todos los posibles permisos que se pueden agregar a tu manifiesto combinado.

Tabla 2: Lista de permisos que la herramienta de combinación puede agregar al manifiesto combinado

El manifiesto de prioridad inferior declara Permisos agregados al manifiesto fusionado
targetSdkVersion es 3 o menor WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE
targetSdkVersion es 15 o menor y usa READ_CONTACTS READ_CALL_LOG
targetSdkVersion es 15 o menor y usa WRITE_CONTACTS WRITE_CALL_LOG

Cómo inspeccionar el manifiesto combinado y buscar conflictos

Incluso antes de compilar tu app, puedes obtener una vista previa de tu manifiesto combinado. Para ello, debes abrir tu archivo AndroidManifest.xml en Android Studio y hacer clic en la pestaña Merged Manifest que se encuentra en la parte inferior del editor.

En la vista Merged Manifest, se muestran los resultados del manifiesto combinado en la parte izquierda y se incluye información sobre cada manifiesto combinado a la derecha, como se observa en la figura 2. Los elementos combinados a partir de archivos de manifiesto de prioridad inferior se destacan en diferentes colores, a la izquierda. El significado de cada color se especifica en Manifest Sources, a la derecha.

Figura 2: La vista Merged Manifest

Los archivos de manifiesto que formaron parte de la compilación pero que no aportaron elementos ni atributos se detallan en Other Manifest Files, a la derecha.

Para ver información sobre el origen de un elemento, haz clic sobre un elemento en la parte izquierda. Podrás ver los detalles en Registro de combinaciones, a la derecha.

Si se produce algún conflicto, este aparecerá en Errores de combinación, en la parte derecha con una recomendación sobre cómo resolver el conflicto usando los marcadores de reglas de combinación. También se incluyen errores impresos en la ventana Event Log (selecciona View > Tool Windows > Event Log).

Si deseas ver un registro completo del árbol de decisiones de fusión, puedes buscar el archivo de registro en el directorio build/outputs/logs/ de tu módulo, denominado manifest-merger-buildVariant-report.txt.

Apéndice: Políticas de fusión

La herramienta de combinación de manifiestos puede unir de manera lógica cada elemento XML de un archivo de manifiesto con un elemento coincidente de otro archivo. La herramienta une los elementos a través de una “clave de coincidencia”, que puede ser un valor de atributo único (como android:name) o la exclusividad natural de la propia etiqueta (por ejemplo, puede haber un solo elemento <supports-screen>). Si los dos manifiestos tienen el mismo elemento XML, la herramienta los fusiona con una de las tres políticas de fusión:

Combinar
Combina todos los atributos sin conflictos en la misma etiqueta, y los elementos secundarios según sus respectivas políticas de combinación. Si alguno de los atributos tiene un conflicto con otro, los fusiona con los marcadores de reglas de fusión.
Fusionar solo elementos secundarios
No combina ni fusiona los atributos (mantiene únicamente los atributos provistos por el archivo de manifiesto de mayor prioridad), y fusiona los elementos secundarios según sus políticas de fusión.
Keep
Deja el elemento "como se encuentra" y lo agrega al elemento principal común en el archivo fusionado. Esta opción solo se usa cuando es aceptable que haya diferentes declaraciones del mismo elemento.

En la tabla 3, se enumera cada tipo de elemento, el tipo de política de fusión empleado y la clave usada para determinar la coincidencia de elementos entre dos manifiestos.

Tabla 3: Claves de coincidencia y políticas de fusión de elementos de manifiesto

Elemento Política de fusión Clave de coincidencia
<action> Fusionar Atributo android:name
<activity> Fusionar Atributo android:name
<application> Fusionar Solo hay una por <manifest>
<category> Fusionar Atributo android:name
<data> Fusionar Solo hay una por <intent-filter>
<grant-uri-permission> Fusionar Solo hay una por <provider>
<instrumentation> Fusionar Atributo android:name
<intent-filter> Keep Sin coincidencia; se permiten varias declaraciones dentro del elemento principal
<manifest> Fusionar solo elementos secundarios Solo hay uno por archivo.
<meta-data> Fusionar Atributo android:name
<path-permission> Fusionar Solo hay una por <provider>
<permission-group> Fusionar Atributo android:name
<permission> Fusionar Atributo android:name
<permission-tree> Fusionar Atributo android:name
<provider> Fusionar Atributo android:name
<receiver> Fusionar Atributo android:name
<screen> Fusionar Atributo android:screenSize
<service> Fusionar Atributo android:name
<supports-gl-texture> Fusionar Atributo android:name
<supports-screen> Fusionar Solo hay una por <manifest>
<uses-configuration> Fusionar Solo hay una por <manifest>
<uses-feature> Fusionar Atributo android:name (si no está presente, entonces el atributo android:glEsVersion)
<uses-library> Fusionar Atributo android:name
<uses-permission> Fusionar Atributo android:name
<uses-sdk> Fusionar Solo hay una por <manifest>
Elementos personalizados Fusionar Sin coincidencia; la herramienta de combinación no los reconoce, por lo que siempre se incluyen en el manifiesto combinado.