AndroidStuido 采用模块化构建工程的方式,每一个模块配置一个 AndroidManifest.xml ,甚至每一个构建类型、产品特性均可以配置一个 AndroidManifest.xml。最终生成 apk 的时候,按照下图指定的优先级进行合并处理。(图片来源:Google 官方文档)node
上图介绍了三种清单文件的合并流程,从左到右,优先级由低到高。android
构建变体清单,主要包含两种:buildType 和 productFlavor 。git
优先级由高到低依次是:github
buildTypes {
// 测试版本
debug {
}
// 发行版本
release {
}
}
flavorDimensions 'stage', 'api'
productFlavors {
// 开发阶段
dev {
dimension 'stage'
}
// 生产阶段
pro {
dimension 'stage'
}
minApi21 {
dimension 'api'
}
minApi23 {
dimension 'api'
}
minApi26 {
dimension 'api'
}
}
复制代码
分别创建对应的编译变体目录和 AndroidManifest.xml 文件。api
以上面的配置为例,优先级依次是(高到低的顺序):app
dev -> minApi23 -> devMinApi23 -> debug -> devMinApi23Debugmaven
主模块中的清单文件,即 main
目录下的 AndroidManifest.xml 文件。ide
依赖库指依赖本地的 aar
文件或依赖远程 maven 仓库的 aar
文件。它们一般都包含一个 AndroidManifest.xml 文件。模块化
jar 类型的三方库,只有 class 文件,不在此讨论范围。工具
与主模块相对立,库模块也能够包含多个构建类型和产品特性。首先,它们先按照 构建变体清单文件 定义的优先级,合并出自身的一个 AndroidManifest.xml ,再做为库模块的清单文件与主模块的清单文件合并。
早期 Android 版本中,应用能够自由访问的 API ,在新版本中受到系统权限的限制。为兼容这些应用,新版本中会容许这些应用在无权限的状况下访问这些受限的 API 。
如 WRITE_EXTERNAL_STORAGE
最先出如今 Android API 4 中。那么,当你应用的 targetSdkVersion 设置成小于 4 时,也能够在无权限的状况下访问 外部存储 。
在 AndroidManifest 合并中,若是优先级低的 Manifest 中 targetSdkVersion 小于 4,那么在 合并后的 AndroidManifest 中,会自动添加这些隐式权限。
下面两张图是合并先后,app/AndrodManifest.xml 和 library/AndroidManifest.xml 的对比。
发现 合并前 app/AndrodManifest.xml 中未申明权限,可是合并后多了三条权限记录。
优先级较低的清单声明 | 向合并后的的清单添加的权限 |
---|---|
targetSdkVersion <= 3 | WRITE_EXTERNAL_STORAGE READ_EXTERNAL_STORAGE READ_PHONE_STATE |
targetSdkVersion <= 15 且使用 READ_CONTACTS |
READ_CALL_LOG |
targetSdkVersion <= 15 且使用 WRITE_CONTACTS |
WRITE_CALL_LOG |
隐式系统权限主要为兼容 Android 早起版本。在 9102 年及之后的开发中并很少见。所以,了解便可。
借用官方的一张图,介绍了在合并过程当中,默认的一些合并方式。
以 dev 是高优先级,debug 是低优先级为例:
<!-- dev 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:windowSoftInputMode="adjustPan">
<meta-data android:name="dev_index" android:value="dev" />
</activity>
<!-- debug 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:screenOrientation="portrait">
<meta-data android:name="debug_index" android:value="debug" />
</activity>
复制代码
所以最终合并结果以下:
<!-- 合并后devDebug 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:screenOrientation="portrait" android:exported="false" android:windowSoftInputMode="adjustPan">
<meta-data android:name="dev_index" android:value="dev" />
<meta-data android:name="debug_index" android:value="debug" />
</activity>
复制代码
上述提到的都属性合并,可是 meta-data
标签最终却也合并了。标签的合并,你们都熟悉的是 Activity 的合并。每一个模块的 Activity 只须要在该模块的 AndroidManifst.xml 注册,不须要在主模块中再次注册,由于最终会将它们合并在一块儿。
标签合并使用匹配键做为依据,activity
使用 android:name 做为匹配件,meta-data
也是。匹配键的属性值不一样,便可无冲突合并。
下表列出,manifest 所有子标签的匹配键,作解决合并冲突的参考。
标签 | 合并策略 | 匹配键 |
---|---|---|
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 只有一个 |
自定义元素 | 合并 | 不匹配;合并工具并不知晓这些元素, 所以它们始终包含在合并后的清单中 |
Android 中经过标记的方式,在合并过程当中解决冲突。按照前面介绍的属性合并和标签合并,对应采用属性标记和节点标记(标签做为节点)的方式人为解决冲突。
须要使用命名空间 Android tools
。
xmlns:tools="http://schemas.android.com/tools"
复制代码
属性标记有四个。默认标记、移除、替换、选择器。
标记 | 做用 |
---|---|
tools:strict | 默认标记,无实际意义,须要使用 replace、remove 解决冲突 |
tools:replace | 指定替换的属性名字 |
tools:remove | 指定移除的属性名字 |
tools:selector | 选择指定模块进行合并,结合 replace、remove 使用 |
这些例子比较简单 ,借用下官方的示例代码。
<!-- 低优先级 -->
<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">
复制代码
<!-- 低优先级 -->
<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">
复制代码
<!-- 来源于 com.example.lib1 的低优先级 -->
<activity android:name="com.example.ActivityOne" android:windowSoftInputMode="stateUnchanged">
<!-- 高优先级 -->
<activity android:name="com.example.ActivityOne" android:screenOrientation="portrait" tools:remove="android:windowSoftInputMode" tools:selector="com.example.lib1">
<!-- 合并后 -->
<activity android:name="com.example.ActivityOne" android:screenOrientation="portrait">
复制代码
节点标记使用 tools:node 。
属性值 | 做用 |
---|---|
merge | 没有发生冲突时合并标签中全部属性和全部嵌套元素,便是默认方式 |
merge-only-attributes | 未正确验证,估计已失效。 |
remove | 合并后移除此标签 |
removeAll | 同 remove 相似,可是移除所有此标签 |
replace | 替换低优先级中相同的标签 |
strict | 使用时,遇到不匹配的标签都致使合并失败,须要使用上述属性值解决 |
<!-- 合并后devDebug 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:screenOrientation="portrait" android:exported="false" android:windowSoftInputMode="adjustPan">
<meta-data android:name="dev_index" android:value="dev" />
<meta-data android:name="debug_index" android:value="debug" />
</activity>
复制代码
merge-only-attributes 与资料说明的不太同样,未发现正确用途 。
使用 remove 后,移除匹配键相同的标签。
<!-- 低优先级 -->
<activity
android:name="com.flueky.lib.TestActivity"
android:exported="false"
android:screenOrientation="portrait">
<meta-data
android:name="debug_index"
android:value="debug" />
</activity>
<!-- 高优先级 -->
<activity
<activity
android:name="com.flueky.lib.TestActivity"
android:windowSoftInputMode="adjustPan">
<meta-data
android:name="debug_index"
tools:node="remove" />
</activity>
<!-- 合并后 -->
<activity
android:name="com.flueky.lib.TestActivity"
android:exported="false"
android:windowSoftInputMode="adjustPan"
android:screenOrientation="portrait">
</activity>
复制代码
<!-- 低优先级 -->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:screenOrientation="portrait">
<meta-data android:name="debug_index" android:value="debug" />
<meta-data android:name="dev_index" android:value="dev" />
</activity>
<!-- 高优先级 -->
<activity android:name="com.flueky.lib.TestActivity" android:windowSoftInputMode="adjustPan">
<meta-data tools:node="removeAll" />
</activity>
<!-- 合并后 -->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:windowSoftInputMode="adjustPan" android:screenOrientation="portrait">
</activity>
复制代码
removeAll 移除所有相同标签。
<!-- 低优先级 -->
<activity android:name="com.flueky.lib.TestActivity" android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- 高优先级 -->
<activity android:name="com.flueky.lib.TestActivity" android:windowSoftInputMode="adjustPan" tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<!-- 合并后 -->
<activity android:name="com.flueky.lib.TestActivity" android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
复制代码
使用 replace 合并后,直接按照高优先级中的配置。
集成第三方库时,遇到过最多的问题是:库的最小 sdk 版本小于项目的最小 sdk 版本。此时集成第三方库时一定出现问题。
解决方法:
<!-- com.flueky.library 便是第三方库的 packageName -->
<uses-sdk tools:overrideLibrary="com.flueky.library" />
复制代码
最后,偷偷的说下,AndroidStudio 3.5.2 版本支持直接看合并后的 AndroidManifest.xml 文件。
以为有用?那打赏一个呗。去打赏
我的主页已经更新 ,欢迎收藏flueky.github.io/。