Android Studio清单文件合并规则

        使用Android Studio和基于Gradle的构建时,一个应用可能会有多个清单文件(AndroidManifest.xml)存在,比如src/main目录下的清单文件,library模块中的清单文件,第三方库文件(aar)中的清单文件,以及多渠道构建目录下的清单文件。在应用构建过程中,会将相关的清单文件组合合并成一个最终的清单文件,用于应用程序的打包和发布。既然说到清单文件的合并,就一定有一个优先级的问题,而清单文件的优先级则是由其所处的位置决定的。而合并的范围包括 清单元素、子元素、属性。

合并冲突规则
       什么情况下会产生冲突? 当待合并的清单文件中具有相同的元素,但是属性值不一样的情况,且按照默认的冲突合并规则无法解决时就会产生合并冲突。 前面提到清单文件的合并是有优先级的,这个优先级如下:
       最高优先级:构建类型( build types )
       高优先级:产品定制(product flavors)(直译为产品风味可能不妥)
       中优先级:应用主文件(main/src)
       低优先级:依赖和库文件(dependencies and libraries)

 合并规则如下表所示:
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.

 对上图规则表的解读:
1. 合并过程中的冲突基于上表中的规则,在XML节点级别和属性级别进行解决。
2. 两个文件中都没有的属性,最终清单文件中自然没有这个属性。
3. 一个文件含有某个属性,另一个没有,最终清单文件中的属性值为包含该属性的清单文件中的值,此时和优先级高低没有关系。
4. 两个文件都有某个属性,且都是默认值,最终都是默认值。
5. 两个文件都有某个属性,一个为默认值,一个非默认值,最终结果为非默认值,此时和文件的优先级高低没有关系。
6. 两个文件都有某个属性,且都为非默认值,此时可能产生冲突错误。

       在合并所有的清单文件之后,此时的清单文件并不是最终的清单文件,因为清单文件中有部分属性是被抽到构建文件build.gradle当中去了,只有将build.gradle文件中的设置覆盖到清单文件当中才能生成最终的清单文件。build.gradle文件中的属性在合入清单文件时,遵照下面的优先级,优先级高的覆盖优先级低的。
高优先级:构建类型(build types)
中优先级:产品定制(product flavors)
低优先级:默认配置(defaultConfig)
      合并的最终文件放置在app/build/intermediates/的子目录当中,intermediates下面的目录按照productFlavors和buildTypes进行细分,如下图所示,可以在里面查看最终的合并结果。在下面的截图中,我定义了两个flavor : demo和full;两个构建类型:release和debug。


构建文件中的部分内容如下:
   
   
  1. defaultConfig {
  2. applicationId "com.ericencore.example.helloas"
  3. minSdkVersion 16
  4. targetSdkVersion 22
  5. versionCode 1
  6. versionName "1.0"
  7. }
  8. productFlavors {
  9. full {
  10. applicationId "com.ericencore.example.helloas.full"
  11. versionName "1.0-full"
  12. }
  13. demo {
  14. applicationId "com.ericencore.example.helloas.demo"
  15. versionName "1.0-demo"
  16. }
  17. }

下图是product flavors为demo, build types为debug的最终的清单文件,下图中红色方框选中的部分为build.gradle文件内容覆盖的部分,最后一个除外。最后一个是在覆盖前应用package的值和Activity类的相对路径生成绝对路径,这样即便在覆盖后package变成了另一个,也没有影响类的实际路径。


下图是product flavors为full, build types为debug的最终的清单文件,其余如上所述。


src/main下的原始清单文件:


合并冲突标记和选择器
        清单标记和选择器应用特定的冲突解决方案来覆盖默认的合并规则。举例来说,可以应用一个冲突标记来合并一个库清单文件和一个高优先级的清单文件,其中库清单文件中的minSdkVersion大于高优先级清单文件;或者是用来合并两个具有相同Activity但是android:theme值不同的清单文件。
       一个冲突标记是Android tools名字空间中一个特殊的属性,它定义了一个特定的冲突解决方案。应用冲突标记可以解决使用默认合并规则不能解决的问题,支持如下几种冲突标记:
      merge
      当应用合并规则时没有产生冲突,对属性进行合并,默认动作是合并。
      replace
      使用高优先级清单文件中的属性值代替低优先级文件中的属性值。
      strict
      设置合并策略等级来合并拥有相同属性的元素,当属性值不同且无法解决冲突时将构建失败。
       merge-only
       仅对于低优先级属性允许合并操作。
       remove-all
       移除合并清单文件中指定的低优先级元素。
       默认情况下,清单合并进程在节点级别应用merge冲突标记,所有声明的属性默认是strict策略。

合并冲突标记的两个典型应用场景
1. 合并依赖库中的清单文件和高优先级清单文件,依赖库的minSdkVersion大于后者。
在这种情况下,会报类似如下的错误:
Error:Execution failed for task ':app:processDemoDebugManifest'.
Manifest merger failed : uses-sdk:minSdkVersion 1 cannot be smaller than version 7 declared in library [com.android.support:appcompat-v7:23.3.0] D:\data\aswk\HelloAS\app\build\intermediates\exploded-aar\com.android.support\appcompat-v7\23.3.0\AndroidManifest.xml
Suggestion: use tools:overrideLibrary="android.support.v7.appcompat" to force usage
       这种情况,如日志提示所示,可以使用overrideLibrary冲突标记来解决这个冲突,具体做法是在高优先级的清单文件中,在uses-sdk中添加tools:overrideLibrary="包名1,包名2,...",如下图所示

       那么如何知道所依赖的库文件中的minSdkVersion呢,其实在构建过程中,Android Studio 和Gradle不仅会将依赖的包文件下载下来,还会将这些aar包展开,aar包的展开内容可以在build/intermediates/exploded-aar目录下进行查看,可以在里面看到详细信息,包括清单文件,如图所示:


2. Application中的属性的合并
        例如icon, label, theme等属性,可以在替换者的清单文件里声明tools:replace="android:xxx", 举例来说,如果希望release(build type)版本里面的属性设置起作用,那么就需要在release中的清单文件里声明tools:replace="android:icon, android:lable, android:theme",不同的属性间使用逗号隔开,android不能省略。

标记选择器
       标记选择器将合并动作限定在一个特定的低优先级清单文件。例如,一个标记选择器可以用于只移除某个库中的一个权限,而不移除其它库中的这个权限。  这个例子使用tools:node标记来移除permissionOne属性,同时tools:selector选择器指定一个特定的库com.example.lib1。库lib1中的permissionOne属性就会被过滤掉。
  
  
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2. package="com.android.example.app"
  3. xmlns:tools="http://schemas.android.com/tools">
  4. ...
  5. <permission
  6. android:name="permissionOne"
  7. tools:node="remove"
  8. tools:selector="com.example.lib1">
  9. ...
  
将构建值注入清单
方法:使用占位符向清单文件中注入值
1. 在AndroidManifext.xml文件中需要注入值的地方使用${name}占位符进行占位,占位符的名字可以随便定义,但是建议使用有意义的命名,做到望文生义。
2. 在模块的构建文件build.gradle中的特定区域,如buildTypes区域或者是productFlavors区域为占位符设置对应的值,格式为manifestPlaceholders = [name : value]。
3. 在重新构建之后,build.gradle中对应占位符的值会写入到合并后的清单文件中,生成最终的清单文件,供打包apk使用。

注意事项:
1. 在build.gradle文件中设置占位符时,一个区域(如buildTypes中的release)只能设置一个占位符,设置多个占位符并不会报错,但是最终结果将是最后一个设置的值。在将manifestPlaceholders理解为一个变量时,这个现象就很好理解了。
2. 占位符不仅支持全值替代,也支持部分替代。
3. 占位符仅能作用于字符串类型的属性值,例如android:lable,android:name, android:value的值,对于其它类型的属性值,在进行占位时就会提醒错误。例如android:supportsRtl="${APP_RTL}",它的值的类型是布尔型,此时会提示错误:Validates resource references inside Android XML files。其它类型如drawable, theme也是类似。

特殊的占位符applicationId
1. applicationId不是清单文件中的一个属性名,而是Android Studio为build.gradle提供的一个默认的占位符,不会占用manifestPlaceholders变量,后续定义的值不会挤掉applicationId。
2. applicationId可以在清单文件中直接使用,如${applicationId},默认情况下它会去代替package的值。这也是为什么如果在build.gradle文件中的productFlavors中定义了不同的applicationId,其打出来的包就是完全不一样的apk包了,它们可以被同时安装在同一个Android手机或者模拟器上。但是它们并不会改变清单文件中注册的组件的类路径,在构成绝对类路径的时候还是使用的原始设置的package的值,所以不能因为它的值可能会被applicationId所替代就不进行设置。

使用场景
APP的多渠道打包是典型的应用场景,通过在productFlavors中定义不同的渠道,然后在渠道中使用占位符来赋予不同的渠道号,来达到通过静态设置一次性打包出所有渠道包的需求。

多维productFlavors
       在很多情况下,productFlavors并不仅仅只是一维的,而是多维的。上面提及的productFlavors是功能程度维度(demo和full),还可能有其它维度,例如手机CPU架构维度(arm, x86, mips),这个时候就可以产生2*3个Flavors。
使用方法:
1. 在build.gradle文件的android{}中,使用flavorDimensions(String...dimensions)来定义flavor的维度,例如flavorDimensions("VERSION", "ABI")。
2. 在productFlavors中声明各自的flavor。例如demo, full, arm, x86, mips。
3. 在各个flavor里面声明本flavor属于哪个dimension, 使用flavorDimension来进行设置。例如flavorDimension:"VERSION"。

注意事项:
1. 在清单文件的合并过程中,是有优先级的概念的,在多个维度的flavor之间也是有优先级的差别,这里的优先级在使用flavorDimensions(String...dimensions)定义维度时决定,越在后面优先级越高(ABI > VERSION),但是官方文档是越在前面优先级越高(VERSION > ABI),这和实际测试的情况不符,所以这个地方可能不太准确,待日后补充。
2. flavor之间的优先级只作用于flavor这一级,也就是说它的优先级仍然小于build types,高于src/main中的优先级。

隐式权限
       当导入一个通过隐式授权来指定运行时环境的库文件时,可能会自动将这些权限添加到合并的清单文件中。例如一个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_CONTACTSpermission READ_CALL_LOG
targetSdkVersion < 16 and using the WRITE_CONTACTSpermission WRITE_CALL_LOG

这里涉及到Android的隐式授权机制,处于兼容性考虑会在某些平台版本上进行某些权限的自动授权。主要就是上表中罗列的几种。
1. 当minSdkVersion和targetSdkVersion小于4时,系统会默认为应用加上 WRITE_EXTERNAL_STORAGE READ_PHONE_STATE 权限,而在4以上,如果需要这两个权限需要显示的声明。
2. 当显示声明 WRITE_EXTERNAL_STORAGE 权限时,系统默认添加 READ_EXTERNAL_STORAGE 权限,但是这一点在api-levle19发生改变。
3. 当targetSdkVersion小于16的时候,只要声明了 READ_CONTACTS 权限和 WRITE_CONTACTS 权限,系统会对应的授予 READ_CALL_LOG 权限和 WRITE_CALL_LOG 权限。
具体可以查看这几个特殊的权限的官方文档,对于特殊情况都有说明。

处理清单合并构建错误
        在构建过程中,清单合并进程会为每个合并事务保存一个记录,记录名为manifest-merger-<productFlavor>-report.txt,位于build/outputs/logs/文件夹中。每个模块的构建变体都会有一个对应的日志文件。 当产生一个构建错误时,合并进程会将描述合并冲突的错误消息保存在这个日志文件中,例如下面两个清单文件的android:screenOrientation进行合并时产生一个构建错误。
高优先级清单声明:
<activity
   android:name="com.foo.bar.ActivityOne"
   android:screenOrientation="portrait"
   android:theme="@theme1"/>
低优先级清单声明:
<activity
   android:name="com.foo.bar.ActivityOne"
   android:screenOrientation="landscape"/>
错误日志:
/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
参考文档:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值