Android资源文件合并
什么文件是资源文件?
/res目录下的所有文件,/assets目录Manifest文件
需要合并的资源文件来源是哪?
通常apk中的资源来源有3个,具体可以参考官网:
- 主资源(main source set):比如
src/main/res - 编译变量(Build variant source set): 比如
src/demoDebug/res - 库文件依赖(libraries): 也就是我们引进的
aar。
为什么要合并?
因为文件冲突了所以要合并。
那么随之而来的问题是系统如何唯一表示一个资源文件?相同resource type(anim/drawable/string等)和相同resource qualifier(比如hdpi, value中的语言等)下相同文件名的资源,系统会认为他是相同的,会导致冲突,需要合并。
如何合并?
合并/assets目录
asset冲突只会是文件冲突,规则也简单,优先使用本地文件。
合并/res目录
单一module中的资源文件发生冲突时如何合并?
单一module下可能就会有相同的资源存在,比如有多个主资源集。那么当出现这种冲突的情况的时候,系统会怎么处理呢?规则是低优先级的资源会被覆盖掉。
覆盖的优先级从高到低如下:
build variant > build type > product flavor > main source set > library dependences
举个栗子,如果我们主资源集下有两个资源: res/layout/a.xml, res/layout/b.xml, build type文件夹下面有res/layout/a.xml。那么最后打包生成的apk中的res/layout/a.xml来自于build type, res/layout/b.xml来自于main source set。
不同module中的资源文件发生冲突时如何合并?
当第三方依赖中的res文件与本地文件有冲突时,会优先选用本地文件。但res/values略有不同,此目录下的strings.xml、color.xml、styles.xml等文件会被整合到一个叫values.xml的文件中去,后与各第三方依赖中的values.xml进行内容上的合并,不会像res其它子目录文件一样直接舍弃第三方冲突文件。
Manifest合并
低优先级的manifest被合并到高优先级manifest中。
不同来源的manifest优先级由高到低:
- 构建变体(build variant)中的manifest
- 构建变体(build variant)manifest: 例如
src/demoDebug - 构建类型(build type) manifest: 例如
src/debug - 产品风味(product flavor) manifest: 例如
src/demo
- 构建变体(build variant)manifest: 例如
- 本地 app module 中的manifest
- 依赖的库中的manifest
合并 manifest 中的属性值的默认规则如下:
| High priority attribute | Low priority attribute | Attribute’s merged result |
|---|---|---|
| No value | No value | No value (use default value) |
| No value | Value B | Value B |
| Value A | No value | Value A |
| Value A | Value A | Value A |
| Value A | Value B | Conflict error—you must add a merge rule marker |
但是也有如下例外:
<manifest>中的属性不会进行合并,直接使用高优先级manifest中的属性<uses-feature>和<uses-library>中的android:required属性使用or规则进行合并<uses-sdk>中的属性总是使用高优先级manifest中的,但以下情况例外:- 当低优先级中定义的
minSdkVersion较高时会报错。但是可以使用overrideLibrary合并规则解决此错误。 - 当低优先级中定义的
targetSdkVersion较低时,合并工具会使用高优先级manifest中的较高值。但是,它还添加了确保导入的库继续正常工作所需的任何系统权限(以防更高版本的Android增加了权限限制的情况)。可以点击这里查看合并工具可能会添加的权限
- 当低优先级中定义的
<intent-filter>元素在合并中不会被改变,只会被添加到其父节点中去
合并规则标记
合并规则标记是一个 XML 属性,可用于指定您对如何解决合并冲突或移除不需要的元素和属性的偏好。您可以对整个元素应用标记,也可以只对元素中的特定属性应用标记。
合并两个清单文件时,合并工具会在优先级较高的清单文件中查找这些标记。
所有标记都属于 Android tools 命名空间,因此您必须先在 <manifest> 元素中声明此命名空间,如下所示:
1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" |
节点标记
如需对整个 XML 元素(给定清单元素中的所有属性及其所有子标记)应用合并规则,请使用以下属性:tools:node="merge"
在没有冲突的情况下,使用合并冲突启发法合并此标记中的所有属性以及所有嵌套元素。这是元素的默认行为。
低优先级清单:
1 | <activity android:name="com.example.ActivityOne" |
高优先级清单:
1 | <activity android:name="com.example.ActivityOne" |
合并后的清单结果:
tools:node="merge-only-attributes"
仅合并此标记中的属性,不合并嵌套元素。
低优先级清单:
高优先级清单:
<activity android:name=”com.example.ActivityOne” android:screenOrientation=”portrait” **tools:node=”merge-only-attributes”**>
合并后的清单结果:
tools:node="remove"
从合并后的清单中移除此元素。虽然您似乎只需要删除此元素即可,但如果您发现合并后的清单中有不需要的元素,而且该元素是由不受您控制的优先级较低的清单文件(如导入的库)提供的,则必须使用此属性。
低优先级清单:
高优先级清单:
合并后的清单结果:
tools:node="removeAll"
与 tools:node="remove" 类似,但它会移除与此元素类型匹配的所有元素(同一父元素内)。
低优先级清单:
高优先级清单:
合并后的清单结果:
tools:node="replace"
完全替换优先级较低的元素。也就是说,如果优先级较低的清单中有匹配的元素,会将其忽略并完全按照此元素在此清单中显示的样子使用它。
低优先级清单:
高优先级清单:
<activity-alias android:name=”com.example.alias” **tools:node=”replace”**>
合并后的清单结果:
tools:node="strict"
每当此元素在优先级较低的清单中与在优先级较高的清单中不完全匹配时,都会导致构建失败(除非已通过其他合并规则标记解决)。这会替换合并冲突启发式算法。例如,如果优先级较低的清单只是包含一个额外的属性,构建就会失败(尽管默认行为是将该额外属性添加到合并后的清单中)。
低优先级清单:
高优先级清单:
<activity android:name=”com.example.ActivityOne” android:screenOrientation=”portrait” **tools:node=”strict”**>
这会导致清单合并错误。在严格模式下,这两个清单元素不能有任何不同。因此,您必须应用其他合并规则标记解决这些差异。(通常,这两个元素会正常合并在一起,如上面的 tools:node="merge" 示例中所示。)
属性标记
如需只对清单标记中的特定属性应用合并规则,请使用以下属性。每个属性可接受一个或多个属性名称(包括属性命名空间),属性名称之间用英文逗号分隔。
tools:remove="attr, ..."
从合并后的清单中移除指定属性。虽然您似乎只需要删除这些属性即可,但如果优先级较低的清单文件包含这些属性,而您想确保它们不会被纳入合并后的清单,则必须使用此属性。
低优先级清单:
高优先级清单:
<activity android:name=”com.example.ActivityOne” android:screenOrientation=”portrait” **tools:remove=”android:windowSoftInputMode”**>
合并后的清单结果:
tools:replace="attr, ..."
将优先级较低的清单中的指定属性替换为此清单中的属性。换句话说,始终保留优先级较高的清单中的值。
低优先级清单:
高优先级清单:
<activity android:name=”com.example.ActivityOne” android:theme=”@newtheme” android:exported=”true” android:screenOrientation=”portrait” **tools:replace=”android:theme,android:exported”**>
合并后的清单结果:
tools:strict="attr, ..."
每当这些属性在优先级较低的清单中与在优先级较高的清单中不完全匹配时,都会导致构建失败。这是所有属性的默认行为,但具有特殊行为的属性除外,如合并冲突启发法中所述。
低优先级清单:
高优先级清单:
<activity android:name=”com.example.ActivityOne” android:screenOrientation=”portrait” **tools:strict=”android:screenOrientation”**>
这会导致清单合并错误。您必须应用其他合并规则标记解决冲突。(切记:这是默认行为,因此如果您移除 tools:strict="screenOrientation",上面的示例将具有相同的结果。)
您也可以对一个元素应用多个标记,如下所示。
低优先级清单:
高优先级清单:
<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”**>
合并后的清单结果:
合并策略
清单合并工具可以在逻辑上将一个清单文件中的每个 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> 只有一个 |
| 自定义元素 | 合并 | 不匹配;合并工具并不知晓这些元素,因此它们始终包含在合并后的清单中 |
参考: