深入理解Android之Gradle学习笔记

来源:互联网 发布:java中单例设计模式 编辑:程序博客网 时间:2024/05/29 19:53
深入理解Android之Gradle学习笔记
最近在学习gradle,innost的这篇文章可以说是目前中文说gradle最好的文章
深入理解 Android 之 Gradle.文章名字虽然叫深入理解,但是其实讲的也不深,不过比其他的说脚本怎么配置的文章好太多了,读完之后收货颇多,在这里记录重点,并且把他文中的demo进行实现改进(作者未提供源码),算是对原文的一个总结和补充(源码在文末)。
基础知识
Gradle
Gradle 是一个框架,负责定义流程和规则,而具体的构建工作则是通过插件的方式来完成的,比如编译Java 有 Java 插件,编译 Groovy 有 Groovy 插件,编译Android APP 有 Android APP 插件,编译 Android Library 有 Android Library 插件。我们可以通过apply plugin:'XXX'来导入插件。
Gradle对象
Gradle 主要有三种对象,这三种对象和三种不同的脚本文件对应,在 gradle 执行的时候,会将脚本转换成对应的对象(delagate):
  • Gradle 对象:当我们执行 gradle xxx 或者什么的时候,gradle 会从默认的配置脚本中构造出一个 Gradle 对象。在整个执行过程中,只有这么一个对象。Gradle 对象的数据类型就是 Gradle。我们一般很少去定制这个默认的配置脚本。
  • Project 对象:每一个build.gradle 会转换成一个 Project 对象。对于multi-project build,root的build.gradle会变为root project,module内的build.gradle会变为subproject.
  • Settings 对象:每一个 settings.gradle 都会转换成一个 Settings 对象。
Gradle生命周期
Gradle工作流程

Gradle工作包含三个阶段:
  • 首先是初始化阶段。对我们前面的multi-project build而言,就是执行settings.gradle
  • Configration阶段的目标是解析每个project中的build.gradle。比如multi-project build例子中,解析每个子目录中的build.gradle。在这两个阶段之间,我们可以加一些定制化的Hook。这当然是通过API来添加的。
  • Configuration阶段完了后,整个build的project以及内部的Task关系就确定了。恩?前面说过,一个Project包含很多Task,每个Task之间有依赖关系。Configuration会建立一个有向图来描述Task之间的依赖关系。所以,我们可以添加一个HOOK,即当Task关系图建立好后,执行一些操作。
最后一个阶段就是执行任务了。当然,任务执行完后,我们还可以加Hook。
multi-project
这里要重点说下multi-project,此时会有一个root project,若干个subproject(每一个subproject代表一个module)。那么root project和subproject之间有什么关系呢? 很久以前是只有subproject没有root project的,这个时候会有个问题,有一些共性的东西每个build.gradle都要写,若有100个module,那部分共性代码就写100次。然后有了root project,我们可以把公共的东西写到root project里。所以subprojects和allprojects这2个build script在root project会很常见。
基本task
android插件依赖于Java插件,而Java插件依赖于base插件。base插件有基本的tasks生命周期和一些通用的属性。base插件定义了例如assemble和clean任务,Java插件定义了check和build任务,这两个任务不在base插件中定义。
这些tasks的约定含义:
assemble: 集合所有的output
clean: 清除所有的output
check: 执行所有的checks检查,通常是unit测试和instrumentation测试
build: 执行所有的assemble和check
Posdevice实例
前提
本文OS为mac,直接使用AS的Terminal来构建,主要是2个命令./gradlew assemble./gradlew clean,所以就不用搭环境了。当然很多时候我会在./gradlew xxx之后加入-q,可以去掉一些系统日志,让结果看起来更清晰点。
本文的gradlew版本如下
X-Pro:Version2Asset fish$ ./gradlew -version------------------------------------------------------------Gradle 2.14.1------------------------------------------------------------Build time: 2016-07-18 06:38:37 UTCRevision: d9e2113d9fb05a5caabba61798bdb8dfdca83719Groovy: 2.4.4Ant: Apache Ant(TM) version 1.9.6 compiled on June 29 2015JVM: 1.8.0_77 (Oracle Corporation 25.77-b03)OS: Mac OS X 10.10.5 x86_64
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
为什么用./gradlew ...,而不是用gradle ...呢?第二种是用环境变量里的gradle,第一种是用当前工程里的gradle即gradle wrapper里的gradle,一般我们都使用第一种,不同工程的gradle插件版本可能差别很大。
需求
(为了更好的体现gradle的思想,我对原文的需求进行适当的修改。)
有个android Project,内有2个module,分别是app module和library module.其中app module的名字叫app,library module的名字叫cposdevicesdk。
  • cposdevicesdk编译出release版本的jar包拷贝到根目录的output文件夹下,debug版本不编译
  • app编译产生的debug和release的apk都需要拷贝到根目录的output文件夹下
  • output文件夹下最后会有3个产物,app编译产生的debug和release版本,cposdevicesdk编译出release版本,这3个产物的名字内必须有版本号,版本号来自manifest
实现
需求定了就可以撸起来了。很容易的,我们new一个project叫做Posdevice,里面有2个module,app和cposdevicesdk,app依赖于cposdevicesdk。此时工程结构如下所示。
此时其实有3个build.gradle文件,一个setting.gradle文件。3个build.gradle分别是根build.gradle,module app内build.gradle以及cposdevicesdk内build.gradle。
一次gradle构建只产生一个gradle对象,有多少个module便对应多少个gradle project(注意和android studio的Project区分,本文中as的project我都会写明AS project)
所以这里会有一个gradle对象,1个root project对象,2个subproject对象,1个setting对象
编译环境配置
首先我们看到app和cposdevicesdk的build.gradle里面都有以下代码,注意下compileSdkVersion和buildToolsVersion,不同人的机器上,这些值可能不一样,所以最好不要在build.gradle里面写死(有时候github上拉下来的代码编译不过也是由此引起)。
android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { ... } buildTypes { ... }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
那怎么写compileSdkVersion和buildToolsVersion才比较灵活呢?有2种方法,一种是写在local.properties里面。我们在setting.gradle里去读取值,然后利用ext给grale对象创建一个成员存起来,以后全局都可以从gradle里获取值了。第二种是利用gradle.properties文件。我这里为了学习对compileSdkVersion采用方法1,对buildToolsVersion采用方法2
ext gradle
代码如下,首先在local.properties里添加sdk.api=android-25,注意必须要带android,不能只写25.
#local.properties#AS帮我们生成的sdk.dir=/Users/fish/Documents/android-sdk-macosx#额外添加,必须如下写,不能只写25sdk.api=android-25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
接着在settings.gradle内读取到sdk.api的值,然后用ext给gradle增加一个变量api,这样其他地方就能用gradle.api来取这个值了
//settings.gradledef initSdkApi(){ println "setting initSdkApi" Properties properties = new Properties() //local.properites 也放在 posdevice 目录下 File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties") properties.load(propertyFile.newDataInputStream()) /* 根据 Project、Gradle 生命周期的介绍,settings 对象的创建位于具体 Project 创建之前 而 Gradle 底对象已经创建好了。所以,我们把 local.properties 的信息读出来后,通过 extra 属性的方式设置到 gradle 对象中 而具体 Project 在执行的时候,就可以直接从 gradle 对象中得到这些属性了! */ gradle.ext.api = properties.getProperty('sdk.api')}//初始化initSdkApi()include':app', ':cposdevicesdk'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
//app和cposdevicesdk里的build.gradleandroid {// 采用api导入的方式 compileSdkVersion gradle.api }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5
gradle.properties
这种方式会更简单,在gradle.properties里定义buildToolsVer
#gradle.propertiesorg.gradle.jvmargs=-Xmx1536m# When configured, Gradle will run in incubating parallel mode.# This option should only be used with decoupled projects. More details, visit# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects# org.gradle.parallel=true#额外添加buildToolsVer=25.0.0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
然后在2个module的build.gradle里都能用buildToolsVer了
android {// 采用api导入的方式 compileSdkVersion gradle.api// 利用gradle.properties buildToolsVersion buildToolsVer }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
结论
明显第二种方法简单一些,所以我们尽量利用gradle.properties,当然第一种方法学习下来熟悉gradle也不错。
配置好之后,我们可以在AS的terminal里执行下./gradlew assemble,顺利通过
utils.gradle
在gradle中,我们常常会定义一些常用的函数,这样全局通用,这些函数往往会写到一个gradle文件里,我们就定一个utils.gradle。这里定义2个函数,一个是getVersionNameAdvanced,从manifest内去获取版本号。另一个是disableDebugBuild,对debug的task设置disable,这样task就不会执行了。
def getVersionNameAdvanced(){ def xmlFile = project.file("src/main/AndroidManifest.xml") def rootManifest = new XmlSlurper().parse(xmlFile) return rootManifest['@android:versionName']}//对于 android library 编译,我会 disable 所有的 debug 编译任务def disableDebugBuild(){ //返回值保存到 targetTasks 容器中 println "project.tasks size "+ project.tasks.size() //project.tasks 包含了所有的 tasks,下面的 findAll 是寻找那些名字中带 debug 的 Task。def targetTasks = project.tasks.findAll{task -> task.name.contains("Debug") } //对满足条件的 task,设置它为 disable。如此这般,这个 Task 就不会被执行 targetTasks.each{// println "disable debug task : ${it.name}" it.setEnabled false }}//将函数设置为 extra 属性中去,这样,加载 utils.gradle 的 Project 就能调用此文件中定义的函数了ext{ getVersionNameAdvanced = this.&getVersionNameAdvanced disableDebugBuild = this.&disableDebugBuild}
  • 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
  • 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
utils.gradle里面定义了这些函数,其他gradle文件要用必须要apply(相当于import)。那我们是不是要每个gradle都加下面代码呢?
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"

这么做当然可以,但是还有更简单的方法,那就是在根build.gradle里配subprojects,完整的根build.gradle如下所示
// Top-level build file where you can add configuration options common to all sub-projects/modules.println "root build.gradle execute"buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.3' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }}subprojects{//为每个子 Project 加载 utils.gradle 。当然,这句话可以放到 buildscript 花括号之后,必须位于subprojects之内 apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"}allprojects { repositories { jcenter() }}task clean(type: Delete) { delete rootProject.buildDir}
  • 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
  • 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
禁止cposdevicesdk的debug版本编译
好了,基础都写好了,下面来完成需求,在cposdevicesdk的build.gradle里加以下代码,就可以禁止cposdevicesdk的debug版本编译,这里的project就是cposdevicesdk的build.gradle对应的project,project.afterEvaluate会在task有向图创建完毕之后被调用。
/* 因为我的项目只提供最终的 release 编译出来的 Jar 包给其他人,所以不需要编译 debug 版的东西 当 Project 创建完所有任务的有向图后,我通过 afterEvaluate 函数设置一个回调 Closure。在这个回调 Closure 里,我 disable 了所有 Debug 的 Task*/project.afterEvaluate{ println'afterEvaluate -> disableDebugBuild lib' disableDebugBuild()}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
拷贝jar和apk
拷贝这件事情应该发生在assemble之后,我们如何在assmble之后插入一个拷贝的任务呢?介绍2种方法
函数调用
第一种方法是函数调用,innost的文章里用这种方法。先找的 assemble 任务,然后我通过 doLast 添加了一个 Action。这个 Action 就是 copyOutput,copyOutput是一个在utils.gradle里定义的函数。
tasks.getByName("assemble"){ it.doLast{ println "$project.name: After assemble, jar libs are copied to local repository" copyOutput(true) }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
task->finalizedBy
这种方法是写一个copyTask,然后把copyTask绑定在assembleRelease后面,在我的代码里使用这种方法,代码如下,其实要是绑在assemble后面更加合理,但是我试了下不行,不知道为什么。
//lib的build.gradletask copyTask(type: Copy){ println "i am coping "from('build/intermediates/bundles/release/') into('../output/') include('classes.jar') rename (/(.*).jar/, 'cposdevicesdk-release'+project.getVersionNameAdvanced()+'.jar')}tasks.whenTaskAdded { task -> //下边如果用 assemble,不行if (task.name == 'assembleRelease') { task.finalizedBy 'copyTask' }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
命名加版本号
其实上边的copyTask里已经加入改名字的代码了.app的copyTask如下,比较简单,我们自己定义了一个task,copyTask 他的类型是Copy(代表继承AbstractCopyTask),后面的from,into,include,rename都是AbstractCopyTask的方法,返回this,这是gradle task的常见写法。
rename的时候使用正则替换,第一个变量是一个正则表达式用//包起来,代表以.apk结尾的任意字符串,第二个变量里的$1就是.apk之前的所有字符串。
task copyTask(type: Copy){ println "apk is coping "from('build/outputs/apk') include('*.apk') into('../output/') rename(/(.*).apk/,'$1-'+project.getVersionNameAdvanced()+'.apk')}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
lib的copyTask如下,首先lib编译出来是aar文件,而我们想要jar包,jar包在哪呢?jar包是中间产物,文件是 ./build/intermediates/bundles/release/classes.jar,我们只要把他拷贝出来就行了。
task copyTask(type: Copy){ println "jar is coping "from('build/intermediates/bundles/release/') into('../output/') include('classes.jar') rename (/(.*).jar/, 'cposdevicesdk-release'+project.getVersionNameAdvanced()+'.jar')}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
clean注意清除output
我们每次./gradlew assemble都会把2个apk,1个jar拷贝到ouput里去,所以对应的clean要加入删除代码,在clean的时候删除output文件夹,我们可以在clean后加入删除的代码就好了,如下所示。
clean.doFirst { delete"${rootDir}/output/" println "delete output before clean"}
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4
好了,大功告成!可以用./gradlew assemble./gradlew clean2个命令玩起来了。
其他
根据下边的日志看起来,copy应该会无效啊,此时afterEvaluate都没执行,是处于Configuration阶段,没有到Execution阶段(在execution阶段完成各种编译链接) 但是实际上copy是发生在assemble之后的,我估计这就是闭包的doLast和直接代码的区别,真正的copy发生在doLast内。L12之后开始execution。
192:Posdevice fish$ ./gradlew assemble -qsetting.gradle executesetting initSdkApiroot build.gradle executeapp build.gradle executeapk is coping lib build.gradle executejar is coping afterEvaluate -> disableDebugBuild libproject.tasks size 152debug tasks size 73taskGraph.whenReadyafter assemble
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
实例2 通过构建脚本影响源代码
需求
  • 默认构建是产生debug和release2个包,要求再加一个demo包,demo包的签名和debug包保持一致
  • 在apk的第一个页面显示 I am Debug/Release/Demo
实现
首先第一个需求加一个demo包,非常简单在buildtype那里加就ok了。主要看第二个需求,要不同的buildtype编译出来的apk能够知道自己是属于哪个buildtype的,这里实际上是通过gradle代码影响了工程代码。一般来说工程代码和构建脚本是相互独立的,要如何才能影响到工程的代码呢?我们可以在构建的时候把当前的buildtype写到某个文件,然后在apk的代码里去读取这个文件。ok,lets do it!
buildtype增加demo
首先实现buildtype增加demo,很简单,在app的build.gradle内的buildTypes内加下demo即可,demo还得配置下签名。buildTypes内其实隐藏了一个debug。
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } demo{ //和debug使用同一个签名 signingConfig signingConfigs.debug } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
assets文件记录buildtype
根据innost大神的思路,我写下了如下代码,在preDebugBuild、preReleaseBuild、preDemoBuild任务开始的时候添加一个doFirst任务,这是一种常见的做法,preXXXBuild完成之后就会执行我们的doFirst内的任务。这样看起来没什么问题,但是我试了下,有问题。
之前,我们一直在用2个命令./gradlew assemble./gradlew clean,现在再学习几个。./gradlew assembleDebug./gradlew assembleRelease./gradlew assembleDemo,这3个命令分别是构建debug包,构建relese包和构建Demo包,实际上assemble就是依赖于assembleDebug、assembleRelease、assembleDemo这3个task。在这里,我试了下用./gradlew assembleDebug得到debug包,可是debug包里的assets文件里写的是I am release。然后我又用./gradlew assembleDemo构建了demo包,结构里面还是I am release。Why?
def runtime_config_file = 'app/src/main/assets/runtime_config' project.afterEvaluate{ //找到 preDebugBuild 任务,然后添加一个 Action tasks.getByName("preDebugBuild"){ it.doFirst{ println "generate debug configuration for ${project.name}" def configFile = new File(runtime_config_file) configFile.withOutputStream{os-> os << I am Debug\n' //往配置文件里写 I am Debug } } } //找到 preReleaseBuild 任务 tasks.getByName("preReleaseBuild"){ it.doFirst{ println "generate release configuration for ${project.name}" def configFile = new File(runtime_config_file) configFile.withOutputStream{os-> os << I am release\n' } } } //找到 preDemoBuild。这个任务明显是因为我们在 buildType 里添加了一个 demo 的元素 //所以 Android APP 插件自动为我们生成的 tasks.getByName("preDemoBuild"){ it.doFirst{ println "generate offlinedemo configuration for ${project.name}" def configFile = new File(runtime_config_file) configFile.withOutputStream{os-> os << I am Demo\n' } } } }
  • 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
  • 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
我把task的依赖图打出来后发现,原来有如下依赖关系,从下面可以看出assembleDebug会调用preDebugBuild, preDemoBuild,preReleaseBuild,所以虽然我们只是执行assembleDebug,但是preDebugBuild, preDemoBuild,preReleaseBuild都会被调用,所以最后写成了I am release。原作者能够成功,估计是gradle插件的版本不一样。
->表示depend onassembleDebug->packageDebug->transformClassesWithDexForDebug->prepareDebugDependencies->prepareComAndroidSupportSupportCoreUi2501Libarary->preDebugBuild, preDemoBuild,preReleaseBuild
  • 1
  • 2
  • 1
  • 2
那怎么办呢?其实很简单,把assembleDebug和assembleDemo的具体task看一下,比较一下看看各自有什么特殊的task,基于这个task就可以了。怎么看assembleDebug的具体task呢?执行./gradlew assembleDebug就可以了,注意不要加-q,大概如下所示,以冒号开头的都是任务,比较多。
...:app:prepareComAndroidSupportAnimatedVectorDrawable2501Library UP-TO-DATE:app:prepareComAndroidSupportAppcompatV72501Library UP-TO-DATE:app:prepareComAndroidSupportSupportCompat2501Library UP-TO-DATE:app:prepareComAndroidSupportSupportCoreUi2501Library UP-TO-DATE:app:prepareComAndroidSupportSupportCoreUtils2501Library UP-TO-DATE:app:prepareComAndroidSupportSupportFragment2501Library UP-TO-DATE...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
我把assembleDebug和assembleDemo的具体task对比了一下,找到了prepareDebugDependencies和prepareDemoDependencies,尝试了下用prepareXXXDependencies,果然成功了,而且直接用./gradlew assemble生成3个包也没问题!
效果如下:
app的build.gradle部分代码如下所示
def runtime_config_file = 'app/src/main/assets/runtime_config'project.afterEvaluate{ println "task size "+tasks.size() //找到 prepareDebugDependencies 任务,然后添加一个 Action tasks.getByName("prepareDebugDependencies"){ it.doFirst{ println "generate debug configuration for ${project.name}"def configFile = new File(runtime_config_file) configFile.withOutputStream{os-> os << 'I am Debug\n'//往配置文件里写 I am Debug } } } //找到 prepareReleaseDependencies 任务 tasks.getByName("prepareReleaseDependencies"){ it.doFirst{ println "generate release configuration for ${project.name}"def configFile = new File(runtime_config_file) configFile.withOutputStream{os-> os << 'I am release\n' } } } //找到 prepareDemoDependencies tasks.getByName("prepareDemoDependencies"){ it.doFirst{ println "generate demo configuration for ${project.name}"def configFile = new File(runtime_config_file) configFile.withOutputStream{os-> os << 'I am Demo\n' } } }}
  • 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
  • 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
实例2优化–buildConfigField
实例2这么做其实挺复杂的,我们完全可以使用更简单的方式来解决问题,那就是使用buildConfigField。
我们在app的build.gradle内写如下代码,用buildConfigField来定义一个field叫做API_URL。
buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "String", "API_URL","\"i am release\"" } demo{ //和debug使用同一个签名 signingConfig signingConfigs.debug applicationIdSuffix 'demo' buildConfigField "String", "API_URL","\"i am demo\"" } debug{ buildConfigField "String", "API_URL","\"i am debug\"" } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
这个gradle在编译之后会产生3个Build.Config文件,可以看到我们定义的API_URL变为了BuildConfig的一个成员变量,然后我们可以在代码里直接用BuildConfig.API_URL.为什么?看BuildConfig的包名,和我们程序包名一致,所以可以直接用。
package com.fish.test;publicfinalclassBuildConfig {publicstaticfinalboolean DEBUG = Boolean.parseBoolean("true"); publicstaticfinal String APPLICATION_ID = "com.fish.test"; publicstaticfinal String BUILD_TYPE = "debug"; publicstaticfinal String FLAVOR = ""; publicstaticfinalint VERSION_CODE = 1; publicstaticfinal String VERSION_NAME = "1.0"; // Fields from build type: debugpublicstaticfinal String API_URL = "i am debug";}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
android代码如下
publicclassMainActivityextendsAppCompatActivity {@OverrideprotectedvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String s = BuildConfig.API_URL; TextView tv = (TextView) findViewById(R.id.aa); tv.setText(s); }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
可以看到利用buildConfigField简便优雅的实现了实例2的需求。
经验总结
  • 跟本地编译环境相关参数应该放入gradle.properties内
  • Gradle可以通过ext为对象额外添加属性或者方法
  • gradle命令支持缩写,比如aR表示assembleRelease
  • 由于groovy支持动态类型,所以有时候写错了也不会有提示,而且AS内的gradle也无法debug,所以要多用println来打日志
  • 由于AS的terminal比较简陋,所以我打日志的时候一般会在日志里填中文,这样会醒目很多
源码
Posdevice实例 https://github.com/chefish/Posdevice
实例2 https://github.com/chefish/Version2Asset
Ref
http://wiki.jikexueyuan.com/project/deep-android-gradle/four-four.html
https://segmentfault.com/a/1190000004234712?_ea=538654
https://segmentfault.com/a/1190000004241503#articleHeader12
0 0