通过Android Studio和基于Gradle的构建,每个app都可以在多个位置包含manifest文件,比如productFlavor的src/main/文件夹,libraries,Android Library工程的Android ARchive (AAR)和dependencies。在构建过程中,manifest合并过程会把你的app所包含的多个AndroidManifest.xml文件中的设定结合起来,为app的打包和发布产生一个单独的APK manifest文件。Manifest设定会基于manifest的优先级来合并,优先级则由manifest的文件位置决定。构建你的app会把特定build variant的这些manifests的manifest元素,属性和子元素都合并起来。
合并冲突规则
当要合并的manifests包含有相同的manifest元素且具有不同的属性值,而又不能基于默认的合并冲突规则解析时会发生合并冲突。Conflict markers and selectors也可以定义定制的合并规则,比如允许一个导入的库具有比其它更高优先级的manifests中定义的版本更高的minSdkVersion。
manifest合并优先级决定了在合并冲突中保留那个manifest的设定,具有更高优先级的manifest中的设定将覆写更低优先级的manifest中的设定。
下面的列表详细说明了在合并过程中,哪个manifest设定具有更高的优先级:
- 最高优先级:buildType manifest设定
- 高优先级:productFlavor manifest设定
- 中等优先级:app工程的src/main/目录下的Manifests
- 低优先级:依赖及库的manifest设定
Manifest合并冲突基于下面的规则在XML节点和属性的层次进行解决:
High Priority Element | Low Priority Element | Manifest Merge Result |
---|---|---|
no attribute | no attribute | no attribute |
attribute set to default | default attribute | |
attribute set to non-default | low priority attribute | |
attribute set to default | no attribute | default attribute |
attribute set to non-default | high priority attribute | |
attribute set to default | attribute set to default | default attribute |
attribute set to default | attribute set to non-default | low priority attribute |
attribute set to non-default | attribute set to default | high priority attribute |
attribute set to non-default | attribute set to non-default | Merge if settings match, otherwise causes conflict error. |
manifest合并规则的例外:
uses-feature android:required; 和uses-library android:required元素默认值为true,构建系统用OR来合并这些元素,以便于任何需要的feature或library都可以被包含在产生的APK中。
如果没有声明<uses-sdk>元素,minSdkVersion和targetSdkVersion的默认值为1。当出现合并冲突时,将使用更高优先级的manifest中的那个。
导入一个minSdkVersion值比app的src/main/ manifest的对应值更高的library将产生一个error,除非使用了overrideLibrary冲突marker。
注意:如果没有显式地声明,targetSdkVersion默认值为minSdkVersion。如果在所有的manifest或build.gradle文件中都没有minSdkVersion元素,则其值为1。
当导入了一个其targetSdkVersion值比app的src/main/ manifest的值低的library时,manifest合并过程显式地(explicitly)获取权限并确保导入的library能正常工作。
manifest元素只合并子manifest元素。
intent-filter元素总是原封不动地被添加到合并之后的manifest里共同的父节点下。
重要:合并了manifests之后,构建过程将用build.gradle文件中也出现的设定覆盖最终的manifest设定。更多细节请参考Configuring Gradle Builds。
合并冲突标签(Markers)和选择器(Selectors)
Manifest标签和选择符通过特定的冲突决议规则覆盖默认的规则。比如,使用一个冲突标签来将一个具有更高minSdkVersion值的library manifest与更高优先级的manifest合并,或者合并具有相同activity但它们具有不同android:theme值的manifests。
合并冲突标签(Markers)
一个合并冲突标签(Markers)是Android tools命名空间的一个特殊属性,它定义了一个特别的合并冲突决议规则。创建一个冲突标签来为默认合并规则无法处理的合并冲突避开一个合并冲突error。
merge
- 根据合并规则进行合并时没有冲突就合并属性。这是默认的合并行为。
replace
- 用高优先级manifest中的属性值替换低优先级manifest的属性值。
strict
- 设置合并策略的层次,以便于在 合并后的元素具有相同的属性但值不同时 产生一个构建失败,除非通过冲突规则解决了冲突。
merge-only
- 只合并低优先级的属性。
remove
- 从合并后的manifest中移除特定的低优先级元素。
remove-All
- 从合并后的manifest中移除所有相同节点类型的低优先级元素。
默认情况下,manifest合并过程为节点应用merge合并标签。所有声明的manifest属性默认为strict合并策略。
要设置一个合并冲突标签,首先要在AndroidManifest.xml文件中声明命名空间。然后,在manifest中键入合并冲突标签来描述一个定制的合并冲突行为。这个例子插入了replace标签设置一个replace行为来解决android:icon和android:label manifest元素间的冲突。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.tests.flavorlib.app"
xmlns:tools="http://schemas.android.com/tools">
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
tools:replace="icon, label">
...
标签属性
冲突标签使用tools:node和tools:attr属性来把合并行为限制在特定的XML节点或属性之上。
tools:attr属性只使用restrict,remove,和replace合并行为。一个特定的元素可以应用多个tools:attr标签值,比如使用tools:replace="icon, label, theme"替换低优先级的icon,label,和theme属性。
导入的库的合并冲突标签
overrideLibrary冲突标签应用于<uses-sdk>manifest声明,它被用于导入一个库即使那个库的<uses-sdk>值,比如minSdkVersion,被设置为与其它高优先级manifests中的的值不同。
没有这个标签的话,来自于<uses-sdk>值的库manifest合并冲突将导致合并过程失败。
这个例子使用了overrideLibrary冲突标签来解决src/main/ manifest中的minSdkVersion值与一个导入的库的manifest之间的合并冲突。
src/main/ manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.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"/>
...
库的manifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib1">
...
<uses-sdk android:minSdkVersion="4" />
...
</manifest>
注意:默认的合并过程不允许导入具有比app的src/main/ manifest中所定义的minSdkVersion值更高的库,除非使用了overrideLibrary冲突标签。
标签选择器(Marker Selectors)
标签选择器将一个合并行为限制在一个特定的低优先级manifest。比如,一个标签选择器可以被用于只从一个库中移除一个权限,同时允许其它的库请求相同的权限。
这个例子使用了tools:node标签来移除permisionOne属性,同时tools:selector选择器指定了特定的库com.example.lib1。permisionOne权限只被从lib1库的manifest中过滤掉。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.example.app"
xmlns:tools="http://schemas.android.com/tools">
...
<permission
android:name="permissionOne"
tools:node="remove"
tools:selector="com.example.lib1">
...
向一个Manifest中注入Build Values
Manifest合并也可以被配置为使用manifest占位符,来向manifest属性里插入build.gradle文件中的属性值。
Manifest占位符使用${name}语法来插入属性值,其中name是被插入的build.gradle属性。build.gradle文件使用manifestPlaceholders属性来定义占位符的值。
注意:apps中的未解决占位符名称将导致构建失败。库中的未解决占位符名称产生warnings,并需要在库被导入到一个app中时被解决。
这个例子展示了manifest占位符${applicationId}被用于把build.gradle的applicationId属性值注入到android:name属性值。
注意:Android Studio为 构建文件中没有显示的build.gradle applicationId值提供了一个默认的${applicationId}占位符。当把库模块构建为一个AAR(Android ARchive)包时,不要在manifest合并设定中提供一个自动的@{applicationId}占位符。而要使用一个不同的占位符,比如@{libApplicationId},如果你想要在打包的库中包含应用程序Ids的话为它提供一个值。
Manifest项(entry):
<activity
android:name=".Main">
<intent-filter>
<action android:name="${applicationId}.foo">
</action>
</intent-filter>
</activity>
Gradle构建文件:
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
productFlavors {
flavor1 {
applicationId = "com.mycompany.myapplication.productFlavor1"
}
}
合并后的manifest值:
<action android:name="com.mycompany.myapplication.productFlavor1.foo">
manifest占位符语法和构建文件的manifestPlaceholders属性可被用于注入其它的manifest值。对于applicationId之外的其它属性,manifestPlaceholders属性可以被显式地声明在build.gradle文件中。这个例子展示了manifest占位符注入activityLabel值。
Gradle构建文件:
android {
defaultConfig {
manifestPlaceholders = [ activityLabel:"defaultName"]
}
productFlavors {
free {
}
pro {
manifestPlaceholders = [ activityLabel:"proName" ]
}
}
manifest文件中的占位符:
<activity android:name=".MainActivity" android:label="${activityLabel}" >
注意:占位符值支持部分值注入,比如android:authority="com.acme.${localApplicationId}.foo"。
跨Product Flavor组的Manifest合并
当使用了GroupableProductFlavor属性时,product flavor组中的任何manifests的manifest合并优先级将遵循构建文件(build file)中product flavor组列出的顺序。manifest合并过程将基于配置的build variant为product flavor创建一个单独的合并manifest。
比如,如果一个variant分别引用了product flavor组ABI,Density,API和Prod的product flavors x86,mdpi,21和paid,它们在build.gradle文件中也以这样的顺序列出,则manifest合并过程以这个优先级顺序合并manifests,即遵循product flavors在构建文件中列出的顺序。
为了描绘这个例子,下面的表显示了每个product flavor组的product flavors是如何列出的。这个product flavors和组的结合定义了build variant。
Product Flavor Group | Product Flavor |
---|---|
ABI | x86 |
density | mdpi |
API | 22 |
prod | paid |
- prod-paid AndroidManifest.xml(最低优先级)被合并进API-22 AndroidManifest.xml中。
- API-22 AndroidManifest.xml被合并进density-mpi AndroidManifest.xml中。
- density-mpi AndroidManifest.xml被合并进ABI-x86 AndroidManifest.xml(最高优先级)中。
隐式权限(Implicit Permissions)
导入一个其目标Android runtime隐蔽地获取权限的库可能自动地向最终的合并manifest中添加权限。比如,如果一个targetSdkVersion为16的应用程序导入了一个targetSdkVersion为2的库,Android Studio将添加WRITE_EXTERNAL_STORAGE权限以确保权限可以在SDK版本间兼容。
注意:更近一些的Android版本把隐式的权限替换为了权限声明。
Importing this library version | Declares this permission in the manifest |
---|---|
targetSdkVersion < 2 | WRITE_EXTERNAL_STORAGE |
targetSdkVersion < 4 | WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE |
Declared WRITE_EXTERNAL_STORAGE | READ_EXTERNAL_STORAGE |
targetSdkVersion < 16 and using the READ_CONTACTS permission | READ_CALL_LOG |
targetSdkVersion < 16 and using the WRITE_CONTACTS permission | WRITE_CALL_LOG |
处理Manifes合并Build Errors
在构建过程中,manifest合并过程在模块的build/outputs/logs文件夹下的manifest-merger-<productFlavor>-report.txt文件中存储了每一个合并事务的记录。模块的每次build variants产生一个不同的log文件。
当发生了一个manifest合并build erros时,合并过程在log文件中记录了错误消息,该消息描述了合并冲突。比如,下面的manifests之间的android:screenOrientation合并冲突导致了一个build error。
高优先级manifest的声明为:
<activity
android:name="com.foo.bar.ActivityOne"
android:screenOrientation="portrait"
android:theme="@theme1"/>
低优先级manifest的声明为:
<activity
android:name="com.foo.bar.ActivityOne"
android:screenOrientation="landscape"/>
错误log为:
/project/app/src/main/AndroidManifest.xml:3:9 Error:
Attribute activity@screenOrientation value=(portrait) from AndroidManifest.xml:3:9
is also present at flavorlib:lib1:unspecified:3:18 value=(landscape)
Suggestion: add 'tools:replace="icon"' to element at AndroidManifest.xml:1:5 to override
Done。
原文。