版本的统一管理
当我们的工程中有许多module的时候,分开管理编译版本,minsdk将会是一件很麻烦的事,因为一个library的改动,可能会影响到其他module。这时我们就需要对所有的版本进行统一的管理,管理的方式有两种:
rootProject
我们可以把一些需要用的字段都放在project的build.gradle(注意是project的不是module的)中:
ext {
compileSdk = 21
minSdk = 11
targetSdk = 23
support = "23.1.1"
buildTools = "21.0.1"
buildstyle ="debug"
}
这样,在module的build.gradle中可以进行读取:
defaultConfig {
applicationId "android.com.testgradle"
minSdkVersion rootProject.ext.minSdk
targetSdkVersion rootProject.ext.targetSdk
versionCode 1
versionName "1.0"
}
gradle.properties
找到工程目录下的gradle.properties文件,如果没有也可以自己创建:
ANDROID_COMPLILE_SDK_VERSION=21
ANDROID_BUILD_SDK_VERSION=21.0.1
ANDROID_TEXT=test
然后在各个module的build.gradle中可以引用:
compileSdkVersion ANDROID_COMPLILE_SDK_VERSION as int
buildToolsVersion ANDROID_BUILD_SDK_VERSION
需要注意的是在gradle.properties中声明的格式都是string类型,如果如要转化成int类型,可以用as int 进行强制转化。
程序中对buildTypes的区分
buildTypes是对不同build类型的处理
当你点击运行按钮的时候会根据build Variant进行对应的方式编译。
build Variant可以在这里进行选择:

library的buildTypes
默认情况下被依赖工程会使用release模式,与上层依赖的app工程选择的模式无关
需要在build.gradle中进行设置:
defaultPublishConfig "debug"
新增buildTypes
你可以在buildTypes,根据需要新增一个类型,如下代码所示:
buildTypes {
debug {
buildConfigField("String","TEXT","\"这个字符串来自debug模式\"")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
stag {
buildConfigField("String","TEXT","\"这个字符串来自test模式\"")
minifyEnabled false
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
buildConfigField("String","TEXT","\"这个字符串来自release模式\"")
minifyEnabled false
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
不同模式字符不同
根据上面的代码,在不同的buildTypes中,TEXT这个变量是不一样的,而且根据代码可以看出,这个变量是一个String类型,那么我们在程序文件中可以这样通过BuildConfig引用:
t2.setText(BuildConfig.TEXT);
Variant
gradle插件允许最终生成的包以多个维度进行组合
例如我们可以设定一下几个维度:
productFlavors {
red {
applicationId 'android.com.red'
versionCode 1
minSdkVersion 21
targetSdkVersion 22
}
blue {
applicationId 'android.com.blue'
}
yellow {
applicationId 'android.com.yellow'
}
}
每个维度中可以设置这个版本的最小sdk限制,以及targetsdkversion
假设我们设置的维度有red blue yellow但是结合之前讲过的buildType(假设只有debug和release)
那么将会出现以下构建:
blueDebug和blueRelease
yellowDebug和yellowRelease
redDebug和redRelease
Gradle会为每一个Variant创建一个任务
对应如下:
gradle assembleBlue 会生成debug和release两个版本
gradle assembleDebug 会生成blue yellow red三个版本
gradle assembleBlueDebug 会生成bluedebug一个版本
这里还有一个用处需要提一下:
Gradle在打包android应用之前会将所有的代码,资源文件,包括manifest进行结合,当然library也会提供额外的资源,这些也会进行合并。
字符串读取
如果我们在创建一些设置时,需要动态的去更改内容,可以设置一个变量,然后从本地文件或者打包的命令行读取,我们这里就拿上面提到过的BuildConfig做例子,希望程序中引用的字符串是从本地读取的或从命令行读取的
从本地文件中读取
首先需要在最开始的地方设置一个变量aaa:

然后在buildTypes的debug模式中修改对应的代码:
debug {
buildConfigField("String","TEXT","\""+aaa+"\"")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
然后在工程目录下创建一个本地文件test.properties,并添加内容:

然后回到你刚才定义字符串的build.gradle中,添加:
if(rootProject.file('test.properties').exists()){
java.util.Properties properties = new Properties()
properties.load(rootProject.file('test.properties').newDataInputStream())
aaa = properties.getProperty('debug.text')
println("!!!!!"+aaa)
}else {
aaa = "文件没找到"
}
即可读取到你本地文件的字符串
从命令行读取
如果是从命令行读入就更加简单,只需要将上面读取文件的代码改为:
aaa = new String(System.console().readLine("请输入字符串:"))
编译命令
首先想看一个工程包含了多少task,需要切到这个工程目录下:
gradle tasks
会列出所有的tasks:

当然篇幅有限,这里不贴出所有的任务了,我们只需要知道编译命令即可,如图可以看到,如果想编译debug版本,使用:
assembleDebug
其它同理即可。
如果使用build则回编译出所有的版本
冲突问题

在执行打包的时候很有可能会出现如上问题,这是由于依赖的jar冲突问题,我们可以分析一下工程结构。
我现在的结构是:

现在调整一下结构:

把jar以module的形式提供就不会出现冲突。
编译流程


Task任务
执行顺序
例如我们新建一个任务:
task umengtest{
println("aaaaaaaaa")
println("bbbbbb")
}
他的执行如下:

它并不是在执行任务的时候执行的,而是在执行任务之前就打印了。
这是由于gradle构建有三个阶段:
初始化阶段,配置阶段,执行阶段。
上面的例子实际是打印在了配置阶段,换句话说,你不执行这个任务,执行这个工程别的任务也会打印。
例如我再写一个任务:
task umengtestaa<<{
println("ccc")
println("dddd")
}
这次执行这个任务:

你会发现也打印aaaa这说明这不是在真正的执行阶段执行的,为了保证任务的可控性,可以像上面umengtestaa那样写加一个<<符号
这样就可以保证了任务的可控性:

或者使用:
task umengtest{
doLast{
println("aaaaaaaaa")
println("bbbbbb")
}
}
打包任务
有了上面的基础,下面就可以说一下打包的任务了,根据我们之前的工程,我们有一个app的module和四个library module,我需要打印出一个apk和四个jar,明确了任务,现在可以开始实施了。
打开工程的build.gradle
def sdk = [
root : 'build/sdk/',
]task dabao( type:Zip) {
dependsOn('cp_main')
def name = 'umeng_test' ;
destinationDir = file('build')
archiveName = name + '.zip'
from('build/sdk') {
into( name )
}
}
task cp_main(type: Copy, dependsOn: ['app:assembleRelease'] ) {
destinationDir = file( sdk.root )
from('app/build/outputs/apk') {
include('app-release.apk')
rename ('app-release.apk','test.apk' )
into('.')
}
from('mylibrary1/build/intermediates/bundles/release') {
include('classes.jar')
rename ('classes.jar','library1.jar' )
into('.')
}
from('mylibrary2/build/intermediates/bundles/release') {
include('classes.jar')
rename ('classes.jar','library2.jar' )
into('.')
}
from('mylibrary3/build/intermediates/bundles/release') {
include('classes.jar')
rename ('classes.jar','library3.jar' )
into('.')
}
from('mylibrary4/build/intermediates/bundles/release') {
include('classes.jar')
rename ('classes.jar','library4.jar' )
into('.') }
}
这时,我们再去找一下工程的build文件夹下可以发现:

打包不同内容的module
打包不同内容的module可以利用之前讲过的variant或这个buildtypes来控制,这里就不说了,不明白的,可以回头再去看一下结合上面的打包脚本没有什么难度,然而还有一种需求,不是某个变量或者包名的更改,而是两个版本中两个文件的不同,我们可以试一下修改variant的方式来实现,在app的build.gradle中:
productFlavors {
pay {
}
free {
}
}
修改结构目录:

这样编译出来就会有两个不同类型的apk了
防止打包错误
在这里已经说完了所有与打包相关的东西了,在最后仍然加这一个标题是交给大家如何防止项目开发者打包出错,或者上传包出错后定位问题。
方法就是在打包的时候,生成一个记录文件,在这个包中记录打包时间,和当前git的版本号:
def releaseTime() {
return new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("GMT+8"))
}
def getGitVersion() {
return 'git rev-parse --short HEAD'.execute().text.trim()
}
task writefile( ){
File configFile = new File('config.xml');
if (!configFile.exists()){
configFile.createNewFile()
}
FileOutputStream out =new FileOutputStream(configFile)
def result ="编译时间:"+"${releaseTime()}\n"+"commitid:"+"${getGitVersion()}\n" out.write(result.getBytes())
out.close();
}
task cp_config(type: Copy, dependsOn: ['writefile'] ) {
destinationDir = file( sdk.root )
duplicatesStrategy = 'exclude'
includeEmptyDirs = false
from('.') {
include('config.xml')
into('.')
}
}
以上工程中所用到的所有代码,已经上传github
地址如下:
https://github.com/mymdeep/AndroidGradle
如有对groovy语法不清楚的看官,请看一下我的上一篇文章,groovy基础知识:
http://www.jianshu.com/p/b58b254d8f6e
*更多的开发知识,可以关注我的公众号:

网友评论