settings.gradle(对应Settings.java)决定哪些工程需要被gradle处理,占用了整个gradle生命周期的三分之一,即Initialzation初始化阶段。
对默认的文件位置进行修改,从而让gradle知道哪种资源要从哪些文件夹中去查找。
// sourceSets是可以调用多次的
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
sourceSets {
main {
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
}
// sourceSets一般情况下是一次性配置
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
}
// 使用编程的思想,配置sourceSets
this.android.sourceSets{
main {
jniLibs.srcDirs = ['libs']
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
Gradle中的Plugin是对完成指定功能的Task封装的体现,只要工程依赖了某个Plugin,就能执行该Plugin中所有的功能,如:使用java插件,就可以打出jar包,使用Android插件,就可以生成apk、aar。
apply plugin: 'groovy'
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
resources {
srcDir 'src/main/resources'
}
}
}
最后,Async一下工程,buildSrc就会被识别出来了,整体目录如图:
与java一样,在groovy目录下,创建一个包,再创建一个插件类(如:com.lqr.gradle.study.GradleStudyPlugin),该插件类必须实现Plugin接口。
注意:gradle插件类是.groovy文件,不是.java文件
import org.gradle.api.Plugin
import org.gradle.api.Project
/**
* 自定义Gradle插件
*/
class GradleStudyPlugin implements Plugin {
/**
* 插件引入时要执行的方法
* @param project 引入当前插件的project
*/
@Override
void apply(Project project) {
println 'hello gradle study plugin. current project name is ' + project.name
}
}
在编写完插件类的逻辑之后,需要在META-INF.gradle-plugins目录下创建一个properties文件(建议以插件类包名来命名,如:com.lqr.gradle.study.properties),在该properties中声明插件类,以此来指定插件入口。
该properties文件的名字将作为当前gradle插件被app工程引用的依据。
implementation-class=com.lqr.gradle.study.GradleStudyPlugin
// 如果报错 Could not find implementation class 'xxx' 的话,一般是类全路径有问题,默认包不需要写包路径,修改如下即可:
// implementation-class=GradleStudyPlugin
打开app工程的build.gradle,应用上面的自定义gradle插件,并Async。
apply plugin: 'com.android.application'
apply plugin: 'com.lqr.gradle.study'
android {
...
}
可以看到,在gradle的配置阶段,就输出了前面自定义插件的apply方法中的日志。
插件往往会在gradle脚本中进行参数配置,如在android{}中,可以配置compileSdkVersion等参数,其实本质上,就是在gradle脚本中使用闭包方式创建了一个javaBean,并将其传递到插件中被插件识别读取而已。步骤如下:
1)创建一个实体类,声明成员变量,用于接收gradle中配置的参数。(可以理解为就是javaBean,不过要注意,该文件后缀是.groovy,不是.java)
class ReleaseInfoExtension {
String versionCode
String versionName
String versionInfo
String fileName
ReleaseInfoExtension() {}
@Override
String toString() {
return "versionCode = ${versionCode} , versionName = ${versionName} ," +
" versionInfo = ${versionInfo} , fileName = ${fileName}"
}
}
2)在自定义插件中,对当前project进行扩展。
class GradleStudyPlugin implements Plugin {
/**
* 插件引入时要执行的方法
* @param project 引入当前插件的project
*/
@Override
void apply(Project project) {
// 这样就可以在gradle脚本中,通过releaseInfo闭包来完成ReleaseInfoExtension的初始化。
project.extensions.create("releaseInfo", ReleaseInfoExtension)
}
}
3)打开在app工程的build.gradle,通过扩展key值命名闭包的方式,就可以配置指定参数了。
apply plugin: 'com.lqr.gradle.study'
releaseInfo {
versionCode = '1.0.0'
versionName = '100'
versionInfo = '第一个app信息'
fileName = 'release.xml'
}
4)接收参数,如:
def versionCodeMsg = project.extensions.releaseInfo.versionCode
自定义插件无非就是封装一些常用Task,所以,扩展Task才是自定义插件的最重要的一部分。
扩展Task也很简单,继承DefaultTask,编写TaskAction注解方法,下面以 “把app版本信息写入到xml文件中”的task 为例,注释很详细,不多赘述:
import groovy.xml.MarkupBuilder
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class ReleaseInfoTask extends DefaultTask {
ReleaseInfoTask() {
group 'lqr' // 指定分组
description 'update the release info' // 添加说明信息
}
/**
* 使用TaskAction注解,可以让方法在gradle的执行阶段去执行。
* doFirst其实就是在外部为@TaskAction的最前面添加执行逻辑。
* 而doLast则是在外部为@TaskAction的最后面添加执行逻辑。
*/
@TaskAction
void doAction() {
updateInfo()
}
private void updateInfo() {
// 获取gradle脚本中配置的参数
def versionCodeMsg = project.extensions.releaseInfo.versionCode
def versionNameMsg = project.extensions.releaseInfo.versionName
def versionInfoMsg = project.extensions.releaseInfo.versionInfo
def fileName = project.extensions.releaseInfo.fileName
// 创建xml文件
def file = project.file(fileName)
if (file != null && !file.exists()) {
file.createNewFile()
}
// 创建写入xml数据所需要的类。
def sw = new StringWriter();
def xmlBuilder = new MarkupBuilder(sw)
// 若xml文件中没有内容,就多创建一个realease节点,并写入xml数据
if (file.text != null && file.text.size() <= 0) {
xmlBuilder.releases {
release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
}
}
file.withWriter { writer ->
writer.append(sw.toString())
}
} else { // 若xml文件中已经有内容,则在原来的内容上追加。
xmlBuilder.release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
}
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { String line, int index ->
if (index != lengths) {
writer.append(line + '\r\n')
} else if (index == lengths) {
writer.append(sw.toString() + '\r\n')
writer.append(line + '\r\n')
}
}
}
}
}
}
与创建扩展属性一样,扩展Task也需要在project中创建注入。
/**
* 自定义Gradle插件
*/
class GradleStudyPlugin implements Plugin {
/**
* 插件引入时要执行的方法
* @param project 引入当前插件的project
*/
@Override
void apply(Project project) {
// 创建扩展属性
// 这样就可以在gradle脚本中,通过releaseInfo闭包来完成ReleaseInfoExtension的初始化。
project.extensions.create("releaseInfo", ReleaseInfoExtension)
// 创建Task
project.tasks.create("updateReleaseInfo", ReleaseInfoTask)
}
}
再次Async工程之后,就可以在Idea的gradle标签里看到自定义好的Task了。
以上就是自定义gradle插件的核心内容了,但是,这种在工程下直接创建buildSrc目录编写的插件,只能对当前工程可见,所以,如果需要将我们自定义好的grdle插件被其他工程所使用,则需要单独创建一个库工程,并创建如buildSrc目录下所有的文件,最后上传maven仓库即可,这部分可自行百度了解。
译者序 | Gradle Android插件用户指南翻译
Manipulation tasks(操作task) | Gradle Android插件用户指南翻译
自定义Apk输出位置:
this.afterEvaluate {
this.android.applicationVariants.all { variant ->
def output = variant.outpus.first() // 获取变体输出文件(outputs返回是一个集合,但只有一个元素,即输出apk的file)
def apkName = "app-${variant.baseName}-${variant.versionName}.apk"
output.outputFile = new File(output.outputFile.parent, apkName)
}
}
variant.baseName : baidu-release
variant.name : baiduRelease