Better

业精于勤荒于嬉

AndroidManifest-Merge

Better's Avatar 2017-03-06 Android译文

  1. 1. 合并优先级
  2. 2. 合并冲突启发式heuristics
  3. 3. 合并规则标记
    1. 3.1. 节点标记
      1. 3.1.1. tools:node="merge"
      2. 3.1.2. tools:node="merge-only-attributes"
      3. 3.1.3. tools:node="remove"
      4. 3.1.4. tools:node="removeAll"
      5. 3.1.5. tools:node="replace"
      6. 3.1.6. tools:node="strict"
    2. 3.2. 属性标记
      1. 3.2.1. tools:remove="attr, ..."
      2. 3.2.2. tools:replace="attr, ..."
      3. 3.2.3. tools:strict="attr, ..."
    3. 3.3. 标记选择器
    4. 3.4. 覆盖依赖库的<use-sdk>
    5. 3.5. 隐式系统权限(implicit)
  4. 4. 检查合并清单以及查找冲突
  5. 5. 附录:合并原理

一个apk只会包含一个清单(AndroidManifest)文件,但是在androidStudio下你的工程会有多个清单文件来自于-主modle下,build variants,依赖的库(依赖的Modle,aar)。所以在运行工程的时候,gradle会合并这些清单成一个清单。
清单合并工具合并所有的XML元素(elements)是遵循合并启发和一些你在特定XML的属性(attributes)所指定的偏好。本文将展示清单是怎样合并的以及怎样解决合并带来的冲突。
提示:使用 Merged Manifest view 来查看合并的结果和查找冲突。

合并优先级

合并工具合并所有的清单文件到一个里面是基于清单文件的优先级(priority)。比如,你又三个清单文件,优先级最低的清单合并到高一点的优先级清单中,然后高一点优先级的清单合并到最高优先级清单文件里面。as illustrated in figure :

合并三个清单文件的过程,最低优先级(左)到最高优先级(右)
这里有三个基本类型的清单文件可以被合并到彼此中,合并优先级如下(从高到低):

  1. build variant清单文件。
    如果你又多个build variant,他们的清单优先级如下:

    1. Build variant清单(像src、demoDebug/)
    2. Build type清单(像src/debud)
    3. Product flavor 清单(像src/demo)

    如果你使用的是flavor,他们的优先级对应于在flavorDimensions所列出来的那样(第一个是最高的。

  2. 主Modle工程的清单

  3. 依赖工程清单。
    如果你又多个依赖项目,他们的清单优先级与你依赖顺序有关(它们在Gradle依赖项块中出现的顺序)。

比如说依赖项目的清单文件合并到主Modle的清单,然后主Modle的清单在合并到build variant的清单中。
注意:在 Build with source sets.中列出的是相同的优先级。
重要:build.gradle文件中构建配置信息会覆盖其他的合并清单的相应属性。比如,buidl.gradleminSdkVersion配置会覆盖掉其他清单的 <uses-sdk > 元素。为了避免冲突,你应该忽略其他清单的<uses-sdk>并且只在build.gradle文件中定义这些配置。更多细节参考 Configure Your Build

合并冲突启发式heuristics

合并工具理论上来说可以上将来自一个清单的每个XML元素与另一个清单中的相应元素进行匹配(更多参考 merge policies )。
如果一个低优先级清单的某个元素在高优先级中没有匹配,那么元素将添加到高优先级清单中。然而如果某个元素匹配到了,合并工具将尝试将元素的所有属性合并到高优先级清单中。如果合并工具发现,两个清单中相同的属性有不同的值,就会发生冲突(译:匹配到的元素的属性)。
下表展示了合并工具合并所有属性到一个元素的结果。
表一:

然而,一下这些情形是合并工具尝试避免冲突的行为:

  • <manifest>元素的属性是不会合并的-即只使用高优先级清单的属性。
  • 在<uses-feature> 和 <uses-library>元素所使用的android:required属性使用 OR规则来合并。必然要如果存在一个冲突,true将应用并且将始终包括一个清单所需的功能或库。
  • < use-sdk >元素的属性总是使用高策略清单的属性,出了一下几种情况:
    • 低优先级指定的minSdkVersion版本高一些的时候,就会发生冲突,除非你指定了合并规则。
    • 低优先级指定的targetSdkVersion版本低一些的时候,合并工具会使用高优先级的值,并且还会添加了必要的任何系统权限,以确保导入的库继续正常工作(以防高版本增加的系统的权限限制restrictions)。更多参考 implicit system permissions.
  • < intent-filter >元素时不会去匹配的。在每个清单中都被独立对待,并且是直接的添加合并后的清单中。
    对于其他的属性冲突,你将会看到错误,并且你必须指定合并工具怎样去添加这些冲突的属性到高优先级的清单中(请看下面的一张)。
    注意:不要依赖属性(attribute)的默认值。因为所有的属性值都要合并到元素(element)中去,这也许会出现一些不期望出现的值-高优先级清单没有申明的属性确实依赖它的默认值。比如,一个高优先级的清单没有申明 android:launchMode属性,他使用默认的值standard。但是如果低优先级的清单申明了这个属性用其他的值,然后低清单申明的值将被应用到合并后的清单中(覆盖默认值)。所以你应该明显的指定每一个属性的值。(每一个属性的默认值参见 Manifest reference。)

    合并规则标记

    合并规则标记是一个你可以指定的偏好去解决合并冲突和不想要的元素或属性的XML属性 。合并规则标记可以使用在一个元素的所有属性或某个你指定的属性上。
    所有的规则标记归属在Android tools namespace命名空间 ,所以你必须现在manifest元素里面申明如下:
1
2
3
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp"
xmlns:tools="http://schemas.android.com/tools">

节点标记

为了在一个元素的所有属性上(manifest中所指定的元素的所有属性和他的字节点),使用下面的属性:

tools:node="merge"

合并当前标签的所有属性和所有嵌套的元素,如果没有冲突的时候使用前面的合并冲突启示。这是元素的默认的行为。
低优先级的清单:

1
2
3
4
5
6
7
<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>

高优先级的清单:

1
2
3
4
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:node="merge”>
</activity>

合并结果:

1
2
3
4
5
6
7
8
<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"

只合并当前的标签,不合并嵌套的元素:
低优先的清单:

1
2
3
4
5
6
7
8
<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>

高优先的清单:

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

合并结果:

1
2
3
4
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
android:windowSoftInputMode=”stateUnchanged”>
</activity>

tools:node="remove"

从合并结果清单中移除当前的元素。虽然他看起来像是你直接删除了这元素,有时候这是很必要的当你在结果清单中发现某个元素是被低优先级清单提供并且你不需要,或者已经超出你的控制范围了(比如一个导入的库)。
低优先级清单:

1
2
3
4
5
6
<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>

高优先级:

1
2
3
4
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”cow”
tools:node=”remove”/>
</activity-alias>

结果清单:

1
2
3
4
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”duck”
android:value=”@string/quack”/>
</activity-alias>

tools:node="removeAll"

tools:node="remove"一样,但是是移除的所匹配到的元素的所有同名元素(在相同的父元素下)。
低优先级:

1
2
3
4
5
6
<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>

高优先级:

1
2
3
<activity-alias android:name=”com.example.alias”>
<meta-data tools:node=”removeAll”/>
</activity-alias>

结果:

1
2
<activity-alias android:name=”com.example.alias”>
</activity-alias>

tools:node="replace"

完全的替换低优先级的元素。也就是说(This is),如果优先级较低的清单中有匹配的元素,忽略它,并使用此元素在高优先级清单中显示的元素显示。
低优先级清单:

1
2
3
4
5
6
<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>

高优先级的清单:

1
2
3
4
5
<activity-alias android:name=”com.example.alias”
tools:node=”replace”>
<meta-data android:name=”fox”
android:value=”@string/dingeringeding”/>
</activity-alias>

合并结果:

1
2
3
4
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”fox”
android:value=”@string/dingeringeding”/>
</activity-alias>

tools:node="strict"

当低优先级中的某个元素在高优先级中没有完全的匹配的时候生产构建错误(除非被其他的合并规则标记解决)。这种情形覆盖了合并规则启发式。比如,如果低优先级清单中简单的指定了一个属性,将报错(而属性的默认值想额外的天骄到合并清单中)。
低优先级清单:

1
2
3
4
5
6
7
<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>

高优先清单:

1
2
3
4
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:node="strict”>
</activity>

这将发生清单合并错误。连个清单中的这个元素在strict模式下根本不能区分。所以你必须指定合并标记来解决这些不同的地方。(原则上来,在tools:node="merge"例子中这连个元素将会很好的合并。)

属性标记

为了避免只在清单节点中指定的元素属性中运用合并标记,你可以使用以下的属性。每个属性接受一个或多个属性名称(包括属性命名空间),用逗号(commas)隔开。

tools:remove="attr, ..."

从结果清单中移除指定的属性名称。虽然他看起来像是你直接删除了这些属性,这是必要的当低优先级的清单中包含了一些你在结果清单中不想要的属性。
低优先级清单:

1
2
<activity android:name=”com.example.ActivityOne”
android:windowSoftInputMode=”stateUnchanged”>

高优先级清单:

1
2
3
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:remove=”android:windowSoftInputMode”>

合并结果:

1
2
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”>

tools:replace="attr, ..."

用当前清单的指定属性替换低优先级清单的属性。换句话说,总是使用高优先级清单的值。
低优先级清单:

1
2
3
4
<activity android:name=”com.example.ActivityOne”
android:theme=”@oldtheme”
android:exported=”false”
android:windowSoftInputMode=”stateUnchanged”>

高优先清单:

1
2
3
4
5
<activity android:name=”com.example.ActivityOne”
android:theme=”@newtheme”
android:exported=”true”
android:screenOrientation=”portrait”
tools:replace=”android:theme,android:exported”>

合并结果:

1
2
3
4
5
<activity android:name=”com.example.ActivityOne”
android:theme=”@newtheme”
android:exported=”true”
android:screenOrientation=”portrait”
android:windowSoftInputMode=”stateUnchanged”>

tools:strict="attr, ..."

当低优先级清单的这些属性的值与高优先级的不匹配的时候生产错误。这是所有属性的默认行为。出了模板冲突启发式中描述的那些特定的行为。
低优先级清单:

1
2
3
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”landscape”>
</activity>

高优先级清单:

1
2
3
4
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:strict="android:screenOrientation”>
</activity>

这将发错清单合并错误。你必须指定其他的合并标记去解决这个冲突。(记住:这是默认的行为,所以上面的所有例子都会报错如果你加上tools:strict="screenOrientation”。)
你同样可以在一个元素中添加多个标记,如下:
低优先级清单:

1
2
3
4
5
<activity android:name=”com.example.ActivityOne”
android:theme=”@oldtheme”
android:exported=”false”
android:allowTaskReparenting="true"
android:windowSoftInputMode=”stateUnchanged”>

高优先级清单:

1
2
3
4
5
6
<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”>

合并结果:

1
2
3
4
5
<activity android:name=”com.example.ActivityOne”
android:theme=”@newtheme”
android:exported=”true”
android:allowTaskReparenting="true"
android:screenOrientation=”portrait”>

标记选择器

如果你执行对指定的导入库添加合并规则,添加tools:selector属性和用依赖库的包名。
比如,下面的清单,remove标记仅仅只会在低优先级的依赖库清单神效。

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

如果是来自其他地方的低优先级清单,remove标记将失效。
注意:如果你对属性标记的某一个属性使用了标记,他将应用到所有的属性。

覆盖依赖库的<use-sdk>

默认情况下,当导入的库的minSdkServison值比主工程的清单中的值更高时,是会报错的,所有依赖库是不会被导入的。为了让合并工具忽略错误并且保持主工程的较低的minSdkVersion值,添加overrideLibrary标记。属性值可以是一个或多个库包名称(逗号分隔),指示可以覆盖主清单的minSdkVersion的库。
比如你的主清单是这样使用overideLabrary

1
2
3
4
5
6
<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"/>
...

然后下面的清单在编译<use-sdk>的时候就不会报错,并且合并清单保持主工程的minSdkVersion="2"

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

隐式系统权限(implicit)

一些android版本的APIs曾经是开放给app使用的,但是在最近的后续的 system permissions 版本已经变得受限制了。为了避使访问这些API的应用程序变得不可访问,最近的Android版本允许应用程序在没有权限的情况下继续访问这些API,如果他们将targetSdkVersion设置为低于添加限制的版本的值。这有效的隐式的授予权限去访问这些APIs。因此,这可以影响具有不同的targetSdkVersion值的合并清单,如下所示。
如果低优先级的清单有一个更低的targetSdkVersion,这说明需要提供隐式权限,但是高优先级的清单并不会用于相同的权限(因为他的targetSdkVersion是等于或者高于需要受限制访问权限的版本),也就是说合并工具会自动在结果清单中添加相应的系统权限。
比如如果你的apptargetSdkVersion是4或者更高一点,然而导入库的是3或者更低一点,然后合并工具将添加 WRITE_EXTERNAL_STORAGE权限到合并结果中。下面的列表列出了所有有可能被添加的权限。
注意:如果的targetSdkVersion是23或者更高,你必须动态申请任何危险的权限当你的app将去访问这些被权限保护的APIs时候。更多, Working with System Permissions.
隐式权限

检查合并清单以及查找冲突

及时在你构建APK之前,你也可以通过打开Android Studio的AndroidManifest.xml来查看你的合并清单,然后点击底部的Merged Manifest选项。
合并清单结果在左侧展示了合并的结果,在右侧展示合并清单的具体的信息,如下图所示。从低优先级清单合并而来的元素时高亮的用其他颜色显示在左侧。每种颜色所指定的是右边的清单来源。
清单
清单文件是构建过程的一部分,但是不是右边的其他的清单文件贡献的。
点击左侧的元素右侧将出现更多的信息关于这个元素合并日志。
如果发生了合并冲突,右侧将提示怎样使用合并冲突标记去解决冲突。错误将在Event Log打印出来(select View > Tool Windows > Event Log)。
你可以在你的Modle下面的build/outputs/logs/ 路径的取名为manifest-merger-buildVariant-report.txt的文件,查看完全的合并信息。

附录:合并原理

清单合并工具原则上是将一个清单的元素与另一个清单响应的元素一一匹配。合并工具通过“match key”来匹配每一个元素:是否是唯一的属性名称(比如:android:name),他本身是否唯一(比如,一个清单中<supports-screen>只能有个)。如果两个清单有相同的元素,合并工具将使用下面的三个原则来合并:

  1. 合并
    把没有发生冲突的属性结合到一起并且根据子元素个字的合并原则合并子元素。如果有任何的元素发生冲突了,根据合并标记来合并。
  2. 只合并子类
    不结合或者合并这些属性(值保留高优先级清单的属性)并且根据子元素各自的合并原则合并子元素。
  3. 保持
    将元素“保留原有”并且添加到公共的合并文件父类中。这仅仅会发生在当可以接受几个元素的申明的时候。
    表一,列出了每个元素的类型,所使用的合并原则,以及合并两个清单的某个元素的key
    表三,清单元素合并原则以及匹配的key。


原文

thinks:
AndroidManifest合并原理
Manifest合并

This article was last updated on days ago, and the information described in the article may have changed.