初识Gradle
前言
开发安卓项目现在基本使用的是AS,AS为我们提供了集成的 Android 开发工具用于开发和调试项目的基础,在此基础上还提供了Gradle的构建支持,帮助我们构建app,包括编译、打包等过程。但在之前,或者在不同的开发任务中,使用的构建工具是不一样的。比如我们公司java后台就使用的maven,再早一点idea上使用的是Ant。它们的作用都是帮助我们构建项目,只是我们要学习的Gradle是结合了前两者的优点,在此基础之上做了很多改进,变得更加强大和灵活。
Gradle是一款开源的自动构建工具,它的SDL是基于Groovy语言实现,就类似我们的android的Api,java的语法。所以我们学习gradle主要学习两点1、Groovy语法 2、Gradle为我们提供的Api。
新知识就从我们熟悉的android的gradle构建开始看起。
android中的Gradle
首先在下面我们能看到几个关于Gradle的文件
- app下面的build.gradle
- wrapper下面的两个文件
- 根目录下面的build.gradle文件
- 根目录下的gradle.properties文件
- settings.gradle文件
接下来我们分别认识下这些文件。
根目录build.gradle
在项目中首先根目录中的 settings.gradle配置的是当前项目所依赖的application和library,当然配到Project中后如果application需要使用某一个library,还是需要再导一次包如:implementation project(':libraryRefresh')
,这样在这个个导包的工程中就可以使用包中的东西了。
对于根目录build.gradle来说,主要有两个作用
repositories:
添加支持的仓库
dependencies:
添加编译所需要的插件或第三方库。
形象比喻就是在dependencies中申明需要提供人民币,在repositories中申明了是哪个银行提供、中国银行还是建设银行。
这里还有区分下buildscript和allprojects
buildscript:
我们知道整个项目是由Gradle管理的,Gradle管理项目也需要使用代码的,也就是脚本。脚本的执行也是需要依赖的,那么对于脚本的依赖申明在buildscript中,比如classpath 'com.android.tools.build:gradle:3.2.0'
。
allprojects:
根据名字我们可以知道,这个依赖是针对整个Project的。当我们的application和library中所需要第三方库的时候,我们直接在它自己的build.gradle 中的dependencies{}中添加implementation导入,那么可能根目录的allprojects {
repositories {}}中添加maven { url 'https://maven.google.com' }
的仓库依赖。
buildscript {
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
//一个clean Task,用于删除根目录下的buil文件
task clean(type: Delete) {
delete rootProject.buildDir
}
app下面的build.gradle
在讲这个之前,我先贴一下之前android-同一套代码打多个APP中使用的build.gradle 。
这里讲app下面的build.gradle 我想告诉大家的是这里的build.gradle 到底可以为我们项目配置那些东西。
//添加一个groovy语言用于解析json的用的引用
import groovy.json.JsonSlurper
//声明一个android插件,拥有这个插件,才可以配置安卓环境
apply plugin: 'com.android.application'
//android项目的参数,构建android项目的所有配置都写在这里
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.cs.demo"
minSdkVersion 16
targetSdkVersion 28
versionCode 1
versionName "1.0.0"
resConfigs "zh"
// 设置MultiDex可用
multiDexEnabled true
flavorDimensions "versionCode"
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
//配置默认的app图标和名字
manifestPlaceholders = [
label : "反清",
icon : "@drawable/yzxy"
]
}
//配置编译时期release和debug要做的配置
buildTypes {
release {
//混淆文件
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
zipAlignEnabled true //zip对齐
debuggable false
signingConfig signingConfigs.release
}
debug {
//不混淆文件
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debuggable true
signingConfig signingConfigs.release //为了高德定位key鉴权正常,必须跟releas版本key保持一致
buildConfigField "String", "BASE_URL", "\"https://www.baidu.com\"" //配置debug的服务器地址
}
}
//读取集合json文件中的集合,进行循环,设置多渠道
productFlavors {
//在 gradle.properties中添加 GROUP=All
//读取channel_config下json文件
def json = file("${rootProject.projectDir.path}/channel_config/group${GROUP}.json").getText("UTF-8")
//读取app目录下的channel.json文件内容
def flavors = new JsonSlurper().parseText(json) //转换成Jsons数组对象
flavors.each { flavor ->
//循环flavors数组
def fileName = flavor.channel_name
def channelJson = file("${rootProject.projectDir.path}/channel_config/channels/${fileName}.json").getText("UTF-8")
def qudao = new JsonSlurper().parseText(channelJson) //转换成Jsons数组对象
def INDEPENDENT_URL = flavor.INDEPENDENT_URL
"${flavor.channel_name}" {//渠道名称,对应文件中的channel_name
//单独服务地址
if ("Y".equals(INDEPENDENT_URL)) {
buildConfigField "String", "BASE_URL", "\"${qudao.BASE_URL}\"" //服务器地址
}
buildConfigField "String", "COLOR_STYLE", "\"${qudao.COLOR_STYLE}\"" //主题颜色
applicationId qudao.applicationId
buildConfigField "String", "APP_NAME", "\"${qudao.label}\"" //应用名称
manifestPlaceholders = [
icon : qudao.icon,//APP要显示的ICON图标,对应文件中的icon
label : qudao.label,
channel_name: flavor.channel_name
]
}
}
}
//其实大多是时候是用于噢配置文件,对于这一段代码主要是读取某一个目录下的json文件,然后循环json文件中的集合,然后匹配替换某一个文件
sourceSets {
def json = file("${rootProject.projectDir.path}/channel_config/group${GROUP}.json").getText("UTF-8")
//读取app目录下的channel.json文件内容
def flavors = new JsonSlurper().parseText(json) //转换成Jsons数组对象
flavors.each { flavor ->
def colorstyle = flavor.COLOR_STYLE
"${flavor.channel_name}" { //渠道资源配置
if (colorstyle.equals("blue")) {
//单独指定两个资源文件,一个替换res-style-blue下目录文件,一个替换icons下文件
res.srcDirs = ["src/res-style-blue", "${rootProject.projectDir.path}/channel_config/icons/${flavor.channel_name}"]
}
if (colorstyle.equals("yellow")) {
res.srcDirs = ["src/res-style-yellow", "${rootProject.projectDir.path}/channel_config/icons/${flavor.channel_name}"]
//指定资源目录
}
}
}
}
//修改apk名
applicationVariants.all { variant ->
variant.outputs.all { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('release.apk')) {
// 输出apk名称为xxx_v1.1.1_20171005-21:23:30.apk
def fileName = "${variant.flavorName}_v${variant.versionName}_${releaseTime()}_release.apk"
outputFileName = new File(fileName)
}
if (outputFile != null && outputFile.name.endsWith('debug.apk')) {
// 输出apk名称为xxx_v1.1.1_20171005-21:23:30.apk
def fileName = "${variant.flavorName}_v${variant.versionName}_${releaseTime()}_debug.apk"
outputFileName = new File(fileName)
}
}
}
}
def releaseTime() {
return new Date().format("yyyyMMdd_HH_mm_ss", TimeZone.getTimeZone("GMT+8:00"))
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
apply
这里先介绍下apply plugin: 'com.android.application'
,
这是声明引用 com.android.application 插件,那么这个模块就是一个Android应用程序,如果是一个库,库的上面导入的那么就是apply plugin: 'com.android.library'
还有一种使用apply from
,比如apply from: this.file('config.gradle')
,这个声明在
根目录的build.gradle上面,那么我们就可以使用config.gradle中的相关内容。
buildTypes
buildTypes其实很简单,就是设置debug或release项目的时候不同配置,这个配置,其中有几点可以说一下:
- debuggable 设置当前app是debug版本还是release版本,用于当前环境判断,可以在Release版本的时候屏蔽某些功能,获取的时候关键代码
(appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0)
- signingConfig 建议在debug环境的时候设置同样的签名文件,保证在正式和测试的时候使用同样的key进行签名,避免测试包不能进行第三方鉴权
- buildConfigField 这是在BuildConfig文件下生成一个配置字段,供我们全局访问,取的时候只需要在项目中使用
BuildConfig.BASE_URL
就能获取到值很方便
productFlavors
对于设置变体,入门的可以看看给你找好的中文官网的。看完你有没有发现,如果要设置很多变体是不是要在productFlavors中写很多配置文件,多了就一大坨,渠道有变化还经常要改这个build.gradle文件,上面同一套代码打多个APP介绍的就是使用配置文件去做这个事情,再多也不怕,想详细了解的稍后可以看连接中的文章
sourceSets
sourceSets是为我们配置资源文件位置的,可以为单个的app,也可以为每一个App变体,如果我们productFlavors中有5个渠道,每个渠道的APP加载的Icon都不一样,那么你就可以使用sourceSets为每一个app配置不同的Icon。
这里首先说一下,sourceSets中根据google上面的显示可配置的是由3项,
1、 java.srcDirs = [‘other/java’] 设置java文件地址
2、 res.srcDirs = [‘other/res1’, ‘other/res2’] 设置资源文件地址
3、 manifest.srcFile ‘other/AndroidManifest.xml’ 设置AndroidManifest的地址
其中数组各种的说明可以设置多个路径
当然根据我的观察,这3种设置使用后,对于其执行的结果是不一样的。
- 如果你合并的是 java.srcDirs,那么在合并的时候,如果相同目录下有文件相同,那么就会产生文件冲突,那么你需要进行排除冲突,主要代码
exclude 'com/example/kylin/fristtest/MyTestOne.java'
,相关详细说明看这篇文章java.srcDirs合并规则 - 如果不要合并的是res.srcDirs资源文件,那么你文件是直接替换,就是说在主目录下和你定义的资源路径下都有同一个资源名称的时候,你使用了res.srcDirs重新指定路径,那么会以重新指定的为准,就是覆盖了主目录下的,但是重新指定路径里面没有有资源,那么还是以主目录下为准,也就是替换了一部分。相关参考看同一套代码打多个APP这篇博客
- 如果你要替换的是manifest.srcFile,那么也有一套规则。其实Gradle经常在做这个事,当我们项目里面有多个Library的时候,Library中又有AndroidManifest.xml文件,那么我们跑起来的时候,其实Gradle就将其他Library的AndroidManifest.xml全部合并到主app的AndroidManifest.xml中,点击AndroidManifest的Mergend Manifest就能看到所有的文件。那么文件合并的规则是什么样的呢?看google的官方中文文档,讲的很清楚合并多个清单文件
dependencies
dependencies里面配置的是我们导入的jar包,在这里面我们要掌握的是api 和compile、implementation和compile等导包的规则和区别。具体规则大家直接看Gradle的官网介绍。
还有就是在我们使用太多的jar和Library中冲突或者版本管理的问题。
如果冲突:
compile ('com.android.support:appcompat-v7:23.3.0'){
exclude module: 'support-v4'
}
使用同一的属性管理jar的版本,在android的rootProject的build.gradle中,定义如下代码块
ext {
compileSdkVersion = 25
buildToolsVersion = "26.0.0"
minSdkVersion = 14
targetSdkVersion = 22
appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
}
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
在这里主要介绍了android中我们常用的gradle知识。后面将会介绍groovy的相关语法,以及Gradle中比较重要的Project和Task相关概念。目的是让我对Gradle有大概的认识,知道写些能用到的脚本帮助我们简化工作。
Groovy与DSL
深入理解Android之Gradle
项目自动构建工具对比(Maven、Gradle、Ant)
Ant vs Maven vs Gradle构建工具介绍
这篇博客写于2019年,本来是一个系列博客,准备将后面全部写完然后发布的,但是很多原因,导致没能完成。2020年准备发掉这个博客,开始今年的学习之路。在博客草稿箱中,记录了剩下30多篇没有完成的博客,本来是想学习数据结构和算法,但是看到剩下的博客,决定有始有终,写完所有存货。