管理清单文件

本页面介绍了清单合并的工作原理,以及如何应用合并偏好来解决合并冲突。有关应用清单文件的介绍,请参阅应用清单概览

合并多个清单文件

您的 APK 或 Android App Bundle 文件只能包含一个 AndroidManifest.xml 文件,但您的 Android Studio 项目可能包含主源集、构建变体和导入库提供的多个清单文件。构建应用时,Gradle 构建会将所有清单文件合并成一个单独的清单文件,该文件会打包到您的应用中。

清单合并工具通过遵循合并启发式算法和您使用特殊 XML 属性定义的合并偏好,将每个文件中的所有 XML 元素组合在一起。

提示:使用后续部分中描述的合并清单视图,预览合并清单的结果并查找冲突错误。

合并优先级

合并工具根据每个清单文件的优先级,按顺序将所有清单文件合并到一个文件中。例如,如果您有三个清单文件,则优先级最低的清单将合并到次高优先级的清单中,然后该清单再合并到优先级最高的清单中,如图 1 所示。

图 1. 合并三个清单文件的过程,从最低优先级到最高优先级。

有三种基本类型的清单文件可以相互合并,它们的合并优先级如下(最高优先级在前):

  1. 您的构建变体的清单文件

    如果您的变体有多个源集,它们的清单优先级如下:

    • 构建变体清单(例如 src/demoDebug/
    • 构建类型清单(例如 src/debug/
    • 产品变种清单(例如 src/demo/

      如果您使用变种维度,清单优先级与 flavorDimensions 属性中列出的每个维度的顺序相对应(第一个具有最高优先级)。

  2. 应用模块的主清单文件
  3. 来自包含的库的清单文件

    如果您有多个库,它们的清单优先级与它们在 Gradle dependencies 块中出现的顺序匹配。

例如,库清单会合并到主清单中,然后主清单会合并到构建变体清单中。请注意,这些与使用源集构建中描述的所有源集的合并优先级相同。

重要提示:build.gradle 文件中的构建配置会覆盖合并清单文件中的任何相应属性。例如, build.gradlebuild.gradle.kts 文件中的 minSdk 会覆盖 <uses-sdk> 清单元素中的匹配属性。为避免混淆,请省略 <uses-sdk> 元素,并且仅在 build.gradle 文件中定义这些属性。有关更多详细信息,请参阅配置您的构建

合并冲突启发式算法

合并工具可以将一个清单中的每个 XML 元素逻辑地匹配到另一个清单中的相应元素。有关匹配工作原理的详细信息,请参阅前一节中的合并优先级

如果优先级较低的清单中的元素与优先级较高的清单中的任何元素都不匹配,则将其添加到合并清单中。但是,如果存在匹配的元素,则合并工具会尝试将每个清单中的所有属性合并到同一元素中。如果工具发现两个清单包含具有不同值的相同属性,则会发生合并冲突。

表 1 描述了合并工具尝试将所有属性合并到同一元素时可能出现的结果。

表 1. 属性值的默认合并行为

高优先级属性 低优先级属性 属性的合并结果
无值 无值 无值(使用默认值)
值 B 值 B
值 A 无值 值 A
值 A 值 A
值 B 冲突错误——您必须添加合并规则标记

但是,在少数情况下,合并工具的行为会有所不同,以避免合并冲突:

  • <manifest> 元素中的属性从不合并在一起;只使用优先级最高的清单中的属性。
  • <uses-feature> <uses-library> 元素中的 android:required 属性使用合并。如果存在冲突,则应用 "true",并且始终包含一个清单所需的特性或库。
  • <uses-sdk> 元素中的属性始终使用优先级较高的清单中的值,但在以下情况下除外:
    • 当优先级较低的清单具有更高的 minSdk 值时,除非您应用 overrideLibrary 合并规则,否则会发生错误。
    • 当优先级较低的清单具有较低的 targetSdkVersion 值时,合并工具会使用优先级较高的清单中的值,并且还会添加必要的任何系统权限,以确保导入的库继续正常运行(在较高 Android 版本增加了权限限制的情况下)。有关此行为的更多信息,请参阅有关隐式系统权限的部分。
  • <intent-filter> 元素从不合并清单之间。每个都被视为唯一的,并添加到合并清单中的公共父元素。

对于所有其他属性冲突,您会收到错误,并且必须通过在优先级较高的清单文件中添加特殊属性来指示合并工具如何解决该冲突。请参阅有关合并规则标记的下一节。

不要依赖默认属性值。由于所有唯一属性都合并到同一元素中,如果优先级较高的清单实际上依赖于未声明的属性的默认值,这可能会导致意外结果。例如,如果优先级较高的清单未声明 android:launchMode 属性,则它使用默认值 "standard"——但如果优先级较低的清单使用不同的值声明此属性,则该值将应用于合并清单,从而覆盖默认值。您应该明确定义每个属性,以符合您的预期。每个属性的默认值都记录在清单参考中。

合并规则标记

合并规则标记是您可以使用的一种 XML 属性,用于表达您对如何解决合并冲突或移除不需要的元素和属性的偏好。您可以将标记应用于整个元素或元素中的特定属性。

合并两个清单文件时,合并工具会在优先级较高的清单文件中查找这些标记。

所有标记都属于 Android tools 命名空间,因此您必须首先在 <manifest> 元素中声明此命名空间,如下所示:

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

节点标记

要将合并规则应用于整个 XML 元素(应用于给定清单元素中的所有属性及其所有子标签),请使用以下属性:

tools:node="merge"
在没有冲突的情况下,使用合并冲突启发式合并此标签中的所有属性和所有嵌套元素。这是元素的默认行为。

低优先级清单

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

高优先级清单

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

合并清单结果

<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"
仅合并此标签中的属性;不合并嵌套元素。

低优先级清单

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

高优先级清单

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

合并清单结果

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
</activity>
tools:node="remove"
从合并清单中移除此元素。当您在合并清单中发现不需要的元素,而该元素是由您无法控制的低优先级清单文件(例如导入的库)提供的时使用。

低优先级清单

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

高优先级清单

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

合并清单结果

<activity-alias android:name="com.example.alias">
  <meta-data android:name="duck"
      android:value="@string/quack"/>
</activity-alias>
tools:node="removeAll"
类似于 tools:node="remove",但它会移除所有匹配此元素类型的元素(在同一父元素内)。

低优先级清单

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

高优先级清单

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

合并清单结果

<activity-alias android:name="com.example.alias">
</activity-alias>
tools:node="replace"
完全替换低优先级元素。也就是说,如果低优先级清单中存在匹配元素,则忽略它并完全按照此清单中的样子使用此元素。

低优先级清单

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

高优先级清单

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

合并清单结果

<activity-alias android:name="com.example.alias">
  <meta-data android:name="fox"
      android:value="@string/dingeringeding"/>
</activity-alias>
tools:node="strict"
当低优先级清单中的此元素与高优先级清单中的元素不完全匹配时(除非通过其他合并规则标记解决),则生成构建失败。这将覆盖合并冲突启发式算法。例如,如果低优先级清单包含一个额外属性,则构建会失败(而默认行为会将额外属性添加到合并清单中)。

低优先级清单

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

高优先级清单

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

这会创建清单合并错误。在严格模式下,两个清单元素不能有任何不同。您必须应用其他合并规则标记来解决这些差异。(如果没有 tools:node="strict",这两个文件可以无错误地合并在一起,如 tools:node="merge" 的示例所示。)

属性标记

要仅将合并规则应用于清单标签中的特定属性,请使用以下属性。每个属性接受一个或多个属性名称(包括属性命名空间),以逗号分隔。

tools:remove="attr, ..."
从合并清单中移除指定的属性。当低优先级清单文件包含这些属性而您希望确保它们不进入合并清单时使用。

低优先级清单

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

高优先级清单

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

合并清单结果

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait">
tools:replace="attr, ..."
用此清单中的属性替换低优先级清单中的指定属性。换句话说,始终保留高优先级清单的值。

低优先级清单

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

高优先级清单

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

合并清单结果

<activity android:name="com.example.ActivityOne"
    android:theme="@newtheme"
    android:exported="true"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
tools:strict="attr, ..."
当低优先级清单中的这些属性与高优先级清单中的属性不完全匹配时,则生成构建失败。这是所有属性的默认行为,除了在合并冲突启发式算法中描述的具有特殊行为的属性。

低优先级清单

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

高优先级清单

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

这会创建清单合并错误。您必须应用其他合并规则标记来解决冲突。这是默认行为,因此显式添加 tools:strict="screenOrientation" 也会产生相同的结果。

您还可以将多个标记应用于一个元素,如下例所示:

低优先级清单

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

高优先级清单

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

合并清单结果

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

标记选择器

如果您只想将合并规则标记应用于特定的导入库,请添加带有库包名称的 tools:selector 属性。

例如,对于以下清单,remove 合并规则仅在低优先级清单文件来自 com.example.lib1 库时才应用:

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

如果低优先级清单来自任何其他来源,则 remove 合并规则将被忽略。

注意:如果您将其与其中一个属性标记一起使用,则它将应用于标记中指定的所有属性。

覆盖导入库的 <uses-sdk>

默认情况下,当导入的库的 minSdk 值高于主清单文件时,会发生错误并且无法导入该库。

要让合并工具忽略此冲突并导入库,同时保持您的应用较低的 minSdk 值,请将 overrideLibrary 属性添加到 <uses-sdk> 标签中。属性值可以是一个或多个库包名称(以逗号分隔),指示可以覆盖主清单的 minSdk 的库。

例如,如果您的应用的主清单应用 overrideLibrary 如下所示:

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

那么以下清单可以合并,而不会出现有关 <uses-sdk> 标签的错误,并且合并后的清单保留了应用清单中的 minSdk="2"

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

隐式系统权限

以前可由应用自由访问的一些 Android API 在 Android 的最新版本中已受到系统权限的限制。

为避免破坏那些期望访问这些 API 的应用,最新版本的 Android 允许应用在 targetSdkVersion 设置为低于添加限制的版本时,无需权限即可继续访问这些 API。此行为授予应用隐式权限以允许访问 API。具有不同 targetSdkVersion 值的合并清单可能会受到影响。

如果优先级较低的清单文件具有较低的 targetSdkVersion 值(为其提供了隐式权限),而优先级较高的清单没有相同的隐式权限(因为其 targetSdkVersion 等于或高于添加限制的版本),则合并工具会明确将系统权限添加到合并清单中。

例如,如果您的应用将 targetSdkVersion 设置为 4 或更高,并导入了 targetSdkVersion 设置为 3 或更低的库,则合并工具会将 WRITE_EXTERNAL_STORAGE 权限添加到合并清单中。

表 2 列出了可以添加到合并清单中的所有可能权限:

表 2. 合并工具可能添加到合并清单中的权限列表

低优先级清单声明 添加到合并清单的权限
targetSdkVersion 为 3 或更低 WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE
targetSdkVersion 为 15 或更低,并使用 READ_CONTACTS READ_CALL_LOG
targetSdkVersion 为 15 或更低,并使用 WRITE_CONTACTS WRITE_CALL_LOG

检查合并后的清单并查找冲突

即使在构建应用之前,您也可以预览合并后的清单外观。要查看预览,请执行以下操作:

  1. 在 Android Studio 中,打开您的 AndroidManifest.xml 文件。
  2. 点击编辑器底部的 Merged Manifest 标签页。

“合并清单”视图在左侧显示合并清单的结果,在右侧显示有关每个合并清单文件的信息,如图 2 所示。

从低优先级清单文件合并进来的元素在左侧以不同颜色突出显示。每种颜色的键都在 Manifest Sources 下指定。

图 2. 合并清单视图。

作为构建一部分但未提供元素或属性的清单文件列在 Other Manifest Files 下。

要查看有关元素来源的信息,请在左侧窗格中单击它,详细信息将显示在 Merging Log 下。

如果发生任何冲突,它们将显示在 Merging Errors 下,并附有关于如何使用合并规则标记解决冲突的建议。

错误也会打印在 Event Log 窗口中。要查看它们,请选择 View > Tool Windows > Event Log

要查看合并决策树的完整日志,您可以在模块的 build/outputs/logs/ 目录中找到日志文件,名为 manifest-merger-buildVariant-report.txt

合并策略

清单合并工具可以将一个清单文件中的每个 XML 元素逻辑地匹配到另一个文件中的相应元素。合并器使用匹配键匹配每个元素,该键可以是唯一的属性值(如 android:name)或标签本身的自然唯一性(例如,只能有一个 <supports-screen> 元素)。

如果两个清单具有相同的 XML 元素,则工具使用以下三种合并策略之一将这两个元素合并在一起:

合并
将所有不冲突的属性合并到同一标签中,并根据各自的合并策略合并子元素。如果任何属性相互冲突,则使用合并规则标记将它们合并在一起。
仅合并子级
不合并属性(仅保留优先级最高的清单文件提供的属性),并根据其合并策略合并子元素。
保留
保留元素原样,并将其添加到合并文件中的公共父元素。仅当允许存在同一元素的多个声明时才使用此功能。

表 3 列出了每种元素类型、使用的合并策略类型以及用于确定两个清单之间元素匹配的键。

表 3. 清单元素合并策略和匹配键

元素 合并策略 匹配键
<action> 合并 android:name 属性
<activity> 合并 android:name 属性
<application> 合并 每个 <manifest> 只有一个。
<category> 合并 android:name 属性
<data> 合并 每个 <intent-filter> 只有一个。
<grant-uri-permission> 合并 每个 <provider> 只有一个。
<instrumentation> 合并 android:name 属性
<intent-filter> 保留 无匹配;允许在父元素内进行多次声明。
<manifest> 仅合并子级 每个文件只有一个。
<meta-data> 合并 android:name 属性
<path-permission> 合并 每个 <provider> 只有一个。
<permission-group> 合并 android:name 属性
<permission> 合并 android:name 属性
<permission-tree> 合并 android:name 属性
<provider> 合并 android:name 属性
<receiver> 合并 android:name 属性
<screen> 合并 android:screenSize 属性
<service> 合并 android:name 属性
<supports-gl-texture> 合并 android:name 属性
<supports-screen> 合并 每个 <manifest> 只有一个。
<uses-configuration> 合并 每个 <manifest> 只有一个。
<uses-feature> 合并 android:name 属性(如果不存在,则为 android:glEsVersion 属性)
<uses-library> 合并 android:name 属性
<uses-permission> 合并 android:name 属性
<uses-sdk> 合并 每个 <manifest> 只有一个。
自定义元素 合并 无匹配;这些元素对合并工具来说是未知的,并且总是包含在合并清单中。

将构建变量注入清单

如果需要将 build.gradle 文件中定义的变量插入到 AndroidManifest.xml 文件中,可以使用 manifestPlaceholders 属性。此属性接受一个键值对映射,如下所示:

Groovy

android {
    defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
    }
    ...
}

Kotlin

android {
    defaultConfig {
        manifestPlaceholders["hostName"] = "www.example.com"
    }
    ...
}

然后,您可以将其中一个占位符作为属性值插入到清单文件中:

<intent-filter ... >
    <data android:scheme="https" android:host="${hostName}" ... />
    ...
</intent-filter>

默认情况下,构建工具还会以 ${applicationId} 占位符的形式提供您的应用应用 ID。该值始终与当前构建的最终应用 ID 匹配,包括构建变体所做的更改。当您希望为意图操作等标识符使用唯一的命名空间时(即使在您的构建变体之间),这会很有用。

例如,如果您的 build.gradle 文件如下所示:

Groovy

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    flavorDimensions "type"
    productFlavors {
        free {
            applicationIdSuffix ".free"
            dimension "type"
        }
        pro {
            applicationIdSuffix ".pro"
            dimension "type"
        }
    }
}

Kotlin

android {
    defaultConfig {
        applicationId = "com.example.myapp"
    }
    flavorDimensions += "type"
    productFlavors {
        create("free") {
            applicationIdSuffix = ".free"
            dimension = "type"
        }
        create("pro") {
            applicationIdSuffix = ".pro"
            dimension = "type"
        }
    }
}

然后,您可以像这样在清单中插入应用 ID:

<intent-filter ... >
    <action android:name="${applicationId}.TRANSMOGRIFY" />
    ...
</intent-filter>

当您构建“free”产品风味时,清单结果如下:

<intent-filter ... >
   <action android:name="com.example.myapp.free.TRANSMOGRIFY" />
    ...
</intent-filter>

有关更多信息,请阅读设置应用 ID