编译时区分不同的manifest
很多Android项目都会区分debug和release的manifest文件,以便调试,一些组件化的项目甚至有多个manifest文件来调试不同的组件。举个简单的例子,在app的build.gradle文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| android { defaultConfig { applicationId "com.xxx.xxx" } sourceSets { main { if(是否为debug打包) { manifest.srcFile "${projectDir}/src/main/debug/AndroidManifest.xml" } else { manifest.srcFile "${projectDir}/src/main/release/AndroidManifest.xml" } } } }
|
这个地方的if条件,一般可以是一个变量,或者一个方法:
1 2 3 4 5 6 7 8 9
| def isDebug = true
def isDebug() { return true }
android { ... }
|
然后此处的 ${projectDir} 变量对应的是当前模块所在的路径,这里就是app的路径,不是整个工程的路径。这样一来,我们就能编译不同的manifest文件了。
自动判断编译类型
但是,这样每次编译都需要手动改那个debug变量,挺麻烦的,尤其是一些技术团队可能是用的公司服务器在线编译,每次都要提交代码到仓库。其实我们可以用gradle插件的特性,这样来判断:
1 2 3 4 5 6
| if(gradle.startParameter.taskNames.contains(":app:assembleDebug")) { manifest.srcFile "${projectDir}/src/main/debug/AndroidManifest.xml" } else { manifest.srcFile "${projectDir}/src/main/release/AndroidManifest.xml" }
|
不用改代码,打release包和debug包就自动区分了,当然你也可以增加新的条件,去对应不同的编译task。
解析并自动生成manifest文件
这部分是本文重点啦!从上面的步骤来看,我们显然是准备了2份manifest文件,平时维护时也需要一起修改。实际业务中很可能debug和release的manifest内容差不多,可能只是某些组件节点的属性不同,手动改也挺麻烦的。
能不能通过gradle脚本动态地来修改并生成manifest文件呢?当然可以,本质上就是处理XML文件。AndroidManifest是标准的XML文件。正好,Groovy处理XML又非常的简单。这里我们还是以一个实际例子来讲。
比如我们的需求是,在debug测试时,应用正常显示桌面图标,在release发布时,应用需要隐藏图标。那么,两个manifest文件就是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0" encoding="utf-8"?> <manifest ...> <application ...> <activity ... android:name=".TestActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> ...
|
release版本的manifest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?xml version="1.0" encoding="utf-8"?> <manifest ...> <application ...> <activity ... android:name=".TestActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <data android:host="localhost" android:scheme="${applicationId}" /> </intent-filter> </activity> ...
|
然后平时我们开发过程中,如果新增了一些四大组件,2个文件都要同时增加,而区别却只有入口Activity。我们想要的是release版本的manifest是自动生成的(在生成时插入那个data节点),平时只需要开发改动debug版本的文件即可。
主要思路比较简单:
- 通过Groovy的XML解析库读取debug的manifest文件,遍历节点找到入口Activity;
- 将data节点插入到入口Activity下面;
- 把新的内容写入release版本的文件当中。
先直接看build.gradle脚本源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import groovy.xml.XmlUtil
def getManifestPath(buildType) { return "${projectDir}/src/main/$buildType/AndroidManifest.xml" }
def handleManifestXml(manifest) { if (gradle.startParameter.taskNames.contains(":app:assembleDebug")) { def debugPath = getManifestPath('debug') manifest.srcFile debugPath println("Manifest path: $debugPath") } else { def releasePath = getManifestPath('release') println("Manifest path: $releasePath") manifest.srcFile releasePath
def debugPath = getManifestPath('debug') def debugFile = new File(debugPath) def releaseFile = new File(releasePath) def debugXml = new XmlParser(false, false).parse(debugFile) debugXml.application[0].each { comp -> if (comp.name() == 'activity') { comp.each { filter -> if (filter.toString().contains('android.intent.category.LAUNCHER')) { filter.appendNode('data', ['android:host': 'localhost', 'android:scheme': '${applicationId}']) return true } } } } releaseFile.write(XmlUtil.serialize(debugXml)) } }
android { defaultConfig { applicationId "com.xxx.xxx" } sourceSets { main { handleManifestXml(manifest) } } ...
|
关键逻辑从 def debugXml = new XmlParser(false, false).parse(debugFile)
开始,这里的XmlParser构造方法可以不传任何参数,我这里传false主要是为了让manifest根节点自动添加 xmlns ,这样最后生成文件内容会简洁一点。
然后就是 debugXml.application[0].each { comp -> ... }
循环块,Groovy的语法很神奇,这里可以直接通过节点名称来获取数组,比如application,取0当然就是第一个,一般我们的manifest里就一个application节点,所以也不会出现空指针异常。each是数组的函数,遍历数组的每一个节点,comp参数是我自定义的命名。能拿到application的子节点,后面就都是一些逻辑处理了,不赘述。
一些小问题
如果是Windows开发者,最后写文件时,需要注意换行符和编码的问题。
1、最后的write方法,加上参数:
1
| releaseFile.write(xmlStr, "UTF-8")
|
或者直接改全局配置,修改整个工程下的gradle.properties文件,在gradle的JVM参数后面追加如下:
1
| org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
2、换行符问题,没有JVM参数可供修改,只能手动处理,还是最后write时:
1
| releaseFile.write(XmlUtil.serialize(debugXml).replaceAll('\r\n', '\n'))
|
这里XmlUtil工具类的serialize方法返回类型实际上就是String,所以可以这样直接replace。