管理Manifest清单文件

728 阅读9分钟

Android 项目里面,可以包含多个moudle,每个moudle都存在一个AndroidManifest文件,但是你最终打包的APK里面,只存在一个AndroidManifest,gradle在打包的时候,会合并多个Manifest 文件到一个文件里面。

清单合并工具通过遵循一些合并启发式方法并遵守您使用特殊 XML 属性定义的合并首选项来合并每个文件中的所有 XML 元素。本页介绍清单合并的工作原理以及如何应用标记来解决合并冲突。

合并优先级

合并工具通过根据每个清单文件的优先级顺序合并所有清单文件,将它们合并到一个文件中。例如,如果您有三个清单文件,则最低优先级的清单会合并到下一个最高优先级的清单中,然后再合并到最高优先级的清单中,如图 1 所示。

Android常见的需要合并的清单文件包括三种类型(优先级由低到高):

  1. 库项目中的清单文件

  2. 应用模块的主清单文件

  3. 构建变体的清单文件

冲突合并原则

高优先级属性

低优先级属性

合并后属性

没有值

没有值

没有值

B

B

A

没有值

A

A

A

B

冲突,必须添加

冲突合并标记

某些情况下,合并工具会根据一些规则,避免冲突:

  1. <manifest> 元素中的属性绝不会合并在一起,只会使用优先级最高的清单中的属性

  2. <uses-feature><uses-library> 元素中的 android:required 属性使用 OR 合并,这样一来,如果发生冲突,系统将应用 "true" 并始终包含一个清单所需的功能或库

  3. <uses-sdk> 元素中的属性始终使用优先级较高的清单中的值,但以下情况除外:

      • 如果优先级较低的清单中的 minSdkVersion 值较高,那么除非您应用 overrideLibrary 合并规则,否则将会发生错误。
      • 如果优先级较低的清单中的 targetSdkVersion 值较低,合并工具将使用优先级较高的清单中的值,但也会添加所有必要的系统权限,以确保所导入的库继续正常运作(以防遇到较高的 Android 版本具有更多权限限制的情况)。如需详细了解此行为,请参阅有关隐式系统权限的部分。
  4. 绝不会在清单之间匹配 <intent-filter> 元素。每个该元素都被视为唯一的元素,并添加到合并后的清单中的共同父元素中

合并标记

合并标记又分为两类:节点标记属性标记

节点标记有:

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

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

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

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

检查合并后的清单文件并查找冲突

甚至在您构建应用之前,就可以预览合并后的清单是什么样子,方法是在 Android Studio 中打开您的 AndroidManifest.xml 文件,然后点击编辑器底部的 Merged Manifest 标签页。

在“Merged Manifest”视图中,左侧会显示合并后的清单结果,右侧会显示所合并的每个清单文件的相关信息,如图 2 所示。从优先级较低的清单文件中合并的元素在左侧以不同的颜色突出显示。每种颜色的键在右侧的 Manifest Sources 下方指定。

属于 build 的一部分但未贡献元素或属性的清单文件列在右侧的 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 元素,则该工具会采用三种合并政策中的一种,将这两个元素合并在一起:

合并

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

仅合并子元素

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

保留

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

元素

合并政策

匹配键

<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> 只有一个

自定义元素

合并

不匹配;合并工具并不知晓这些元素,因此它们始终包含在合并后的清单中