此页面介绍清单合并的工作原理以及如何应用合并首选项来解决合并冲突。有关应用清单文件的简介,请参阅应用清单概述。
合并多个清单文件
您的 APK 或 Android 应用包文件只能包含一个AndroidManifest.xml
文件,但您的 Android Studio 项目可能包含由主源集、构建变体和导入库提供的多个清单文件。构建应用时,Gradle 构建会将所有清单文件合并到一个打包到应用中的单个清单文件中。
清单合并工具通过遵循合并启发式方法并遵守您使用特殊 XML 属性定义的合并首选项,将每个文件中的所有 XML 元素组合在一起。
提示:使用合并清单视图(在以下部分中描述)预览合并清单的结果并查找冲突错误。
合并优先级
合并工具会根据每个清单文件的优先级,依次将所有清单文件合并到一个文件中。例如,如果您有三个清单文件,则最低优先级的清单将合并到下一个最高优先级的清单中,然后该清单将合并到最高优先级的清单中,如图 1 所示。
可能相互合并的三种基本类型的清单文件,其合并优先级如下(从最高优先级到最低优先级):
- 您的构建变体的清单文件
如果您的变体有多个源集,则其清单优先级如下所示:
- 构建变体清单(例如
src/demoDebug/
) - 构建类型清单(例如
src/debug/
) - 产品风味清单(例如
src/demo/
)如果您使用的是风味维度,则清单优先级对应于每个维度在
flavorDimensions
属性中列出的顺序(第一个具有最高优先级)。
- 构建变体清单(例如
- 应用模块的主清单文件
- 包含库的清单文件
如果您有多个库,则其清单优先级与它们在 Gradle
dependencies
块中出现的顺序相匹配。
例如,库清单将合并到主清单中,然后主清单将合并到构建变体清单中。请注意,这些是所有源集的相同合并优先级,如使用源集构建中所述。
重要提示:来自build.gradle
文件的构建配置会覆盖合并清单文件中任何相应的属性。例如,来自build.gradle
或build.gradle.kts
文件的 minSdk
会覆盖<uses-sdk>
清单元素中的匹配属性。为了避免混淆,请省略<uses-sdk>
元素,并且仅在build.gradle
文件中定义这些属性。有关更多详细信息,请参阅配置您的构建。
合并冲突启发式方法
合并工具可以将一个清单中的每个 XML 元素逻辑地与另一个清单中的相应元素匹配。有关匹配工作原理的详细信息,请参阅上一节中的合并优先级。
如果来自低优先级清单的元素与高优先级清单中的任何元素都不匹配,则将其添加到合并后的清单中。但是,如果存在匹配的元素,则合并工具会尝试将每个元素的所有属性合并到同一个元素中。如果工具发现两个清单都包含具有不同值的相同属性,则会发生合并冲突。
表 1 描述了合并工具尝试将所有属性合并到同一个元素时可能的结果。
高优先级属性 | 低优先级属性 | 属性的合并结果 |
---|---|---|
无值 | 无值 | 无值(使用默认值) |
值 B | 值 B | |
值 A | 无值 | 值 A |
值 A | 值 A | |
值 B | 冲突错误—您必须添加 合并规则标记。 |
但是,在某些情况下,合并工具的行为有所不同,以避免合并冲突
- 位于
<manifest>
元素中的属性永远不会合并在一起;仅使用来自最高优先级清单的属性。 - 位于
<uses-feature>
和<uses-library>
元素中的android:required
属性使用 OR 合并。如果发生冲突,则应用"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
属性以及库包名称。
例如,使用以下清单,仅当低优先级清单文件来自 com.example.lib1
库时,才会应用 remove
合并规则
<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 允许应用在没有权限的情况下继续访问这些 API,前提是 targetSdkVersion
设置为低于添加限制的版本的数值。此行为授予应用隐式权限以允许访问 API。具有不同 targetSdkVersion
值的合并清单可能会受到影响。
如果低优先级清单文件具有较低的 targetSdkVersion
值,从而为其提供隐式权限,而高优先级清单没有相同的隐式权限(因为其 targetSdkVersion
等于或高于添加限制的版本),则合并工具会显式地将系统权限添加到合并后的清单中。
例如,如果应用将 targetSdkVersion
设置为 4 或更高,并导入 targetSdkVersion
设置为 3 或更低的库,则合并工具会将 WRITE_EXTERNAL_STORAGE
权限添加到合并后的清单中。
表 2 列出了可能添加到合并清单中的所有可能的权限
低优先级清单声明 | 添加到合并清单中的权限 |
---|---|
targetSdkVersion 为 3 或更低 |
WRITE_EXTERNAL_STORAGE 、READ_PHONE_STATE |
targetSdkVersion 为 15 或更低,并使用 READ_CONTACTS |
READ_CALL_LOG |
targetSdkVersion 为 15 或更低,并使用 WRITE_CONTACTS |
WRITE_CALL_LOG |
检查合并清单并查找冲突
即使在构建应用之前,您也可以查看合并清单的外观预览。要查看预览,请执行以下操作
- 在 Android Studio 中,打开
AndroidManifest.xml
文件。 - 点击编辑器底部的合并清单选项卡。
合并清单视图在左侧显示合并清单的结果,在右侧显示每个合并清单文件的信息,如图 2 所示。
从低优先级清单文件中合并的元素在左侧以不同的颜色突出显示。每种颜色的键在**清单来源**下指定。
构建中包含但未贡献元素或属性的清单文件列在**其他清单文件**下。
要查看有关元素来源的信息,请在左侧窗格中单击它,详细信息将显示在**合并日志**下。
如果发生任何冲突,它们将显示在**合并错误**下,并提供使用合并规则标记解决冲突的建议。
错误也会打印在**事件日志**窗口中。要查看它们,请选择**查看 > 工具窗口 > 事件日志**。
要查看合并决策树的完整日志,您可以在模块的build/outputs/logs/
目录中找到日志文件,名为manifest-merger-buildVariant-report.txt
。
合并策略
清单合并工具可以将来自一个清单文件的所有 XML 元素逻辑地匹配到另一个文件中的对应元素。合并器使用匹配键匹配每个元素,匹配键可以是唯一的属性值(例如android:name
)或标签本身的自然唯一性(例如,只能有一个<supports-screen>
元素)。
如果两个清单具有相同的 XML 元素,则该工具将使用三种合并策略之一将这两个元素合并在一起
- 合并
- 将所有非冲突属性合并到同一个标签中,并根据其各自的合并策略合并子元素。如果任何属性相互冲突,则使用合并规则标记将它们合并在一起。
- 仅合并子元素
- 不合并或合并属性(仅保留最高优先级清单文件提供的属性),并根据其合并策略合并子元素。
- 保留
- 按原样保留元素,并将其添加到合并文件中公共的父元素中。仅当允许存在同一元素的多个声明时才使用此方法。
表 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。