hi各位小伙伴,上一章《在飞Android Gradle实战》之核心模块Task3 介绍了Gradle核心模块Task的相关内容。
Task 是真正执行逻辑的角色,大到源码编译,小到copy功能,最后执行者都是task。重点讲了task的创建、查找、以及如何指定task的输入输出、执行顺序、它的依赖。而通过指定task的执行顺序和依赖,可以将我们自己的task插入到一些构建过程中去,可以完成特定的功能等等。
前言:
了解完核心Task,还需要了解另一个核心内容就是Plugin,同时如何来自定义一个Plugin插件。
还是一样的风格,只讲实战中经常用到的内容,让你快速入门和使用,加上我自己的分析让你更加清楚的了解这些内容。至于基础知识请自己学习~。
本章难度:简单 普通 困难
本章重要程度:普通 重要 核心
这章主要说下Gradle自定义Plugin的相关实战经验。主要内容还有Gradle Setting、SourceSet、自定义Plugin、Android插件对Plguin的扩展等等,大体内容如下:
一:Settings
Android开发中可能不大使用到Settings这个类,但是settings.gradle这个文件作为Andorid Rd我们会经常看到。他俩有什么关系呢?
Settings这个类如果要被gradle初始化,完全是settings.gradle中的配置内容的功能。完成对Settings类的初始化首先是配置settings.gradle文件。Settings类的核心作用是决定我们工程中哪些module是要被gradle处理的。
include ':ToolboxSample'
include():引入子工程名字叫ToolboxSample,只有加入了这句话我们项目中ToolboxSample文件夹才会被gradle当成一个工程去处理。
经验:
Settings类,对于开发人员来说是简单的,我们一般只需要配置内部的include。但是它的作用并不简单,因为它自己就占用了一个Gradle的生命周期那就是初始化阶段。所以我们说的gradle的初始化阶段,完全就是在执行settings.gradle文件中的内容。通过settings.gradle中的内容决定我们到底有多少子工程需要被我们处理。
二:SourceSet
为什么我们的gradle知道从我们的src/main/java目录去读取我们的源码呢?就是通过我们的sourceSet这个类做到的。
SourceSet这个类中决定了我们所有源码、资源、第三方库等要存放的位置。那我们创建新项目的时候并没有指定SrouceSet的目录,那它是怎么拿到的呢?这是因为Gradle内部的规则 ‘约定>配置’,就是说只要我们没有主动做修改那么优先默认配置。 Gradle默认就是从java目录下获取源码、从res目录下获取资源进行编译。所以这就是我们SourceSet的关键作用,就是管理我们的源码、资源、库等存放的位置。
AndroidSourceSet:因为我是android开发人员,所以我们看的是AndroidSourceSet,如果你是java开发人员,那么就是JavaSourceSet的源码。
通过看源码看到里面有java、mainfest、res、assets、jni、jniLibs等设置方法,说明这些内容文件夹我们都是可以去修改的。
举例:
1.修改jnilib下的so库。使用app->libs文件夹下寻找so文件,而不是在默认的jnilibs下面。
2.将我们的res文件也分包来管理。
sourceSets{
main{
jniLibs.srcDirs=['libs']//1.修改so库的存放位置改为libs文件夹
}
//2.res分包管理
main {//同理也可以设置资源文件,添加 res-ad,res-player。
res.srcDirs = ['src/main/res', 'src/main/res-ad', 'src/main/res-player']
}
}
经验:
SourceSets是可以调动多次的,所以上面了例子你也可以分两个SourceSets来分别处理不同的功能。SourceSets不只是在android{}闭包中可以使用,在project中也是可以进行配置的,如下:
this.sourceSets {
main{
jniLibs.srcDirs=['libs']//修改so库的存放位置改为libs文件夹
}
}
三:Plugin
Plugin可以将完成任务的所有task封装在一个Plugin中,供其他使用。比如android 插件,可以打成aar、apk等。
如何创建插件?
举例:
1.创建 自定义的名称buildSrc工程:gradle会将buildSrc变成成一个插件,内部结构与普通应用程序是一样的。但是在main中是groovy,因为我们开发plugin都是创建的groovy类。
2.build.gradle:配置sourceSet
3.创建resources文件夹
4.创建类GradleStudyPlugin1实现Plugin接口,实现apply()
class GradleStudyPlugin1 implements Plugin {
@Override
void apply(Project project) {
println 'Hello plugi....'+project.name
//创建扩展属性
project.extensions.create('dapReleaseInfo1',
ReleaseInfoExtension1)
//创建Task
project.tasks.create('dapReleaseInfoTask1',
ReleaseInfoTask1)
}
}
经验:
GradleStudyPlugin1其实就是一个自定义插件了,apply()中参数project,就是引入了当前这个自定义插件对应的project。
5.声明别人如何使用我们的插件
别人如何引用我们的插件呢:就是com.dap.gradle.study.property这个文件中的‘com.dap.gradle.study’
6.App中使用自定义插件
SyncNow之后看控制台
看Android Studio的Terminal,如果打印出你自定义的plugin中的内容,那么证明你的自定义Plugin运行成功
四:自定义插件实战:将之前我们的写、读xml的两个Task功能封装成一个自定义插件
/**
* 与自定义PLugin进行参数传递
*/
class ReleaseInfoExtension1 {
String versionCode
String versionName
String versionInfo
String fileName
ReleaseInfoExtension1(){}
@Override
String toString() {
"""| versionCode = ${versionCode}
| versionName = ${versionName}
| versionInfo = ${versionInfo}
| fileName = ${fileName}
""".stripMargin()
}
}
此类与java中的bean(实体类)同理。
class GradleStudyPlugin1 implements Plugin {
@Override
void apply(Project project) {
println 'Hello plugi....'+project.name
//创建扩展属性
project.extensions.create('dapReleaseInfo1',
ReleaseInfoExtension1)
//创建Task
project.tasks.create('dapReleaseInfoTask1',
ReleaseInfoTask1)
}
}
使用project.extensions.crate('')创建,第一个参数:key 第二个参数:就是有哪些value值,使用ReleaseInfoExtension1中的变量。这样外部就可以使用dapReleaseInfo1这个闭包来完成RelaseInfoExtension1这个类的初始化。
App下面的build.gradle使用,设置参数及数据。
下一步自定义一个Task,里面实现我们的write、read具体业务。
//自定义Task
class ReleaseInfoTask1 extends DefaultTask {
ReleaseInfoTask1() {
group = 'dap'
description = 'update the release info'
}
@TaskAction
void doAction() {
updateInfo()
}
//将extension类信息写入指定文件中
private void updateInfo() {
//获取将要写入的信息
String versionCodeMsg = project.extensions.
dapReleaseInfo1.versionCode
String versionNameMsg = project.extensions.
dapReleaseInfo1.versionName
String versionInfoMsg = project.extensions.
dapReleaseInfo1.versionInfo
String fileName = project.extensions.
dapReleaseInfo1.fileName
def file = project.file(fileName)
//将实体对象写入到xml文件中
def sw = new StringWriter()
def xmlBuilder = new MarkupBuilder(sw)
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 {
//已有其它版本内容
xmlBuilder.release {
versionCode(versionCodeMsg)
versionName(versionNameMsg)
versionInfo(versionInfoMsg)
}
//插入到最后一行前面
def lines = file.readLines()
def lengths = lines.size() - 1
file.withWriter { writer ->
lines.eachWithIndex { line, index ->
if (index != lengths) {
writer.append(line + '\r\n')
} else if (index == lengths) {
writer.append('\r\r\n' + sw.toString() + '\r\n')
writer.append(lines.get(lengths))
}
}
}
}
}
}
运行
经验:
为什么继承DefaultTask?因为我们所有task默认是继承这个Task的。这个Task里的内容就是我们的核心业务逻辑,实现版本信息的维护。@TaskAction被这个注解的方法,可以将这个方法放到我们的执行阶段去执行类似doLast{}闭包。
updateInfo()中是我们的业务逻辑。具体内容可以关注前面的Task篇的内容 《在飞Android Gradle实战》之核心模块Task3。
五:Android插件扩展
Android对Gradle的扩展功能很多,只说一些我用到过的一些内容。
this.android{}这个闭包中就是android提供的扩展内容,具体提供的扩展方法,可以在BaseExtension这个类中查看。
这里面的属性都可以在android{}闭包中使用,具体API可以自己研究。
BaseVariant类中是Andorid插件提供的所有Task。
Android中有三种变体(Variant),分别是applicationVariant(只使用与app plugin)、libraryVariant(只适用于library plugin)、testVaraint
可参考:http://avatarqing.github.io/Gradle-Plugin-User-Guide-Chinese-Verision/advanced_build_customization/manipulation_taskstask.html
举例:name与baseName的区别
this.afterEvaluate {
this.android.applicationVariants.all { variant ->
def name = variant.name
def baseName = variant.baseName
println "android.applicationVariantsName:${name}"
println "android.applicationVariantsBaseName:${baseName}"
variant.outputs.all {
def apkName = "app-${variant.baseName}-${variant.versionName}.apk"
outputFileName = apkName
println outputFileName
}
def manifest = variant.checkManifest
println "manifest:${manifest.name}"
}
}
经验:
name结果是xxxDebug,而baseName结果是xxx-debug。
其他方法,比如variant.buildType()获取buildType varinant.flavorName()获取flavor的名字 varinant.signingConfig()签名等等。所以说build.gradle中对 android{}的配置就是对变体(variant)的配置。
varinat也可以获取到task例如variant.checkManaifet,获取到 结果checkXxxxDebugManifest这个Task。
gradle总结:
gradle生命周期:
只有了解了gradle的生命周期才能写出正确的脚本代码。初始化、配置、执行。初始化阶段gradle会完成所有工程的初始化,决定我们的项目有多少个子项目 这个阶段重点就是解析setting.gradle文件,初始化阶段完毕就是配置阶段,build.gradle中的代码大部分都是执行在配置阶段的,配置阶段执行完之后就是运行阶段,在 运行阶段段真正执行task中的逻辑,只有知道这三个阶段的作用,才能写出正确的脚本代码,否则代码很容易执行再错误的生命周期。可参考:
核心模块project:
Project是脚本代码的入口,所有脚本代码都是写在project的实力中的,而每一个build.gradle文件就对应一个project类的实例,可以在 build.gradle文件中去定位文件 获取root工程以及管理子工程,为Project添加依赖等等。可参考:
核心模块Task:
是真正执行逻辑的角色,大到源码编译,小到copy功能,最后执行者都是task。重点讲了task的创建、查找、以及如何指定task的输入输出、执行顺序、它的依赖。而通 过指定task的执行顺序和依赖,可以将我们自己的task插入到一些构建过程中去,可以完成特定的功能。可参考:
Gradle其他模块:
包括settings、sourceset、plugin等,都是重要的。可参考:
gradle实战总结:
版本信息维护功能的开发,帮我们熟悉了task的具体实现业务流程和熟悉代码api。
告诉大家如何将自己的task挂接到生命周期的中,同时去完成我们自己的功能,
如何自定义一个插件 ,plugin其实就是多个task的一个集合
Ps:我是在飞~~,只有空闲时间更新博客,所以本系列偏快速入门和个人经验分享。主要讲实战中经常用到和我认为重要的内容。所以文章尽量简短,敬请谅解,希望我的博客对你有帮助!本系列暂定阅读者已经有groovy基本知识,如果需要我来说下groovy内容也可以评论中提出,后续单开一章带领大家简单入门下Groovy。
gradle系列文章本章是最后一篇,谢谢大家的阅读,祝各位工作顺利!
年后到现在一个多月的时间紧紧巴巴的,但还是完成《gradle实战系列》。按照计划,后续将是《区块链系列》的实战、经验内容分享~
有问题可联系q:1660380990邮箱[email protected]