概述
Gradle
是一個基於Apache Ant
和Apache Maven
概念的項目自動化建構工具。它使用一種基於Groovy
的特定領域語言來聲明項目設置,而不是傳統的XML。當前其支持的語言限於Java、Groovy和Scala,計劃未來將支持更多的語言。
Gradle可以做哪些事呢
差異管理
多渠道打包,根據渠道的不同實現差異化(例如,不同的簽名文件,不同的icon,不同的服務器地址)等。
依賴管理
我們的應用可以依賴不同的jar
, library
. 你當然可以通過將.jar/library工程下載到本地再copy到你的工程中。但是隨着依賴增加還有各種更新,維護起來複雜性可想而知。有一個中央倉庫的東西,在這個倉庫裏你可以找到所有你能想要的依賴包。而你所做的只需要指定一下座標即可。例如:
compile 'com.squareup.picasso:picasso:2.3.3'
通過這種方式,我們可以很方便的實現依賴的裝載卸載。
項目部署
自動將你的輸出(.jar,.apk,.war…)上傳到指定倉庫,自動部署…
Gradle學習
學習Gradle
,需要學習,groovy
語言,Gradle DSL
學習,Android Plugin DSL
學習,Gradle task
學習。
groovy語言
Gradle
基於groovy
語言,groovy
與java
都是基於jvm
,理解相對容易些,而且日常開發不用學習那麼多 教程。
Gradle DSL
常用的 build.gradle
, Gradle DSL 傳送門
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.novoda:bintray-release:0.4.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
maven {url 'https://dl.bintray.com/calvinning/maven'}
}
}
那麼buildscript
中的 repositories 和allprojects
的 repositories 的作用和區別是什麼呢?
1、 buildscript
裏是gradle腳本執行所需依賴,分別是對應的maven庫和插件
2、 allprojects
裏是項目本身需要的依賴,比如我現在要依賴我自己maven庫的toastutils庫,那麼我應該將maven {url 'https://dl.bintray.com/calvinning/maven'}
寫在這裏,而不是buildscript中,不然找不到。
ext
屬性
ext
定義
ext {
compileSdkVersion = 25
buildToolsVersion = "26.0.0"
minSdkVersion = 14
targetSdkVersion = 22
appcompatV7 = "com.android.support:appcompat-v7:$androidSupportVersion"
//建了一個map,且名字叫做android。
android = [
compileSdkVersion: 23,
buildToolsVersion: "23.0.2",
minSdkVersion : 14,
targetSdkVersion : 22,
]
}
根據ext
屬性的官方文檔,ext屬性是ExtensionAware
類型的一個特殊的屬性,本質是一個Map類型的變量,
ExtentionAware
接口的實現類爲Project, Settings, Task, SourceSet等,ExtentionAware
可以在運行時擴充屬性,而這裏的ext,就是裏面的一個特殊的屬性而已。
ext
使用
訪問變量 通過rootProject.ext.compileSdkVersion
方式。對於數組類型rootProject.ext.android[compileSdkVersion]
這種方式。
使用ext
屬性的優勢。ext
屬性可以伴隨對應的ExtensionAware對象在構建的過程中被其他對象訪問,例如你在rootProject
中聲明的ext
中添加的內容,就可以在任何能獲取到rootProject
的地方訪問這些屬性,而如果只在rootProject/build.gradle
中用def
來聲明這些變量,那麼這些變量除了在這個文件裏面訪問之外,其他任何地方都沒辦法訪問。
注意,如果ext是自己定義的gradle文件,如(denpendies.gradle, config.gradle)需要自己手動導入,可以在根‘build.gradle’的buildscript中添加,
apply from: 'dependencies.gradle'
gradle.properties
文件
詳細介紹,傳送門
gradle.properties
裏面定義的屬性是全局的,可以在各個模塊的build.gradle
裏面直接引用. 且可以在java
代碼中訪問。
注意:在gradle.properties中定義的屬性默認是
String
類型的,如果需要int
類型,需要添加XXX as int
後綴。
第一步,在gradle.properties 定義,如下:
# Java 代碼使用
DEFAULT_NICK_NAME=maolegemi
DEFAULT_NUMBER=10086
# xml文件調用
USER_NAME=wangdandan
TEXT_SIZE=20sp
TEXT_COLOR=#ef5350
第二步, 在build.gradle
中配置
android {
...
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug{
// Java代碼調用, 就會在BuildConfig.java 生成文件,注入這邊變量,見下面java使用
buildConfigField "String", "defaultNickName", DEFAULT_NICK_NAME
buildConfigField "Integer", "defaultNumber", DEFAULT_NUMBER
// xml佈局文件調用,就會在gradleResValues.xml 注入這些變量。在代碼中,就可以訪問
resValue "string", "user_name", "${USER_NAME}"
resValue "dimen", "text_size", "${TEXT_SIZE}"
resValue "color", "text_color", "${TEXT_COLOR}"
}
}
}
第三步,在代碼中使用(引用 BuildConfig.java 和 gradleResValues.xml)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println("defaultNickName:" + BuildConfig.defaultNickName);
System.out.println("defaultNickName.type:" + BuildConfig.defaultNickName.getClass().getSimpleName());
}
Android Plugin DSL
常用的build.gradle
, Android Plugin DSL傳送門
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.joker.cliptest"
minSdkVersion 21
// ...
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
// ...
}
在android閉包下,也可以指定代碼目錄。
sourceSets {
main {
// 指定so庫的存放位置
jniLibs.srcDirs = ["libs"]
//指定資源文件目錄
res.srcDirs = ["src/main/res", "src/main/res-play", "src/main/res-shop"]
}
}
多渠道打包
productFlavors
也是android plugin dsl的內容,關於多渠道打包,這裏不單獨介紹,看這篇內容就可以了。傳送門
//多渠道打包,在此配置
productFlavors {
xiaomi {
resValue("string", "app_name", "小米ktLearn")
}
huawei {
resValue("string", "app_name", "華爲ktLearn")
}
}
Gradle 構建生命週期
Gradle
的構建過程分爲三部分: 初始化階段、配置階段 和 執行階段。其構建流程如下圖所示:
初始化階段
在這個階段中,會讀取根工程中的 setting.gradle 中的 include 信息,確定有多少工程加入構建,然後,會爲每一個項目(build.gradle 腳本文件)創建一個個與之對應的 Project 實例,最終形成一個項目的層次結構。
此外,我們也可以在settings.gradle
文件中,指定其他project的位置,這樣就可以將其他外部工程的module導入到當前的工程之中了。
if(useLocal) {
File projectFile = new File('../picBook/app')
include ':picBook'
project(':picBook').projectDir = projectFile
}
配置階段
執行各項目下的 build.gradle 腳本,完成 Project 的配置,與此同時,會構造 Task 任務依賴關係圖以便在執行階段按照依賴關係執行 Task. 而在配置階段執行的代碼通常來說都會包括以下三個部分的內容,如下所示:
- build.gralde 中的各種語句。
- 閉包。
- Task 中的配置段語句。
注意,執行任何 Gradle 命令,在初始化階段和配置階段的代碼都會被執行。
執行階段
Gradle 根據各個任務 Task 的依賴關係來創建一個有向無環圖。Gradle 構建系統通過調用 gradle <任務名> 來執行相應的各個任務。
Project
Project API
對應org.gradle.api
包下,Project.java
的方法。
getAllprojects
獲取當前工程下所有的project
實例(含當前工程), 在不同的build.gradle
下調用返回不一樣。在根build.gradle
下會返回所有的project
.
getSubprojects
獲取當前工程下所有子工程的project
實例,返回的set
除了不包含當前工程,其他同getAllprojects
.
getParent
獲取當前project
的父類.如果我們在根工程中使用它,返回爲null
.
getRootProject
返回根工程的project
實例。
project
指定工程的實例。有幾種重載方法。
我們常用的
implementation project(':xbase')
還有一種加閉包的重載,可對工程進行配置。
//擴展ext方法
ext.getRealDep = { projectName ->
def aarName = rootProject.ext.project[projectName]
if (aarName) {
//如果在ext.project 配置了,使用配置的倉庫路徑
println "\n use aar [$projectName] "
return aarName
} else {
//沒有在ext.project 配置,則使用當前路徑的工程
Project project = project(":" + projectName)
if (project != null && project.getProjectDir() != null
&& project.getProjectDir().exists()) {
println "\nready to handle project [$projectName] real dependency"
return project
}
}
throw IllegalArgumentException("no project found for :$projectName")
}
allprojects
用於配置當前project
及其旗下的每一個子project
.
allprojects {
repositories {
maven { url "https://jitpack.io" }
maven { url 'https://repo.rdc.aliyun.com/repository/xxxx/'
//一般私有倉庫,需要驗證用戶密碼這樣設置
credentials {
username 'namexxx'
password 'pwdxxx'
}
}
maven { url 'https://dl.bintray.com/umsdk/release' }
google()
jcenter()
}
}
subprojects
統一配置當前 project 下的所有子 project
subprojects {
//當前 project 旗下的子 project 是不是庫,如果是庫纔有必要引入 publishToMaven 腳本
if (project.plugins.hasPlugin("com.android.library")) {
apply from: '../publishToMaven.gradle'
}
}
project屬性
在project.java
接口中,僅僅定義了七個屬性
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
/**
* 默認的工程構建文件名稱
*/
String DEFAULT_BUILD_FILE = "build.gradle";
/**
* 區分開 project 名字與 task 名字的符號
*/
String PATH_SEPARATOR = ":";
/**
* 默認的構建目錄名稱
*/
String DEFAULT_BUILD_DIR_NAME = "build";
String GRADLE_PROPERTIES = "gradle.properties";
String SYSTEM_PROP_PREFIX = "systemProp";
String DEFAULT_VERSION = "unspecified";
String DEFAULT_STATUS = "release";
...
}
幸運的是,Gradle 提供了 ext
關鍵字讓我們有能力去定義自身所需要的擴展屬性。有了它便可以對我們工程中的依賴進行全局配置。
見上面ext使用
和gradle.properties
下定義擴展屬性。
文件相關的API
獲取路徑
//getRootDir返回file指向根目錄
getRootDir().absolutePath
//getBuildDir返回file指向 '根目錄/build'
getBuildDir().absolutePath
//getProjectDir返回file指向 當前工程目錄, 如'根目錄/xlist'
getProjectDir().absolutePath
文件相關
//返回一個File
def mFile = file(pathxxx)
// 文件的拷貝
copy {
from file("build/outputs/apk")
into getRootProject().getBuildDir().path + "/apk/"
exclude {
//排除不需要拷貝的文件
}
rename {
//對拷貝過來的文件進行重命名
}
}
//fileTree方法
// 用法1
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 用法2
fileTree("build/outputs/apk") { FileTree fileTree ->
fileTree.visit { FileTreeElement fileTreeElement ->
println "The file is $fileTreeElement.file.name"
copy {
from fileTreeElement.file
into getRootProject().getBuildDir().path + "/apkTree/"
}
}
}
其他
關於app module下的依賴,除了可以添加依賴,也可以移除, 需要用到 exclude
,另外還有一個transive
方法,控制是否傳遞依賴。configurations
是project
中方法,他們都是類Configuration
中的方法。project
有configurations
方法。
if (local_reader.toBoolean())
configurations {
implementation {
exclude group: 'com.tdhLearn.base', module: 'core'
exclude group: 'com.tdhLearn.base', module: 'push'
transitive false
}
}
exec
執行外部命令。
Task
Gradle task
與我們是最爲緊密的。日常開發中開發者難免會進行 build/clean project
、build apk
等操作。實際上這些按鈕的底層實現都是通過 Gradle task
來完成的,只不過 IDE
使用 GUI
降低開發者們的使用門檻。
task的使用
我們可以在任意一個build.gradle
文件中定義Task
task tian {
//這段代碼執行在配置階段
println "exec tian task"
//doFirst, doLast執行在 gradle執行階段
doFirst {
println "~~~tian task start"
}
doLast {
println "~~~tian task end"
}
}
// 任務依賴 tian
task fang(dependsOn:"tian") {
doLast {
println "~~~fang task end"
}
}
執行
# 根目錄下的task
./gradlew task :tian
# 在其他module下的task
./gradlew task :app:tianThird
Task的屬性
task
組的概念,會對task
進行分組,在gradle
標籤下,我們可以看到。description
爲task
添加描述,相當於task
的註釋,在打印task
時會展示出來(如下圖)。
// group定義組,description爲task添加描述。
task tianSec(group: "TianTask", description: "this is my test task") {
println "~~~tianSec configure"
}
屬性擴展
我們也可以 使用 ext 給 task 自定義需要的屬性
task tian(group: "TianTask") {
//擴展task的屬性
ext.good = "hello tian"
}
//定義 組&描述
task tianSec(group: "TianTask") {
doLast {
//訪問tian的擴展屬性
println "~~~I can get goodValue $tian.good"
}
}
task類型
使用type屬性來直接使用一個已有的task
類型。
// 刪除根目錄下的build文件
task clean(type: Delete) {
delete rootProject.buildDir
}
// 將doc複製到 build/target目錄下
task copyDocs(type: Copy) {
form 'src/main/doc'
into 'build/target/doc'
}
// 執行時複製源文件到目標目錄,然後從目標目錄刪除所有非複製文件
task syncFile(type: Sync) {
from 'src/mian/doc'
into 'build/target/doc'
}
每個 task 都會經歷 初始化、配置、執行 這一套完整的生命週期流程。