其实思路还是挺清晰的,就是将原来手工完成的工作交给脚本去做
gradle使用的groovy基础知识
build.gradle会自动对应一个org.gradle.api.Project实例,大多数人情况下,构建脚本中调用的属性和方法都自动委托给了这个Project实例。
也就是说,如果build.gradle没有使用插件的话,在这个文件里直接调用的外界属性和方法都是Project实例内的,一般我们见到的代码都是方法的调用,不过是去掉了括号,使用了闭包
Project下的build.gradle是没有应用插件的,Moudle下的build.gradle使用了插件(一般为)
apply plugin: 'com.android.application'
或者为
apply plugin: 'com.android.library'
“构建类型 + 定制产品 = 构建变种版本”
moudle底下的build.gradle的android域下的一个代码块,默认会有两个build type,release和debug。
//默认
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
也是在android代码块里
productFlavors {
//生产环境
production {
}
//准生产环境
preproduction {
}
//外网测试环境
outTest {
}
}
设置完上述选项并编译gradle文件后在android studio左下角查看:
选择我们需要打包的环境。
下一步就需要我们写的groovy代码能自己识别出当前选择的Build Variant并执行相关的文件操作
选择不同的Build Variant时,gradle在执行编译过程中编译的任务会出现差别,在android studio右侧的面板可以查看:
由此我们可以在编译(安装)任务执行图(taskGraph)准备好的时候检测需要执行任务的名字从而判断出此次编译(安装)的Build Variant。
任务(Task)是指不可分的最小工作单元,代表一个逻辑上较为独立的执行过程,比如:编译、复制、打包.
任务执行图(taskGraph):task互相之间存在着依赖关系,比如要编译的时候要先进行语法检查,所以编译任务依赖语法检查任务,当这一系列任务要开始执行的时候就会先构建好一个执行的图谱,就是任务执行图(taskGraph)
//与Build Variant对应,默认为error
def buildVariant = "error"
//gradle是Project实例中的属性
gradle.taskGraph.whenReady { TaskExecutionGraph taskExecutionGraph ->
//不同的Build Variant对应不同的任务
def variantMap = ['outTest' : "task ':RDWork:checkOutTestDebugManifest'",
'production' : "task ':RDWork:checkProductionDebugManifest'",
'preproduction': "task ':RDWork:checkPreproductionDebugManifest'"]
//遍历每一个task
taskExecutionGraph.allTasks.each { output ->
switch (output.toString().trim()) {
case variantMap.outTest:
buildVariant = "outTest"
println('外网测试环境')
break
case variantMap.production:
buildVariant = "production"
println('生产环境')
break
case variantMap.preproduction:
buildVariant = "preproduction"
println('准生产环境')
break
}
}
//如果没有包含备选的任务,抛出异常
//groovy使用“==”时会自动调用equal()方法
if (buildVariant == 'error') {
throw new Exception("无配置的task,请重新配置")
}
}
我们打包时主要做的事情就是改变assets
文件夹中配置文件的配置,例如不同的环境需要用不同的ip地址。
自动打包时将每种环境的配置文件都备份在不同的文件夹下,地址是:
"$rootDir/assets/$moudleName/$buildVariant"
rootDir是Project实例中的属性,代表项目的根目录
gradle给了我们很方便的复制文件的方式,copy代码块(方法)
/**
* 放置文件到对应moudle的assets文件夹
* @param moudleNameList 需要放置的moudle名字的集合
* @param buildVariant 当前的变种版本(Build Variant)
*/
def putFile(moudleNameList,buildVariant) {
moudleNameList.each { moudleName ->
copy {
from "$rootDir/assets/$moudleName/$buildVariant"
into "$rootDir/$moudleName/src/main/assets"
println("${moudleName} copy 成功")
}
}
}
有的时候我们希望能在每次构建时都动态更改,例如版本号,我们assets
文件夹下的ClientVer.xml
文件需要在每次打包时写入当前的版本号,那么做法其实也很简单,在需要改变的文件中需要动态改变的部分填充为占位符,每次重新put文件后就将占位符替换为有意义的字符
/**
* 将文件的源字符串替换为目标字符串
* @param path 文件路径
* @param srcString 源字符串
* @param dstString 目标字符串
*/
def overWriteString(String path, String srcString, dstString) {
def file = new File(path)
def string = file.text.replaceAll(srcString, dstString)
def printWriter = new File(file.parent, file.name).newPrintWriter()
printWriter.write(string)
printWriter.flush()
printWriter.close()
}
其实就是通过groovy执行命令行获取svn最近一次提交的信息并通过正则表达式截取出最近的版本号信息
/**
* 获取最近的svn版本号
* @param path svn文件或文件夹路径
* @return 最近的版本号
*/
String getSvnLastVersion(String path){
//先更新,避免获取版本号错误
//execute()即是执行命令行
"svn update $path".execute()
//获取最近一次提交信息的命令
def cmd = "svn log -l 1 $path"
def cmdResult = cmd.execute().text
def pattern = ~/r\d+/
def matcher = pattern.matcher(cmdResult)
if (matcher.getCount()!=1){
println(cmdResult)
throw new Exception("获取svn最近的版本号时匹配错误,尝试使用svn工具手动获取版本号:\n$cmdResult")
}else {
//获取的信息其会在版本号前加r,将其去掉
String lastVersion = matcher[0].replace('r','')
println("最近一次svn版本号为:$lastVersion")
return lastVersion
}
}
将版本号设为变量,这样就不用到处修改版本号了,只用修改一处,别的地方引用这个变量就可以了
//调用刚才的获取svn版本号的函数
ext.appVersionName = "1.0.0.${getSvnLastVersion(rootDir.absolutePath)}"
ext:上面的程序地址都是位于project的
build.gradle
文件中,而我们的版本号需要在moudle的build.gradle
文件中引用,前面也讲了,这些文件其实相当于Project类的不同对象,而ext关键字就相当于给类中定义了一个静态变量,这样在不同的对象中都可以直接使用
则在moudle的build.gradle文件中就可以这样使用:
versionCode new Integer(appVersionName.replace('.', ''))
versionName "$appVersionName"
如果appVersionName = 1.0.0.3008,则相当于:
//其实这些都是没加圆括号的方法
versionCode 1003008
versionName 1.0.0.3008
在application的build.gradle文件加入下列代码:
//使生成的apk名字加上版本号
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
def fileName = outputFile.name.replace(".apk", "-${appVersionName}.apk")
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
在dependencies中通过
${buildVariant}Complie引用
//例如
outTestCompile 'org.xutils:xutils:3.0'
不同的环境使用不同的applicationId可以让同一个手机装上不同的版本:
productFlavors {
//生产环境
production {
applicationId com.xdja.xxx.production
}
}
build.gradle文件中不宜写太多代码,一些工具方法可以放在其它的文件中
//是一个方法,功能比较像java中的import
apply(from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle")
上文中已经出现过的两个方法其实就是在util.gradle文件中
def overWriteString(String path, String srcString, dstString) {
...
}
String getSvnLastVersion(String path){
...
}
//将函数设置为extra属性中去,这样,加载utils.gradle的Project就能调用此文件中定义的函数了
ext {
overWriteString = this.&overWriteString
getSvnLastVersion = this.&getSvnLastVersion
}
注:根据不同productFlavors
复制文件这部分上述方法仅做参考,推荐使用官方的直接建立对应文件夹的方式实现