Gradle自动构建化基础
Gradle可谓是众多学习安卓工作者的一块心病,每次项目基本上只用到引用第三方库和打包用到,其余就再不关心了,遇到bug也是一顿百度解决,但也只能治标不治本,今天就带大家一起走起Gradle,看看Gradle到底是个什么玩意,能够让我们如此头痛。
因为Gradle的知识点相对来说是比较杂乱的,并且为了针对了解Gradle不同程度的读者来更好找到自己想要的部分,本文分以下俩部分展开
-
Gradle组成
简单介绍Gradle一般组成作用和用法,初学者学习良药。
-
Gradle扩展
Gradle自动构建化基础,充分使用Gradle的功能,Gradle提升必备。
在介绍Gradle的作用之前,首先介绍一下Gradle是什么。
Gradle是构建工具,基于Groovy语言,用来帮助我们构建app的,构建包括编译、打包等过程。
-
Gradle组成
1.Project与Task
在Gradle中,每一个待构建的工程是一个Project,构建一个Project需要执行一系列Task,比如编译、打包这些构建过程的子过程都对应着一个Task。具体来说,一个apk文件的构建包含以下Task:Java源码编译、资源文件编译、Lint检查、打包以生成最终的apk文件等等。
这样说可能同学们不太直观,下面给张图
![](https://img.haomeiwen.com/i3481369/774bc33c64740a66.png)
我们时常切换这个选项,却没有把它和Gradle联系到一起,再看下我们的Task
![](https://img.haomeiwen.com/i3481369/34826d3b29ce7aa2.png)
可以看到
android
,build
,help
等Task,大部分是系统给我们定制好的,当然我们也可以添加,有人说Task作用是什么呢,Task就是指一个个任务,包括Java源码编译、资源文件编译、Lint检查、打包以生成最终的apk文件等等,我们可以尝试点击图中的assemble,等一会就会发现会生成我们的apk,所以说我们打包apk的过程实际上就是执行一个个Task。
2.Gradle配置文件
每个项目和module都会配置一个Gradle,如下图
![](https://img.haomeiwen.com/i3481369/049c1de5cf7b28fa.png)
一个负责Project,一个负责Module。
- gradle.properties
从它的名字可以看出,这个文件中定义了一系列“属性”,实际上,这个文件中定义了一系列供build.gradle
使用的常量,比如keystore的存储路径、keyalias等等,如下:
gradle.properties配置图
填好之后,同步刷新一下Gradle(右上角Sync Now),然后在我们Module中就可以使用了,如下:
signingConfigs {
release {
keyAlias KEY_ALIAS
keyPassword KEY_PASSWORD
storeFile rootProject.file(STORE_FILE)
storePassword STORE_PASSWORD
}
}
如果使用到了数字怎么办呢,我们在使用的时候可以这样
defaultConfig {
applicationId "cn.isimpo.distinguish"
minSdkVersion 15
targetSdkVersion 29
versionCode Integer.parseInt(VERSION_CODE)
versionName "1.0"
}
VERSION_CODE我们事先在gradle.properties
中赋值为1或者其他数字即可。
-
gradlew与gradlew.bat
gradlew为Linux下的shell脚本,gradlew.bat是Windows下的批处理文件。gradlew是gradle wrapper的缩写,也就是说它对gradle的命令进行了包装,比如我们进入到指定Module目录并执行“./gradlew assemble”即可完成对当前Module的构建,和我们直接点击上图展示的Task中的assemble效果一致。 -
local.properties
local.properties
用于定义本地sdk和ndk的路径,当然,也可以定义一些我们想要的字段,比如
# Location of the SDK. This is only used by Gradle.
# header note.
sdk.dir=/Users/simpo/kaifa/android/sdk
KEY_ALIAS=key_alias
KEY_PASSWORD=key_password
STORE_FILE=key/dianqu.keystore
STORE_PASSWORD=store_password
然后看一下在Module中的使用
signingConfigs {
//加载资源
Properties properties = new Properties()
InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() ;
properties.load( inputStream )
//读取字段
def key_keyAlias = properties.getProperty( 'KEY_ALIAS' )
def key_keyPassword = properties.getProperty( 'KEY_PASSWORD' )
def key_storePassword = properties.getProperty( 'STORE_PASSWORD' )
//读取文件
def sdkDir = properties.getProperty('STORE_FILE')
release {
keyAlias key_keyAlias
keyPassword key_keyPassword
storeFile rootProject.file(sdkDir)
storePassword key_storePassword
}
}
相对于直接把变量放gradle.properties
里,这个操作还是复杂了许多。
- settings.gradle
这个我们应该算是比较熟悉的,它的作用就是需要编译哪些Module,正常我们的项目只有一个Module,所以长这样
include ':app'
如果我们添加了很多Module,并且需要编译的时候,就可以再后面接着添加,比如我们新增的Module名称为other,可以更改为:
include ':app', ':other'
如果一个Module中代码量过多,或者有过多的Module参与编译,会导致编译时间增常,往往会耽误很多时间,这就让我们学会如何代码模块化,高聚合,低耦合,从而每次编译只需要编译必须的模块,避免徒增多余的编译时间。
- build.gradle
正常情况下,我们的项目中只有俩个这样的问题,一个在项目目录下,一个在Module目录下。
先看project下的build.gradle
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:*'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
1.buildscript:配置存储库和Gradle本身的依赖,一般此处无需我们修改。
repositories:配置Gradle的存储库,一般有jcenter(),maven();
dependencies:配置了Gradle需要使用的依赖;
classpath:buildscript所需的插件和依赖;
2.allprojects:配置存储库和项目中所有模块(如第三方插件)使用的依赖项或库;
3.task:执行的工作单元,此处是项目clean的时候删除buildDir文件夹。
再看app这个Module下的build.gralde。
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.show"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
1.apply:标明此module的类型。
apply plugin: 'com.android.application' // app应用程序
apply plugin: 'com.android.library' // 库
apply from: 'config.gradle’'//用于给本地文件系统提供路径或到远程位置的URL的脚本插件。
再介绍一下其余参数的含义
android android的配置
compileSdkVersion 编译应用使用的sdk版本
apllicationId 包名
minSdkVersion 支持的最小SDK版本,即在小于此版本的系统上不可使用
targertSdkVersion 目标版本,应用已兼容从minSdkVersion至tartgetSdkVersion之间所有api的变化
versionCode 版本号;只能为整型
versionName 版本名;字符串类型
testInstrumentationRunner 测试框架
buildType 指定生成安装文件的相关配置
release 正式版本配置
minifyEnabled 设置是否开启混淆模式,如果开启,则使用
proguardFiles 属性指定的混淆文件
proguardFiles 添加混淆规则的文件,用于提高反编译阅读难度和降低apk大小
dependencies 依赖的包、模块等;项目需要使用的库、包都要在此处进行关联
implementation 远程依赖的库
testImplementation 测试依赖的库
androidTestImplementation:测试用例库
dependencies 用于引用三方库
好了,到这里Gradle的组成就算结束了。
-
Gradle扩展
当我们熟悉了Gradle的一些基本功能配置之后,我们就可以尝试用Gradle做一些更加智能
的事情,首先我们要知道Gradle和我们的程序是一样的,是由上往下执行的,所以被引用的对象一定要写在引用对象的前面,比如buildTypes
要引用signingConfigs
中的对象,buildTypes
必须要写在signingConfigs
的下面,好,下面看看我们的Gradle还有哪些有趣的东西。
- def
def可以用来声明我们的对象,我们使用的时候写在Module中任何一个地方都可以,看看有哪些用法。
//声明数字
def version = 1
//声明纯字符串
def s1 = 'example'
//声明的字符串中可携带参数,比如引用上面的version,形式为 ${}
def s2 = "gradle version is ${version}"
//可换行的字符串
def s3 = '''my name
is imooc'''
还可以创造数组
声明集合对象
def list = new ArrayList()
或
def list = [1, 2, 3, 4, 5]
//添加集合对象
list.add(1)
//移除集合对象
list.remove(1)
事实上我们def定义后,刷新Gradle,再使用list的时候,可调用的方法都会提示出来,有需要的可以自己选择,如下图
![](https://img.haomeiwen.com/i3481369/c4d217201b50c67b.png)
基本和在java中的集合对象没有区别,这是不是更让大家有一种对Gradle的亲近的感觉,其实和我们的java代码没啥子区别嘛。
还可以创造集合
def number = [one: '1',two: '2']
上面展示了2个key和2个value,看下怎么调用
colors[one]
或
colors.one
更多关于def定义集合和Map的操作可以参考:Gradle集合操作
看下def还可以做什么
//无参闭包
def o = {
println "hello world"
}
//多参闭包
def count = {
x,y->
println "hello world ${x} + ${y}"
}
//定义方法,包含闭包类型的参数
def methodone(Closure closure){
}
//定义方法,包含字符串类型的参数
def methodtwo(String type){
}
Gradle里面的闭包我们可以理解为一个代码块,或理解成一个匿名函数,但是可以传参进去,我们可以把闭包当成参数传给方法,比如上面的闭包和方法可以组合,如下
def count = {
x,y->
println "hello world ${x} + ${y}"
}
//定义方法,包含闭包类型的参数
def methodone(Closure closure){
closure("我是X", "我是Y")
}
methodone(count)
如果count中没有定义参数,会有一个默认值it代表当前的count,闭包对我们来说算是比较新奇的一种用法了,当然我们的def定义的方法也是可以有返回值的并且被其他变量使用,同学们可以自行尝试。
想知道更多闭包的操作,请移步:更多闭包操作
需要注意def的声明中不能引用def的变量,需要知道详情的移步:
def中不能引用def
- ext
ext和def是很像的,但是这里说下区别
ext属性可以伴随对应的
ExtensionAware
对象在构建的过程中被其他对象访问,例如你在rootProject中声明的ext中添加的内容,就可以在任何能获取到rootProject的地方访问这些属性,而如果只在rootProject/build.gradle中用def来声明这些变量,那么这些变量除了在这个文件里面访问之外,其他任何地方都没办法访问。
这是因为在build.gradle中直接定义的属性,只是作为gradle构建的生命周期中的Configuration
阶段的局部变量而已(参见Gradle的生命周期),而往ext属性中写入变量,则可以在整个构建的生命周期都访问到那些变量。
此外要注意,ext属性是属于拥有他的相应的对象的,比如Project对象,因此只要能访问到对应的Project对象,就能访问到对应的ext属性
再看下ext的用法
//定义代码块,在android的rootProject的build.gradle中
ext {
compileSdkVersion = 25
buildToolsVersion = "26.0.0"
}
//然后引用
rootProject.ext.compileSdkVersion
rootProject.ext.buildToolsVersion
我们还可以自己建一个Gradle文件,比如叫config.gradle,放我们的ext定义的参数,如下
ext {
//创建了一个名为android的,类型为map的变量,groovy中可以用[]来创建map类型。那么就是一个map下面又创建了一个map,且名字叫做android。
android = [
compileSdkVersion: 23,
buildToolsVersion: "23.0.2",
minSdkVersion : 14,
targetSdkVersion : 22,
]
dependencies = [
appcompatV7': 'com.android.support:appcompat-v7:23.2.1',
design : 'com.android.support:design:23.2.1'
]
}
想使用这个Gradle里面的ext需要添加引用
apply from: "config.gradle"
然后使用
android {
compileSdkVersion rootProject.ext.android.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
...
minSdkVersion rootProject.ext.android.minSdkVersion
targetSdkVersion rootProject.ext.android.targetSdkVersion
...
}
...
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile rootProject.ext.dependencies.appcompatV7
compile rootProject.ext.dependencies.design
}
同样的,ext也可以创建闭包
ext.method1 = {
}
ext.method2 = { x, y ->
}
调用起来也很方便
rootProject.ext.method1()
rootProject.ext.method2("1","2")
- manifestPlaceholders
manifestPlaceholders的作用是替换manifest中的资源,我们可以在Gradle中声明变量,然后在manifest中使用,比如多渠道打包的时候,我们显示不同的app名称和图标,可以通过下面实现
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
}
productFlavors {
other {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
manifestPlaceholders = [app_name: "其他正式包",app_icon:"@mipmap/release"]
}
debug{
manifestPlaceholders = [app_name: "其他测试包",app_icon:"@mipmap/debug"]
}
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
我们在android里添加里productFlavors,并且可以分别指定release和debug打包,这里我只写了app_name和app_icon,如果想要更多,可以接着在括号里面追加,写好后,刷新Gradle,然后我们看下manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="cn.isimpo.distinguish">
<application
android:icon="${app_icon}"
android:label="${app_name}"
android:roundIcon="${app_icon}">
...
</application>
</manifest>
只需要用${app_icon}和${app_name}就可以引用我们刚刚Gradle中的资源,等我们打包的时候会在build/output/apk下找到我们渠道名为other的渠道包,一个是debug,一个是release,当然如果想要添加更多的渠道包也是可以的,如下
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
}
productFlavors {
other {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
manifestPlaceholders = [app_name: "其他正式包",app_icon:"@mipmap/release"]
}
debug{
manifestPlaceholders = [app_name: "其他测试包",app_icon:"@mipmap/debug"]
}
}
}
othertwo{
}
otherthree{
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
这里我只写了俩个空白设置的渠道包,如果想要增加配置,模仿第一个即可,当然我们也可以指定不同渠道的更多定制信息,如下
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
}
productFlavors {
other {
applicationId "cn.example.show"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
manifestPlaceholders = [app_name: "其他正式包",app_icon:"@mipmap/release"]
}
debug{
manifestPlaceholders = [app_name: "其他测试包",app_icon:"@mipmap/debug"]
}
}
}
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
}
- buildConfigField
buildConfigField可以用于生成我们需要的属性,分三个参数
buildConfigField(数据类型,字段名称,字段值)
数据类型就是我们的String,int等,比如我们想在Module中建一个名叫BASE_URL的字段,就可以这么使用
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "BASE_URL", "\"release\"")
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "BASE_URL", "\"debug\"")
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
可以看到正式包和测试包我们分别打了一个服务器地址(这里用debug和release代替了),写好后,项目rebuild一下,然后我们就可以在我们的java代码中使用,如下
public class BaseConstant {
public static final String BASE_URL = BuildConfig.BASE_URL;
}
那么当我们打不同的包时,这个生成的BuildConfig.BASE_URL也是不一样的,解决了测试和正式服务器的区分。当然这种方式生成的变量也可以给build.gradle自己使用,但是使用之前我们一般会添加一个判断,如下
ext {
base_url = "adress"
}
def getUrl() {
return hasProperty("BASE_URL") ? BASE_URL : ext.base_url
}
hasProperty是Gradle提供给我们判断本地配置的方法,我们需要使用hasProperty判断是否已经存在BASE_URL这个字段,如果没有的就通过其他方式获取,细心的同学可能注意的release和debug两边用还添加了转义字符和",就是因为是字符串,如果是数字或布尔值则不需要。
- buildDir
buildDir这个其实是Gradle提供的一个方法,我们点击过去会发现是getBuildDir()这个方法,这个方法返回的就是我们build的路径,通常我们会利用这个参数来修改我们生成渠道包的一些设置。
未完待续...