转载请注明原文地址:https://www.jianshu.com/p/e501e2bcf315
引子
在引入第三方库时,经常会出现下面两个编译错误:
- com.android.builder.packaging.DuplicateFileException
- Manifest merger failed with multiple errors
其中,DuplicateFileException 通过查看第三方库,依赖的额外库,然后使用 exclude 语句即可解决,具体语法如下图:
而 "Manifest merger failed" ,就是本文主要阐述的问题 —— 合并多个清单文件
概述
APK 文件只能包含一个AndroidManifest.xml 文件,但 Android Studio 项目可以包含多个文件(通过主源集、构建变体和导入的库提供)。因此,在构建应用时,Gradle 构建会将所有清单文件合并到一个封装到 APK 的清单文件中。
清单合并工具通过遵循某些合并启发式算法,并遵守您通过特殊XML 属性定义的合并首选项,来合并各个文件中的所有 XML 元素。 本页介绍清单合并的工作方式以及如何应用合并首选项来解决合并冲突。
提示: 使用 Merged Manifest 视图预览合并清单的效果并找出冲突错误。
合并优先级
合并工具根据每个清单文件的优先级将所有清单文件按顺序合并到一个文件中。 例如,如果您有 3 个清单文件,则会先将优先级最低的清单合并到优先级第 2 高的清单中,然后再将合并后的清单合并到优先级最高的清单中,如下图所示:
有 3 种基本的清单文件可以互相合并,它们的合并优先级如下(按优先级由高到低的顺序):
- 清单文件构建变体
如果您的变体有多个源集,则其清单优先级如下:
a. 构建变体清单(如 src/demoDebug/)
b. 构建类型清单(如 src/debug/)
c. 产品定制清单(如 src/demo/)
如果您使用的是定制维度,清单优先级将与每个维度在flavorDimensions属性中的列示顺序(按优先级由高到低的顺序排列)对应。 - 应用模块的主清单文件
- 所包括库中的清单文件
如果您有多个库,则其清单优先级与依赖顺序(库出现在Gradledependencies 块中的顺序)匹配。
例如,库清单合并至主清单,然后主清单合并至构建变体清单。
注:这些是对所有源集都相同的合并优先级,如使用源集构建 中所述。
重要说明:build.gradle 文件中的构建配置将替换合并清单文件中的任何对应属性。 例如,build.gradle 文件中的minSdkVersion将替换 清单元素中的匹配属性。 为了避免混淆,您只需省去 元素并在build.gradle 文件中定义这些属性。 For moredetails, see Configure Your Build.
合并冲突启发式算法
合并工具可以在逻辑上将一个清单中的每个 XML 元素与另一个清单中的对应元素相匹配。 (有关匹配如何进行的详细信息,请参阅有关合并策略的附录)。
如果优先级较低的清单中的元素与优先级较高的清单中的任何元素均不匹配,则该元素将被添加至合并清单。 但是,如果有匹配元素,则合并工具会尝试将其中的所有属性合并到相同元素中。如果工具发现两个清单包含相同属性,但值不相同,则会出现合并冲突。
如图- 4 即为描述合并工具尝试将所有属性合并到同一元素时可能出现的结果:
但是,在某些情况下,合并工具会采取其他行为方式以避免合并冲突:
-
<manifest>
元素中的属性绝不合并—仅使用优先级最高的清单中的属性。 -
android:required 属性 >
<uses-feature>
and<uses-library>
元素使用 OR 合并,因此如果出现冲突,系统将应用"true"
并始终包括某个清单所需的功能或库。 -
<uses-sdk>
元素始终使用优先级较高的清单中的值,但以下情况除外:- 如果低优先级清单的
minSdkVersion
值较高,除非您应用overrideLibrary
合并规则。 - 如果低优先级清单的
targetSdkVersion
值较低,合并工具将使用高优先级清单中的值,但也会添加任何必要的系统权限,以确保所导入的库继续正常工作(适用于较高的 Android 版本具有更多权限限制的情况)。 如需了解有关此行为的详细信息,请参阅有关隐式系统权限的部分。
- 如果低优先级清单的
- 绝不会在清单之间匹配
<intent-filter>
元素。 每个元素都被视为唯一元素,并添加至合并清单中的常用父元素。
对于属性之间的所有其他冲突,您将收到一则错误,并且必须通过在高优先级清单文件中添加特殊属性来指示合并工具如何解决此错误(请参阅下一节,其中介绍了有关合并规则标记的内容)。
不依赖于默认属性值。由于所有唯一属性都合并到相同元素中,如果高优先级清单实际上依赖于属性的默认值而不需要声明,则可能会导致意外结果。例如,如果高优先级清单不声明
android:launchMode
属性,则会使用"standard"
的默认值;但如果低优先级清单声明此属性具有其他值,该值将应用于合并清单(替代默认值)。因此,您应该按期望明确定义每个属性。(每个属性的默认值都会记录在 Manifest reference 中)。
合并规则标记
合并规则标记是一个 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 元素(给定清单元素中的所有元素及其所有子标记)应用合并规则,请使用以下属性:
1、tools:node="merge"
如果使用合并冲突启发式算法时没有冲突,则合并此标记中的所有属性以及所有嵌套元素。这是元素的默认行为。
译者注:也就是,当使用合并冲突启发式算法出现冲突,就会出现文章引文中提到的 "Manifest merger failed with multiple errors"
低优先级清单:
<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>
2、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>
3、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>
4、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>
5、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>
3、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="merge" 示例所示)。
属性标记
要改为仅向清单标记中的特定属性应用合并规则,请使用以下属性。每个属性接受一个或多个属性名称(包括属性命名空间),并以逗号分隔。
1、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”>
2、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”>
3、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>
默认情况下,导入 minSdkVersion 值高于主清单文件的库时会出错,而且无法导入该库。 要使合并工具忽略此冲突并导入库,同时保持应用的低 minSdkVersion 值,请将 overrideLibrary 属性添加至 <uses-sdk> 标记。属性值可以是一个或多个库包名称(以逗号分隔),指明可能替换主清单的 minSdkVersion 的库。
例如,如果应用的主清单按如下所示应用 overrideLibrary:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk android:targetSdkVersion="22" android:minSdkVersion="2"
tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
...
下面的清单文件就可以直接合并,不会出现与<uses-sdk> 标记相关的错误,合并清单将保留应用清单中的 minSdkVersion="2"。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib1">
<uses-sdk android:minSdkVersion="4" />
...
隐式系统权限
在最近的 Android 版本中,应用曾经可以自由访问的某些 Android API 已受 系统 权限限制。 为了避免中断预期会访问这些 API 的应用,最近的 Android 版本允许应用在无权限的情况下继续访问这些 API,前提是它们已将 targetSdkVersion
设置为低于添加限制的版本的值。此行为有效地向应用授予了隐式 权限,以允许访问 API。 因此,这可能会对具有不同targetSdkVersion
值的合并清单产生以下影响。
如果低优先级清单文件提供隐式权限的 targetSdkVersion
值较低,而且高优先级清单没有相同的隐式权限(由于其 targetSdkVersion
等于或高于添加限制的版本),合并工具将向合并清单显式添加系统权限。
例如,如果您的应用将 targetSdkVersion
设置为 4 或更高值,并且导入了将 targetSdkVersion
设置为 3 或更低值的库,合并工具会将 WRITE_EXTERNAL_STORAGE
权限添加至合并清单。 表 2 列出了可以添加至合并清单的所有可能权限。
注:如果您将应用的 targetSdkVersion
设置为 23 或更高值,则必须在应用尝试访问受这些权限保护的 API 时为任何危险权限执行运行时权限请求。 如需获得更多指导,请参阅使用系统权限。
检查合并清单并查找冲突
即使在构建 APK 之前,也可以预览合并清单,具体方法是:在 Android Studio 中打开您的 AndroidManifest.xml 文件,然后单击编辑器底部的 Merged Manifest 选项卡。
Merged Manifest 视图在左侧显示合并清单的结果,在右侧显示每个合并清单文件的相关信息,如图-2 所示。从低优先级文件中合并的元素在左侧以不同颜色突出显示。 每种颜色的关键字在右侧的 Manifest Sources 下方指定。
图片请看概述部分
属于构建的一部分但不构成元素或属性的清单文件列在右侧的 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 元素,工具将采用三种合并策略中的一种来合并这两个元素:
合并
将所有非冲突属性合并到同一标记中, 然后按其各自的合并策略合并子元素。 如果任何属性 相互冲突,请使用合并规则标记将它们合并在一起。仅合并子项
不整合或合并属性(仅保留 优先级最高的清单文件提供的属性)并按照其合并策略 合并子项。保留
将元素“按原样”保留,然后将其添加至 合并文件中的常用父元素。 此策略仅在可接受相同元素的多个 声明时使用。
下图,列出了每种元素类型、使用的合并策略类型以及用于确定两个清单之间的元素匹配的关键字。
参考:
https://developer.android.com/studio/build/manifest-merge