Manifest

AndroidManifest清单文件

合并优先级(低的合并到高的)【库清单<主清单<flavor的清单】

安卓/清单文件合并优先级

  • 有三种基本的清单文件可以互相合并,它们的合并优先级如下(按优先级由高到低的顺序):

    1. 构建变体的清单文件

      如果变体有多个源代码集,则其清单优先级如下:

      1. 构建变体清单(如 src/demoDebug/)
      2. 构建类型清单(如 src/debug/)
      3. 产品变种清单(如 src/demo/)
        如果使用的是变种维度,则清单优先级与每个维度在 flavorDimensions 属性中的列示顺序(按优先级由高到低的顺序)对应。
    2. 应用模块的注清单文件

    3. 所包含的库中的清单文件
      如果有多个库,则其清单优先级与依赖顺序(库出现在 Gradle dependencies 代码块中的顺序)一致。

  • build.gradle中构建配置将替换合并后的清单文件中所有对应的属性。如:build.gradle中的 minSdkVersion 将替换 清单元素中的匹配属性。为避免混淆,应去掉 只在 build.gradle中定义这些属性。

合并冲突启发法

高优先级属性 低优先级属性 属性的合并结果
没有值 没有值 没有值(使用默认值)
没有值 值 B 值 B
值 A 没有值 值 A
值 A 值 A 值 A
值 A 值 B 冲突错误 - 您必须添加合并规则标记
  • 在某些情况下,合并工具会采取其他行为方式来避免合并冲突:

    • 元素中的属性绝不会合并在一起,只会使用优先级最高的清单中的属性。
  • 元素中 android:required 属性使用 OR 合并,这样如果发生冲突,系统将应用 “true” 并始终包含一个清单所需的功能或库。

    • 元素中的属性始终使用优先级较高的清单中的值,但以下情况除外:

      • 如果优先级低的清单中 minSdkVersion 值较高,除非应用 overrideLibrary 合并规则,否则会发生错误。

      • 如果优先级低的清单中 minSdkVersion 值较低,则将会使用优先级高的清单中的值。也会添加所有必要的系统权限。

  • 绝不会在清单之间匹配 元素。每个该元素都被视为唯一的元素,并添加到合并后的清单中的共同父元素中。

合并规则标记

所有标记都属于Android tools命名空间,需要添加对应的 xmlns:tools 命名空间 优先在优先级高的清单文件中查找这些标记。

节点标记

需要对整个XML元素(给定清单元素中的所有属性及其所有子标记)应用合并规则

  • 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"

    完全替换优先级较低的元素。也就是说,如果优先级较低的清单中有匹配的元素,会将其忽略并完全按照此元素在此清单中显示的样子使用它。

    低优先级清单:

    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>

    这会导致清单合并错误。在严格模式下,这两个清单元素不能有任何不同。因此,您必须应用其他合并规则标记解决这些差异。(通常,这两个元素会正常合并在一起,如上面的 tools:node="merge" 示例中所示。)

属性标记

只需对清单标记中的特定属性应用合并规则,用以下属性。每个属性可接受一个或多个属性名称(包括属性命名空间),属性名称之间用英文逗号分隔。

  • 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属性。

如:对于下面的清单,只有在优先级较低的清单文件来自 com.example.lib1 库时,才会应用 remove 合并规则。

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

如果优先级较低的清单文件来自其他任何来源,都会忽略remove合并规则。

注意:如果将此属性与其中一个属性标记配合使用,则它会应用到该标记中指定的所有属性。

替换导入的库的

默认情况下,导入 minSdkVersion 值高于主清单文件的库时会出错,而且无法导入该库。如需使合并工具忽略此冲突并导入库,同时保留应用的较低 minSdkVersion 值,请将 overrideLibrary 属性添加到 <uses-sdk> 标记。属性值可以是一个或多个库软件包名称(用英文逗号分隔),指明可以替换主清单的 minSdkVersion 的库。

例如,如果应用的主清单按如下方式应用 overrideLibrary

1
2
3
4
5
<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> 标记相关的错误,合并后的清单将保留应用清单中的 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" />
...

隐式系统权限

一些曾经可由应用自由访问的 Android API 在最新的 Android 版本中受到了系统权限的限制。为了避免中断预期会访问这些 API 的应用,最新的 Android 版本允许应用在无权限的情况下继续访问这些 API,条件是它们将 targetSdkVersion 设为低于添加限制的版本的值。此行为会有效地向应用授予隐式权限,以允许它们访问这些 API。因此,这可能会对具有不同 targetSdkVersion 值的合并后的清单产生以下影响。

如果优先级较低的清单文件具有较低的 targetSdkVersion 值,因而为其提供了一项隐式权限,但优先级较高的清单不具备相同的隐式权限(因为它的 targetSdkVersion 等于或高于添加限制的版本),则合并工具会向合并后的清单明确添加相应的系统权限。

例如,如果您的应用将 targetSdkVersion 设为 4 或更高的值,但导入的库将 targetSdkVersion 设为 3 或更低的值,则合并工具会向合并后的清单添加 WRITE_EXTERNAL_STORAGE 权限。表 2 列出了可以向合并后的清单添加的所有可能的权限。

注意:如果您已将应用的 targetSdkVersion 设为 23 或更高的值,那么当应用试图访问受任何危险权限保护的 API 时,您必须对这些权限执行运行时权限请求。如需获得更多指导,请参阅使用系统权限

表 2. 合并工具可以向合并后的清单添加的权限的列表

优先级较低的清单声明 向合并后的清单添加的权限
targetSdkVersion 为 3 或更低的值 WRITE_EXTERNAL_STORAGEREAD_PHONE_STATE
targetSdkVersion 为 15 或更低的值,并且使用 READ_CONTACTS READ_CALL_LOG
targetSdkVersion 为 15 或更低的值,并且使用 WRITE_CONTACTS WRITE_CALL_LOG

附录:合并政策

清单合并工具可以在逻辑上将一个清单文件中的每个 XML 元素与另一个文件中的对应元素相匹配。合并工具会使用“匹配键”匹配每个元素,匹配键可以是唯一的属性值(如 android:name),也可以是标记本身的自然唯一性(例如,只能有一个 <supports-screen> 元素)。如果两个清单具有相同的 XML 元素,则该工具会采用三种合并政策中的一种,将这两个元素合并在一起:

  • 合并

    将所有没有冲突的属性组合到同一标记中,并按各自的合并政策合并子元素。如果任何属性相互冲突,则使用合并规则标记将它们合并在一起。

  • 仅合并子元素

    不组合或合并属性(仅保留优先级最高的清单文件提供的属性),并按各自的合并政策合并子元素。

  • 保留

    将元素“按原样”保留,并将其添加到合并后的文件中的共同父元素中。只有在可以接受同一元素有多个声明时,才会采用此政策。

表 3 列出了每种元素类型、所采用的合并政策类型,以及用于确定两个清单之间元素匹配的键。

表 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> 只有一个
自定义元素 合并 不匹配;合并工具并不知晓这些元素,因此它们始终包含在合并后的清单中