此页面描述了清单合并的工作原理以及如何应用合并首选项来解决合并冲突。有关应用程序清单文件的介绍,请参阅 应用程序清单概述。
合并多个清单文件
您的 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
属性使用或合并。如果发生冲突,将应用"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 列出了可能会添加到合并清单中的所有可能的权限
较低优先级清单声明 | 添加到合并清单的权限 |
---|---|
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。