目录
Markdown版本笔记 | 我的GitHub首页 | 我的博客 | 我的微信 | 我的邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目录
Merge AndroidManifest 合并清单文件
合并多个清单文件
Merge multiple manifest files
APK 文件只能包含一个 AndroidManifest.xml
文件,但 Android Studio 项目可以包含多个文件[may contain several provided by the main source set
, build variants
, and imported libraries
]。因此,在构建应用时,Gradle 构建会将所有清单文件合并到一个封装到 APK 的清单文件中[merges all manifest files into a single manifest file that‘s packaged into your APK]。
清单合并工具通过遵循某些合并启发式算法[merge heuristics],并遵守您通过特殊 XML 属性定义的合并首选项[merge preferences],来合并各个文件中的所有 XML 元素 。 本页介绍清单合并的工作方式以及如何应用合并首选项来解决合并冲突[resolve merge conflicts]。
提示: 使用 Merged Manifest 视图预览合并清单的效果并找出冲突错误。
合并优先级
Merge priorities
合并工具根据每个清单文件的优先级将所有清单文件按顺序
合并到一个文件中。 例如,如果您有 3 个清单文件,则会先将优先级最低的清单合并到优先级第 2 高的清单中,然后再将合并后的清单合并到优先级最高的清单中(如图 1 所示)。
有 3 种基本的清单文件可以互相合并,它们的合并优先级如下(按优先级由高到低的顺序):
1、清单文件构建变体 Manifest file for your build variant
如果您的变体有多个源集,则其manifest优先级如下:
-
Build variant
manifest (如 src/demoDebug/) -
Build type
manifest (如 src/debug/) -
Product flavor
manifest (如 src/demo/)
如果您使用的是 flavor dimensions,清单优先级将与每个维度在 flavorDimensions 属性中的列示顺序(按优先级由高到低的顺序排列)对应。
2、app模块的主清单文件 Main manifest file for the app module
3、所包括库中的清单文件 Manifest file from an included library
如果您有多个库,则其清单优先级与依赖顺序(库出现在 dependencies 块中的顺序)匹配。
重要说明: build.gradle 文件中的构建配置将替换合并清单文件中的任何对应属性。 例如,build.gradle 文件中的minSdkVersion 将替换
清单元素中的匹配属性。为了避免混淆,您只需省去 元素并在 build.gradle 文件中定义这些属性。For more details, see Configure Your Build.
合并冲突启发式算法
Merge conflict heuristics
合并工具可以在逻辑上将一个清单中的每个 XML 元素与另一个清单中的对应元素相匹配。(有关匹配如何进行的详细信息,请参阅有关 合并策略 的附录)。
如果优先级较低的清单中的元素与优先级较高的清单中的任何元素均不匹配,则该元素将被添加至合并清单。 但是,如果有匹配元素,则合并工具会尝试将其中的所有属性合并到相同元素中。如果工具发现两个清单包含相同属性,但值不相同
,则会出现合并冲突。
下表 1 描述了合并工具尝试将所有属性合并到同一元素时可能出现的结果。
高优先级属性 | 低优先级属性 | 属性的合并结果 |
---|---|---|
没有值 | 没有值 | 没有值(使用默认值) |
没有值 | 值 B | 值 B |
值 A | 没有值 | 值 A |
值 A | 值 A | 值 A |
值 A | 值 B | 冲突错误—必须添加一个 合并规则标记 |
但是,在某些情况下,合并工具会采取其他行为方式以避免合并冲突:
-
<manifest>
元素中的属性不合并--仅使用优先级最高的清单中的属性
。 -
<uses-feature>
和<uses-library>
元素中的android:required
属性使用 OR 合并,因此如果出现冲突(值不一致),系统将应用 "true" 并始终包括某个清单所需的功能或库[the feature or library required by one manifest is always included]。 -
<uses-sdk>
元素始终使用优先级较高的清单中的值
,但以下情况除外:- 如果低优先级清单的
minSdkVersion
值较高,您必须应用overrideLibrary
合并规则[an error occurs unless you apply the overrideLibrary merge rule]。 - 如果低优先级清单的
targetSdkVersion
值较低,合并工具将使用高优先级清单中的值,但也会添加任何必要的系统权限
,以确保所导入的库继续正常工作(适用于较高的 Android 版本具有更多权限限制的情况)。 如需了解有关此行为的详细信息,请参阅有关 隐式系统权限[implicit system permissions] 的部分。
- 如果低优先级清单的
- 绝不会在清单之间匹配
<intent-filter>
元素。每个元素都被视为唯一元素,并添加至合并清单中的常用父元素[the common parent element in the merged manifest]。
对于属性之间的所有其他冲突,您将收到一则错误,并且必须通过在高优先级清单文件中添加特殊属性
来指示合并工具如何解决此错误。
不要依赖于默认属性值 Do not depend on default attribute values
由于所有唯一属性[unique attributes]都合并到相同元素中,如果高优先级清单实际上依赖于属性的默认值而不需要声明,则可能会导致意外结果。
例如,如果高优先级清单不声明android:launchMod
e 属性,则会使用 "standard" 的默认值;但如果低优先级清单声明此属性具有其他值,该值将应用于合并清单(替代默认值)。
因此,您应该按期望明确定义每个属性。(每个属性的默认值都会记录在 Manifest reference 中)。
合并规则的标记
Merge rule markers
合并规则标记是一个 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">
节点标记
Node markers
merge 合并所有
要向整个 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>
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 android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
android:windowSoftInputMode=”stateUnchanged”/>
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>
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>
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”/>
strict 严格模式
当此元素在低优先级清单中的情况与在高优先级清单中的情况不完全匹配时生成构建故障(除非已通过其他合并规则标记解决)。 这将替换默认的合并冲突启发式算法。 例如,如果低优先级清单仅包括额外属性[an extra attribute],则构建将会失败(而默认行为会向合并清单添加额外属性)。
低优先级清单:
<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”/>
这会生成清单合并错误。两个清单元素在严格模式下也完全无法区分。 因此,您必须应用其他合并规则标记来解决这些差异。
属性标记 Attribute markers
要改为仅向清单标记中的 特定属性[specific attributes] 应用合并规则,请使用属性标记。每个属性接受一个或多个属性名称(包括属性命名空间),并以逗号分隔。
remove 移除【常用】
从合并清单中删除指定属性。尽管您似乎可以仅删除这些属性,但如果低优先级清单文件不包括这些属性,而且您希望确保它们不纳入合并清单,则必须使用此选项。
低优先级清单:
<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”/>
replace 替换【最常用】
将低优先级清单中的指定属性替换为此清单中的属性。换言之,始终保持高优先级清单的值。
低优先级清单:
<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”>
strict 严格模式
当这些属性在低优先级清单中的情况与在高优先级清单中的不完全匹配时生成构建故障。 这是所有属性的默认行为,具有 合并冲突启发式算法中介绍的特殊行为的属性除外。
低优先级清单:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”landscape”/>
高优先级清单:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:strict="android:screenOrientation”/>
这会生成清单合并错误。 您必须应用其他合并规则标记来解决冲突。
请谨记:这是默认行为,因此如果您删除
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”>
标记选择器
Marker selector
如果您想仅对某个特定的导入库
应用合并规则标记,请添加具有库包名称的 tools:selector
属性。
例如,对于下面的清单,仅在低优先级清单文件来自 com.example.lib1
库时应用 remove
合并规则。
<permission android:name="permissionOne"
tools:node="remove"
tools:selector="com.example.lib1">
如果低优先级清单来自其他源,系统将会忽略 remove
合并规则。
Tips:如果您将此功能与其中一个属性标记配合使用,它将应用至标记中指定的所有选项。
替换导入库的 uses-sdk
默认情况下,导入 minSdkVersion 值高于主清单文件的库时会出错,而且无法导入该库。 要使合并工具忽略此冲突并导入库,同时保持应用的低 minSdkVersion 值,请将 overrideLibrary 属性添加至 <uses-sdk>
标记。属性值可以是一个或多个库包名称(以逗号分隔),指明可能替换主清单的 minSdkVersion 的库。
例如,如果应用的主清单按如下所示应用 overrideLibrary:
<uses-sdk android:targetSdkVersion="22"
android:minSdkVersion="2"
tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
则下面这个清单(来自com.example.lib1)可以合并,并且不会出现与 <uses-sdk>
标记相关的错误,且合并清单将保留应用清单中的 minSdkVersion="2"
。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib1">
<uses-sdk android:minSdkVersion="4"/>
隐式系统权限
Implicit system permissions
在最近的 Android 版本中,应用曾经可以*访问的某些 Android API 已受 系统权限 限制。为了避免中断预期会访问这些 API 的应用[To avoid breaking apps that expect access to these API],最近的 Android 版本允许应用在无权限的情况下继续访问这些 API,前提是它们已将 targetSdkVersion
设置为低于添加限制的版本的值。此行为有效地向应用授予了_隐式权限_,以允许访问 API。 因此,这可能会对具有不同 targetSdkVersion
值的合并清单产生以下影响。
如果低优先级清单文件提供隐式权限的 `targetSdkVersion` 值较低,而且高优先级清单_没有_相同的隐式权限(由于其 `targetSdkVersion` 等于或高于添加限制的版本),合并工具将向合并清单_显式_添加系统权限。
例如,如果您的应用将 targetSdkVersion
设置为 4 或更高值,并且导入了将 targetSdkVersion
设置为 3 或更低值的库,合并工具会将 WRITE_EXTERNAL_STORAGE 权限添加至合并清单。 表 2 列出了可以添加至合并清单的所有可能权限。
注:如果您将应用的
targetSdkVersion
设置为 23 或更高值,则必须在应用尝试访问受这些权限保护的 API 时为任何危险权限执行运行时权限请求。 如需获得更多指导,请参阅 使用系统权限。
表 2. 合并工具可添加至合并清单的权限列表
低优先级清单声明 | 添加至合并清单的权限 |
---|---|
targetSdkVersion 是 3 或更低值 | WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE |
targetSdkVersion 是 15 或更低值,并且使用 READ_CONTACTS | READ_CALL_LOG |
targetSdkVersion 是 15 或更低值,并且使用 WRITE_CONTACTS | WRITE_CALL_LOG |
检查合并清单并查找冲突
Inspect the merged manifest and find conflicts
即使在构建 APK 之前,也可以预览合并清单
,具体方法是:在 Android Studio 中打开您的 AndroidManifest.xml 文件,然后单击编辑器底部的 Merged Manifest
选项卡。
Merged Manifest 视图在左侧显示合并清单的结果
,在右侧显示每个合并清单文件的相关信息
。
从低优先级文件中合并的元素在左侧以不同颜色突出显示,每种颜色的关键字在右侧的 Manifest Sources 下方指定。
属于构建的一部分但不构成元素或属性的清单文件列在右侧的 Other Manifest Files 下方。
要查看有关元素来源
的信息,请在左侧单击元素,详细信息将显示在右侧的 Merging Log 下方。
如果发生任何冲突,它们将显示在右侧的 Merging Errors 下方,并且包含有关如何使用合并规则标记
解决冲突的建议。错误也会打印在 Event Log 窗口中(请选择 View > Tool Windows > Event Log)。
如果您想要查看合并决策树
的完整日志,则可以在【各个模块】的 build/outputs/logs/manifest-merger-【buildVariant】-report.txt
中查找该日志文件。
附录:合并策略
Appendix: Merge policies
清单合并工具可以在逻辑上将某个清单中的每个 XML 元素与其他清单中的对应元素相匹配。 合并工具会使用匹配关键字[match key]
匹配每个元素,匹配关键字可以是唯一的属性值(如 android:name
或标记本身的天然唯一性(例如,只能有一个 <supports-screen>
元素)。 如果两个清单具有相同的 XML 元素,工具将采用三种合并策略中的一种来合并这两个元素:
-
合并[Merge]
:将所有非冲突属性合并到同一标记中,然后按其各自的合并策略合并子元素。 如果任何属性相互冲突,请使用合并规则标记将它们合并在一起。 -
仅合并子项[Merge children only]
:不整合或合并属性(仅保留优先级最高的清单文件提供的属性),并按照其合并策略合并子项。 -
保留[Keep]
:将元素“按原样”保留,然后将其添加至合并文件中的同一父元素。此策略仅在可接受相同元素的多个声明时使用。
表 3. 清单元素合并策略和合并关键字 Manifest element merge policies and match keys
元素 | 合并策略 | 合并关键字 |
---|---|---|
<action> |
合并 |
android:name 属性 |
<activity> |
合并 |
android:name 属性 |
<application> |
合并 | 每个 <manifest> 仅一个 |
<category> |
合并 |
android:name 属性 |
<data> |
合并 | 每个 <intent-filter> 仅 1 个 |
<grant-uri-permission> |
合并 | 每个 <provider> 仅 1 个 |
<instrumentation> |
合并 |
android:name 属性 |
<intent-filter> |
保留 | 不匹配;允许父元素内的多个声明 |
<manifest> |
合并 | 每个文件仅 1 个 |
<meta-data> |
合并 |
android:name 属性 |
<path-permission> |
合并 | 每个 <provider> 仅 1 个 |
<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> 仅 1 个 |
<uses-configuration> |
合并 | 每个 <manifest> 仅 1 个 |
<uses-feature> |
合并 |
android:name 属性(如果不存在,则使用 android:glEsVersion 属性) |
<uses-library> |
合并 |
android:name 属性 |
<uses-permission> |
合并 |
android:name 属性 |
<uses-sdk> |
合并 | 每个 <manifest> 仅 1 个 |
自定义元素 | 合并 | 无匹配;合并工具不了解这些信息,因此它们始终包括在合并清单中 |
2019-6-29