首發於個人blog xsfelvis.github.io/2019/05/02/…html
Transform相關請參考 xsfelvis.github.io/2019/05/02/…java
Gradle自己的領域對象主要有Project和Task。Project爲Task提供了執行上下文,全部的Plugin要麼向Project中添加用於配置的Property,要麼向Project中添加不一樣的Task。一個Task表示一個邏輯上較爲獨立的執行過程,好比編譯Java源代碼,拷貝文件,打包Jar文件,甚至能夠是執行一個系統命令或者調用Ant。另外,一個Task能夠讀取和設置Project的Property以完成特定的操做。 android
官網git
Groovy腳本基礎全攻略github
groovy使用徹底解析shell
gradle dsl 基礎 api
相關代碼在 github.com/xsfelvis/Gr…android-studio
自定義插件類是經過實現Plugin 接口,並將 org.gradle.api.Project做爲模板參數,其中org.gradle.api.Project的實例對象將做爲參數傳給void apply(Project project)
函數,根據官網,能夠看出Project是與Gradle交互的主接口,經過Project可使用gradle的全部特性,而且 Project與build.grale是一對一的關係。簡而言之,就是經過代碼使用Gradle,經過Project這個入口便可緩存
咱們對project的理解更多來源於項目目錄中的build.gradle
文件(由於它其實就是project對象的委託,在腳本中的配置方法都對應着Project中的API,當構建進程啓動後Gradle基於build.gradle中的配置實例化org.gradle.api.Project類,本質上能夠認爲是包含多個Task的容器,全部的Task都存放在TaskContainer中,Project
對象的類圖以下所示:bash
在build.gradle腳本文件中,咱們不只能夠對單獨project進行配置,也能夠定義project塊的共有邏輯等,參考下面的定義。
常見的例子
// 爲全部項目添加倉庫源配置
allprojects {
repositories {
jcenter()
google()
}
}
// 爲全部子項目添加mavenPublish的配置塊
subprojects {
mavenPublish {
groupId = maven.config.groupId
releaseRepo = maven.config.releaseRepo
snapshotRepo = maven.config.snapshotRepo
}
}
複製代碼
Gradle構建腳本默認的名字是build.gradle,當在shell中執行gradle命令時,Gradle會去當前目錄下尋找名字是build.gradle的文件。在Gradle中一個原子性的操做叫作task,簡單理解爲task是Gradle腳本中的最小可執行單元。
下面是task的類圖。
一個Task是由一序列Action組成的,當運行一個Task的時候,這個Task裏的Action序列會按照順序執行
task myTask1 {
doLast {
println "doLast in task1"
}
}
task myTask2 << {
println "doLast in task2"
}
//採用 Project.task(String name) 方法來建立
project.task("myTask3").doLast {
println "doLast in task3"
}
//採用 TaskContainer.create(String name) 方法來建立
project.tasks.create("myTask4").doLast {
println "doLast in task4"
}
project.tasks.create("myTask5") << {
println "doLast in task5"
}
複製代碼
目前task的動做(action)聲明主要包含兩個方法:
在 Gradle 中定義 Task 的時候,能夠指定更多的參數,以下所示:
參數名 | 含義 | 默認值 |
---|---|---|
name | task的名字 | 必須指定,不能爲空 |
type | task的父類 | 默認值爲org.gradle.api.DefaultTask |
overwrite | 是否替換已經存在的同名task | false |
group | task所屬的分組名 | null |
description | task的描述 | null |
dependsOn | task依賴的task集合 | 無 |
constructorArgs | 構造函數參數 | 無 |
gradle中任務的執行順序是不肯定的。經過task之間的依賴關係,gradle可以確保所依賴的task會被當前的task先執行。使用task的dependsOn()
方法,容許咱們爲task聲明一個或者多個task依賴。
task first{
doLast{
println("first")
}
}
task second{
doLast{
println("second")
}
}
task third{
doLast{
println("third")
}
}
task test(dependsOn:[second,first]){
doLast{
println("first")
}
}
third.dependsOn(test)
複製代碼
執行順序
> Task :app:first
> Task :app:second
> Task :app:test1
> Task :app:third
複製代碼
有copy、jar、Delete 等等 能夠參考Doc文檔
task copyFile(type: Copy) {
from 'xml'
into 'destination'
}
Gradle 中經過 task 關鍵字建立的 task,默認的父類都是 org.gradle.api.DefaultTask,這裏定義了一些 task 的默認行爲。看看下面這個例子:
//自定義Task類,必須繼承自DefaultTask
class SayHelloTask extends DefaultTask {
String msg = "default name"
int age = 18
//構造函數必須用@javax.inject.Inject註解標識
@javax.inject.Inject
SayHelloTask(int age) {
this.age = age
}
//經過@TaskAction註解來標識該Task要執行的動做
@TaskAction
void sayHello() {
println "Hello $msg ! age is ${age}"
}
}
//經過constructorArgs參數來指定構造函數的參數值
task hello1(type: SayHelloTask, constructorArgs: [30])
//經過type參數指定task的父類,能夠在配置代碼裏修改父類的屬性
task hello2(type: SayHelloTask, constructorArgs: [18]) {
//配置代碼裏修改 SayHelloTask 裏的字段 msg 的值
msg = "hjy"
}
複製代碼
執行這兩個task
> Task :hello1
Hello default name ! age is 30
> Task :hello2
Hello hjy ! age is 18
複製代碼
Gradle所說的Task是org.gradle.api.Task接口,默認實現是org.gradle.api.DefaultTask類,其類圖以下
TaskContianer 是用來管理全部的 Task 實例集合的,能夠經過 Project.getTasks() 來獲取 TaskContainer 實例。
org.gradle.api.tasks.TaskContainer接口:
//查找task
findByPath(path: String): Task
getByPath(path: String): Task
getByName(name: String): Task
withType(type: Class): TaskCollection
matching(condition: Closure): TaskCollection
//建立task
create(name: String): Task
create(name: String, configure: Closure): Task
create(name: String, type: Class): Task
create(options: Map<String, ?>): Task
create(options: Map<String, ?>, configure: Closure): Task
//當task被加入到TaskContainer時的監聽
whenTaskAdded(action: Closure)
//當有task建立時
getTasks().whenTaskAdded { Task task ->
println "The task ${task.getName()} is added to the TaskContainer"
}
//採用create(name: String)建立
getTasks().create("task1")
//採用create(options: Map<String, ?>)建立
getTasks().create([name: "task2", group: "MyGroup", description: "這是task2描述", dependsOn: ["task1"]])
//採用create(options: Map<String, ?>, configure: Closure)建立
getTasks().create("task3", {
group "MyGroup"
setDependsOn(["task1", "task2"])
setDescription "這是task3描述"
})
複製代碼
默認狀況下,咱們常見的task都是org.gradle.api.DefaultTask
類型。可是在gradle當中有至關豐富的task類型咱們能夠直接使用。要更改task的類型,咱們能夠參考下面的示例
task createDistribution(type:Zip){
}
複製代碼
更多關於task的類型,能夠參考gradle的官方文檔
www.jianshu.com/p/cd1a78dc8…
www.ezlippi.com/blog/2015/0…
blog.csdn.net/lzyzsd/arti…
Gradle 支持一種叫作 up-to-date 檢查的功能,也就是常說的增量構建。Gradle 的 Task 會把每次運行的結果緩存下來,當下次運行時,會檢查輸出結果有沒有變動,若是沒有變動則跳過運行,這樣能夠提升 Gradle 的構建速度。
一般,一個 task 會有一些輸入(inputs)和一些輸出(outputs),task 的輸入會影響其輸出結果,以官網中的一張圖爲例:
圖中表示一個java編譯的task,它的輸入有2種,一是JDK版本號,一是源文件,它的輸出結果爲class文件,只要JSK版本號與源文件有任何變更,最終編譯出的class文件確定不一樣的。當咱們執行過一次·編譯任務以後,再次運行該task,若是發現他的輸入沒有任何改動,那麼它編譯後的結果確定也是不變的,能夠直接從緩存裏獲取輸出,這樣Gradle會標識該task爲UP-TO-DATE,從而跳過該task的執行
如何實現一個增量構建呢,至少指定一個輸入,一個輸出,Task.getInputs() 對象類型爲 TaskInputs,Task.getOutputs() 對象類型爲 TaskOuputs,從中也能夠看到inputs、outputs都支持哪些數據類型
task test1 {
//設置inputs
inputs.property("name", "hjy")
inputs.property("age", 20)
//設置outputs
outputs.file("$buildDir/test.txt")
doLast {
println "exec task task1"
}
}
task test2 {
doLast {
println "exec task task2"
}
}
//第一次的運行結果
> Task :test1
exec task task1
> Task :test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 2 executed
//第二次的運行結果
> Task :test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date
複製代碼
從結果中能夠看到,第2次運行時,test1 task 並無運行,而是被標記爲 up-to-date,而 test2 task 則每次都會運行,這就是典型的增量構建。
能夠經過task註解來實現增量構建,這是一種更加靈活方便的方式
註解名 | 屬性類型 | 描述 |
---|---|---|
@Input | 任意Serializable類型 | 一個簡單的輸入值 |
@InputFile | File | 一個輸入文件,不是目錄 |
@InputDirectory | File | 一個輸入目錄,不是文件 |
@InputFiles | Iterable | File列表,包含文件和目錄 |
@OutputFile | File | 一個輸出文件,不是目錄 |
@OutputDirectory | File | 一個輸出目錄,不是文件 |
@OutputFiles | Map<String, File>或Iterable | 輸出文件列表 |
@OutputDirectories | Map<String, File>或Iterable | 輸出目錄列表 |
class SayHelloTask extends DefaultTask {
//定義輸入
@Input
String username;
@Input
int age
//定義輸出
@OutputDirectory
File destDir;
@TaskAction
void sayHello() {
println "Hello $username ! age is $age"
}
}
task test(type: SayHelloTask) {
age = 18
username = "hjy"
destDir = file("$buildDir/test")
}
複製代碼
Gradle中不少模型類都提供了特別的屬性支持,好比Project
.在gradle內部,這些屬性會以鍵值對的形式存儲。使用ext命名空間,咱們能夠方便的添加屬性。下面的方式都是支持的:
//在project中添加一個名爲groupId的屬性
project.ext.groupId="tech.easily"
// 使用ext塊添加屬性
ext{
artifactId='EasyDependency'
config=[
key:'value'
]
}
複製代碼
值得注意的是,只有在聲明屬性的時候咱們須要使用
ext
命名空間,在使用屬性的時候,ext
命名空間是能夠省略的。
正如咱們常常在Android項目中看到的,咱們能夠在項目的根目錄下新建一個gradle.properties
文件,並在文件中定義簡單的鍵值對形式的屬性。這些屬性可以被項目中的gradle腳本所訪問。以下所示:
# gradle.properties
# 注意文件的註釋是以#開頭的
groupId=tech.easily
artifactId=EasyDependency
複製代碼
複製代碼
有的時候,咱們可能須要在代碼中動態的建立屬性文件並讀取文件中的屬性(好比自定義插件的時候),咱們可使用java.util.Properties
類。好比:
void createPropertyFile() {
def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
def defaultProps = new Properties()
if (!localPropFile.exists()) {
localPropFile.createNewFile()
defaultProps.setProperty("debuggable", 'true')
defaultProps.setProperty("groupId", GROUP)
defaultProps.setProperty("artifactId", project.name)
defaultProps.setProperty("versionName", VERSION_NAME)
defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")
} else {
localPropFile.withInputStream { stream ->
defaultProps.load(stream)
}
}
}
複製代碼
關於屬性很重要的一點是屬性是能夠繼承的。在一個項目中定義的屬性會自動的被其子項目繼承,無論咱們是用以上哪一種方式添加屬性都是適用的。
就是 Gradle 的 Extension,翻譯成中文意思就叫擴展。它的做用就是經過實現自定義的 Extension,能夠在 Gradle 腳本中增長相似 android 這樣命名空間的配置,Gradle 能夠識別這種配置,並讀取裏面的配置內容。
通常咱們經過ExtensionContainer來建立Extension,這個類跟TaskContainer命名有點相似。TaskContainer是用來建立並管理Task的,而ExtensionContainer則是用來建立並管理Extension的,經過Project的如下API能夠獲取到ExtensionContainer對象
ExtensionContainer getExtensions()
複製代碼
/先定義一個普通的java類,包含2個屬性
class Foo {
int age
String username
String toString() {
return "name = ${username}, age = ${age}"
}
}
//建立一個名爲 foo 的Extension
getExtensions().create("foo", Foo)
//配置Extension
foo {
age = 30
username = "hjy"
}
task testExt.doLast {
//能直接經過 project 獲取到自定義的 Extension
println project.foo
}
複製代碼
foo 就是咱們自定義的 Extension 了,它裏面能配置的屬性與類 Foo 中的字段是一致的,在 build.gradle 中能夠直接經過 project.foo 來訪問。每一個 Extension 實際上與某個類是相關聯的,在 build.gradle 中經過 DSL 來定義,Gradle 會識別解析並生成一個對象實例,經過該類能夠獲取咱們所配置的信息。
Project 有個擴展屬性是經過 ext 命名空間配置的,能夠看到 ext 與這裏是相似的,不一樣的是 ext 能夠配置任何鍵值對的屬性值,而這裏只能識別咱們定義的 Java 類裏的屬性值。
<T> T create(String name, Class<T> type, Object... constructionArguments)
<T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments)
複製代碼
先來看看後面這個 API 全部參數的含義。
官方文檔裏還說明了一個特性,建立的 Extension 對象都默認實現了 ExtensionAware 接口,並註明出處。
示例
//父類
class Animal {
String username
int legs
Animal(String name) {
username = name
}
void setLegs(int c) {
legs = c
}
String toString() {
return "This animal is $username, it has ${legs} legs."
}
}
//子類
class Pig extends Animal {
int age
String owner
Pig(int age, String owner) {
super("Pig")
this.age = age
this.owner = owner
}
String toString() {
return super.toString() + " Its age is $age, its owner is $owner."
}
}
//建立的Extension是 暴露出來Animal 類型,建立extension名稱是name,該extension的類型是Pig,後面2個是參數
Animal aAnimal = getExtensions().create(Animal, "animal", Pig, 3, "hjy")
//建立的Extension是 Pig 類型
Pig aPig = getExtensions().create("pig", Pig, 5, "kobe")
animal {
legs = 4 //配置屬性
}
pig {
setLegs 2 //這個是方法調用,也就是 setLegs(2)
}
task testExt << {
println aAnimal
println aPig
//驗證 aPig 對象是 ExtensionAware 類型的
println "aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}"
}
複製代碼
基於前面的這個實例,咱們能夠換一種寫法以下:
getExtensions().add(Pig, "mypig", new Pig(5, "kobe"))
mypig {
username = "MyPig"
legs = 4
age = 1
}
task testExt << {
def aPig = project.getExtensions().getByName("mypig")
println aPig
}
複製代碼
Object findByName(String name)
<T> T findByType(Class<T> type)
Object getByName(String name) //找不到會拋異常
<T> T getByType(Class<T> type) //找不到會拋異常
複製代碼
相似下面這樣的配置應該隨處可見:
outer {
outerName "outer"
msg "this is a outer message."
inner {
innerName "inner"
msg "This is a inner message."
}
}
複製代碼
能夠經過下面的方式來建立
class OuterExt {
String outerName
String msg
InnerExt innerExt = new InnerExt()
void outerName(String name) {
outerName = name
}
void msg(String msg) {
this.msg = msg
}
//建立內部Extension,名稱爲方法名 inner
void inner(Action<InnerExt> action) {
action.execute(inner)
}
//建立內部Extension,名稱爲方法名 inner
void inner(Closure c) {
org.gradle.util.ConfigureUtil.configure(c, innerExt)
}
String toString() {
return "OuterExt[ name = ${outerName}, msg = ${msg}] " + innerExt
}
}
class InnerExt {
String innerName String msg void innerName(String name) {
innerName = name
}
void msg(String msg) {
this.msg = msg
}
String toString() {
return "InnerExt[ name = ${innerName}, msg = ${msg}]"
}
}
def outExt = getExtensions().create("outer", OuterExt)
outer {
outerName "outer"
msg "this is a outer message."
inner {
innerName "inner"
msg "This is a inner message."
}
}
task testExt doLast {
println outExt
}
複製代碼
關鍵在如下下面的方法
void inner(Action<InnerExt> action)
void inner(Closure c)
複製代碼
定義在outer內部的inner,Gradle 解析時本質上會調用 outer.inner(……)方法,該方法的參數是一個閉包(Script Block) 因此在類OuterExt中必須定義inner方法
Gradle Extension 的時候,說到名爲 android 的 Extension 是由 BaseExtension 這個類來實現的,裏面對 buildTypes 是這樣定義的:
private final NamedDomainObjectContainer<BuildType> buildTypes;
複製代碼
buildTypes 就是 NamedDomainObjectContainer 類型的,先來看看 buildTypes 在 Android 中是怎麼使用的,下面這段代碼應該都很熟悉了,它定義了 debug、relase 兩種打包模式:
android {
buildTypes {
release {
// 是否開啓混淆
minifyEnabled true
// 開啓ZipAlign優化
zipAlignEnabled true
//去掉不用資源
shrinkResources true
// 混淆文件位置
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 使用release簽名
signingConfig signingConfigs.hmiou
}
debug {
signingConfig signingConfigs.hmiou
}
}
}
複製代碼
當咱們新建一個項目時候,默認會有debug和release這2個配置,那麼debug、release能夠修改其餘名字嗎,能增長其餘名字來配置嗎,好比想增長一個測試包配置test,還有就是release裏面都能配置哪些屬性呢
我來講下結果,若是不肯定的,能夠實際驗證一下:
能夠看到它是很是靈活的,能夠根據不一樣的場景定義不一樣的配置,每一個不一樣的命名空間都會生成一個 BuildType 配置。要實現這樣的功能,必須使用 NamedDomainObjectContainer 類型。
顧名思義就是命名領域對象容器,它的主要功能有:
named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type. Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.
NamedDomainObjectContainer 須要經過 Project.container(...) API 來建立,其定義爲:
<T> NamedDomainObjectContainer<T> container(Class<T> type)
<T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory)
<T> NamedDomainObjectContainer<T> container(java.lang.Class<T> type, Closure factoryClosure
複製代碼
來看個具體的實例:
//這是領域對象類型定義
class TestDomainObj {
//必須定義一個 name 屬性,而且這個屬性值初始化之後不要修改
String name
String msg
//構造函數必須有一個 name 參數
public TestDomainObj(String name) {
this.name = name
}
void msg(String msg) {
this.msg = msg
}
String toString() {
return "name = ${name}, msg = ${msg}"
}
}
//建立一個擴展
class TestExtension {
//定義一個 NamedDomainObjectContainer 屬性
NamedDomainObjectContainer<TestDomainObj> testDomains
public TestExtension(Project project) {
//經過 project.container(...) 方法建立 NamedDomainObjectContainer
NamedDomainObjectContainer<TestDomainObj> domainObjs = project.container(TestDomainObj)
testDomains = domainObjs
}
//讓其支持 Gradle DSL 語法
void testDomain(Action<NamedDomainObjectContainer<TestDomainObj>> action) {
action.execute(testDomains)
}
void test() {
//遍歷命名領域對象容器,打印出全部的領域對象值
testDomains.all { data ->
println data
}
}
}
//建立一個名爲 test 的 Extension
def testExt = getExtensions().create("test", TestExtension, project)
test {
testDomain {
domain2 {
msg "This is domain2"
}
domain1 {
msg "This is domain1"
}
domain3 {
msg "This is domain3"
}
}
}
task myTask doLast {
testExt.test()
}
複製代碼
運行結果以下:
name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3
複製代碼
NamedDomainObjectContainer 既然是一個容器類,與之相應的必然會有查找容器裏的元素和遍歷容器的方法:
//遍歷
void all(Closure action)
//查找
<T> T getByName(String name)
//查找
<T> T findByName(String name)
複製代碼
仍是接着前面的例子:
//經過名字查找
TestDomainObj testData = testDomains.getByName("domain2")
println "getByName: ${testData}"
//遍歷命名領域對象容器,打印出全部的領域對象值
testDomains.all { data ->
println data
}
複製代碼
須要注意的是,Gradle 中有不少容器類的迭代遍歷方法有 each(Closure action)、all(Closure action),可是通常咱們都會用 all(...) 來進行容器的迭代。all(...) 迭代方法的特別之處是,不論是容器內已存在的元素,仍是後續任什麼時候刻加進去的元素,都會進行遍歷。
咱們在gradle中會看到 android{}
defaultConfig、productFlavors、signingConfigs、buildTypes 這4個內部 Extension對象是怎麼定義的,經過查看源碼能夠找到一個叫 BaseExtension 的類,裏面的相關代碼以下:
private final DefaultConfig defaultConfig;
private final NamedDomainObjectContainer<ProductFlavor> productFlavors;
private final NamedDomainObjectContainer<BuildType> buildTypes;
private final NamedDomainObjectContainer<SigningConfig> signingConfigs;
public void defaultConfig(Action<DefaultConfig> action) {
this.checkWritability();
action.execute(this.defaultConfig);
}
public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
this.checkWritability();
action.execute(this.buildTypes);
}
public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
this.checkWritability();
action.execute(this.productFlavors);
}
public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
this.checkWritability();
action.execute(this.signingConfigs);
}
複製代碼
在 app 的 build.gradle 裏咱們一般會採用插件 apply plugin: 'com.android.application' ,而在 library module 中則採用插件 apply plugin: 'com.android.library',AppPlugin 就是插件 com.android.application 的實現類,LibraryPlugin 則是插件 com.android.library 的實現類,接着再看看 AppPlugin 裏是怎樣建立 Extension 的:
public class AppPlugin extends BasePlugin implements Plugin<Project> {
@Inject
public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
super(instantiator, registry);
}
protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer<BuildType> buildTypeContainer, NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, NamedDomainObjectContainer<SigningConfig> signingConfigContainer, NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelInfo extraModelInfo) {
return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo});
}
public void apply(Project project) {
super.apply(project);
}
//省略...
}
複製代碼
在 createExtension() 方法中,能夠看到建立了一個名爲 android 的 Extension,該 Extension 的類型爲 AppExtension,而 AppExtension 的繼承結構爲 AppExtension -> TestedExtension -> BaseExtension,因此它的實現邏輯大部分都是在 BaseExtension 裏實現的。
在Android 工程中的build.gradle 文件中,咱們配置相關信息使用 android{} 節點,從 AppPlugin 也能看出其 Extension的名稱爲 android ,因此獲取方法以下:
def getInfo() {
//或者 直接 project.android
BaseExtension extension = project.extensions.getByName("android")
def android = project.extensions.getByType(AppExtension)
project.android
println "buildToolsVersion:${extension.buildToolsVersion}"
println "compileSdkVersion:${extension.getCompileSdkVersion()}"
println "applicationId:${extension.defaultConfig.applicationId}"
println "minSdkVersion:${extension.defaultConfig.minSdkVersion}"
println "targetSdkVersion:${extension.defaultConfig.targetSdkVersion}"
println "versionCode:${extension.defaultConfig.versionCode}"
println "versionName:${extension.defaultConfig.versionName}"
}
複製代碼
更詳細的請參考
每次構建的本質其實就是執行一系列的Task,某些Task可能依賴其餘Task,那些沒有依賴的Task總會被最早執行,並且每一個Task只會被執行一遍,每次構建的依賴關係是在構建的配置階段肯定的,在gradle構建中,構建的生命週期主要包括如下三個階段:
構建工具會根據每一個build.gradle文件建立出一個Project實例,初始化階段會執行項目根目錄下的Settings.gradle文件,來分析哪些項目參與構建
include ':app'
include ':libraries:someProject'
這個階段經過執行構建腳原本爲每一個project建立並分配Task。配置階段會去加載全部參與構建的項目的build.gradle文件,會將build.gradle文件實例化爲一個Gradle的project對象,而後分析project之間的依賴關係,下載依賴文件,分析project下的task之間的依賴關係
這是Task真正被執行的階段,Gradle會根據依賴關係決定哪些Task須要被執行,以及執行的前後順序。
task是Gradle中的最小執行單元,咱們全部的構建,編譯,打包,debug,test等都是執行了某一個task,一個project能夠有多個task,task之間能夠互相依賴。例如我有兩個task,taskA和taskB,指定taskA依賴taskB,而後執行taskA,這時會先去執行taskB,taskB執行完畢後在執行taskA。
dependencies { classpath 'com.android.tools.build:gradle:2.2.2' }
apply plugin: 'com.android.application'
Gradle項目有3個重要的文件須要深刻理解:
settings.gradle 文件會在構建的 initialization 階段被執行,它用於告訴構建系統哪些模塊須要包含到構建過程當中。對於單模塊項目, settings.gradle 文件不是必需的。對於多模塊項目,若是沒有該文件,構建系統就不能知道該用到哪些模塊。
項目根目錄的 build.gradle 文件用來配置針對全部模塊的一些屬性。它默認包含2個代碼塊:buildscript{…}和allprojects{…}。前者用於配置構建腳本所用到的代碼庫和依賴關係,後者用於定義全部模塊須要用到的一些公共屬性。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
複製代碼
buildscript:定義了 Android 編譯工具的類路徑。repositories中, jCenter是一個著名的 Maven 倉庫。
allprojects:中定義的屬性會被應用到全部 moudle 中,可是爲了保證每一個項目的獨立性,咱們通常不會在這裏面操做太多共有的東西。
模塊級配置文件 build.gradle 針對每一個moudle 的配置,若是這裏的定義的選項和頂層 build.gradle定義的相同。它有3個重要的代碼塊:plugin,android 和 dependencies。
//構建
gradlew app:clean //移除全部的編譯輸出文件,好比apk
gradlew app:build //構建 app module ,構建任務,至關於同時執行了check任務和assemble任務
//檢測
gradlew app:check //執行lint檢測編譯。
//打包
gradlew app:assemble //能夠編譯出release包和debug包,可使用gradlew assembleRelease或者gradlew assembleDebug來單獨編譯一種包
gradlew app:assembleRelease //app module 打 release 包
gradlew app:assembleDebug //app module 打 debug 包
//安裝,卸載
gradlew app:installDebug //安裝 app 的 debug 包到手機上
gradlew app:uninstallDebug //卸載手機上 app 的 debug 包
gradlew app:uninstallRelease //卸載手機上 app 的 release 包
gradlew app:uninstallAll //卸載手機上全部 app 的包
複製代碼
在gradle的構建過程當中,gradle爲咱們提供了很是豐富的鉤子,幫助咱們針對項目的需求定製構建的邏輯,以下圖所示:
要監聽這些生命週期,主要有兩種方式:
關於可用的鉤子能夠參考Gradle
和Project
中的定義,經常使用的鉤子包括:
Project提供的生命週期回調方法有
//在 Project 進行配置前調用
void beforeEvaluate(Closure closure)
//在 Project 配置結束後調用
void afterEvaluate(Closure closure)
複製代碼
beforeEvaluate 必須在父模塊的 build.gradle 對子模塊進行配置才能生效,由於在當前模塊的 build.gradle 中配置,它本身自己都沒配置好,因此不會監聽到。
settings.gradle 代碼:
include ":app"
複製代碼
build.gradle 代碼:
//對子模塊進行配置
subprojects { sub ->
sub.beforeEvaluate { proj ->
println "子項目beforeEvaluate回調..."
}
}
println "根項目配置開始---"
task rootTest {
println "根項目裏任務配置---"
doLast {
println "執行根項目任務..."
}
}
println "根項目配置結束---"
複製代碼
app/build.gradle 代碼:
println "APP子項目配置開始---"
afterEvaluate {
println "APP子項目afterEvaluate回調..."
}
task appTest {
println "APP子項目裏任務配置---"
doLast {
println "執行子項目任務..."
}
}
println "APP子項目配置結束---"
複製代碼
在根目錄執行:gradle -q,結果以下:
根項目配置開始---
根項目裏任務配置---
根項目配置結束---
子項目beforeEvaluate回調...
APP子項目配置開始---
APP子項目裏任務配置---
APP子項目配置結束---
APP子項目afterEvaluate回調...
複製代碼
project.android 獲取到AppExtension
:
Gradle 提供的生命週期回調方法不少,部分與 Project 裏的功能雷同:
//在project進行配置前調用,child project必須在root project中設置纔會生效,root project必須在settings.gradle中設置纔會生效
void beforeProject(Closure closure)
//在project配置後調用
afterProject(Closure closure)
//構建開始前調用
void buildStarted(Closure closure)
//構建結束後調用
void buildFinished(Closure closure)
//全部project配置完成後調用
void projectsEvaluated(Closure closure)
//當settings.gradle中引入的全部project都被建立好後調用,只在該文件設置纔會生效
void projectsLoaded(Closure closure)
//settings.gradle配置完後調用,只對settings.gradle設置生效
void settingsEvaluated(Closure closure)
複製代碼
Project
中的beforeEvaluate
和afterEvaluate
Settings
對象配置完畢咱們修改 setting.gradle 的代碼以下:
gradle.settingsEvaluated {
println "settings:執行settingsEvaluated..."
}
gradle.projectsLoaded {
println "settings:執行projectsLoaded..."
}
gradle.projectsEvaluated {
println "settings: 執行projectsEvaluated..."
}
gradle.beforeProject { proj ->
println "settings:執行${proj.name} beforeProject"
}
gradle.afterProject { proj ->
println "settings:執行${proj.name} afterProject"
}
gradle.buildStarted {
println "構建開始..."
}
gradle.buildFinished {
println "構建結束..."
}
include ":app"
複製代碼
這個時候的執行結果以下:
settings:執行settingsEvaluated...
settings:執行projectsLoaded...
settings:執行test beforeProject
根項目配置開始---
根項目裏任務配置---
根項目配置結束---
settings:執行test afterProject
settings:執行app beforeProject
子項目beforeEvaluate回調...
APP子項目配置開始---
APP子項目裏任務配置---
APP子項目配置結束---
settings:執行app afterProject
APP子項目afterEvaluate回調...
settings: 執行projectsEvaluated...
構建結束...
複製代碼
能夠看到 gradle.beforeProject 與 project.beforeEvaluate 是相似的,一樣 afterProject 與 afterEvaluate 也是相似的。
除此以外,Gradle 還有一個通用的設置生命週期監聽器的方法:addListener
Gradle 在配置完成後,會對全部的 task 生成一個有向無環圖,這裏叫作 task 執行圖,他們決定了 task 的執行順序等。一樣,Gradle 能夠對 task 的執行生命週期進行監聽。
//任務執行前掉用
void afterTask(Closure closure)
//任務執行後調用
void beforeTask(Closure closure)
//全部須要被執行的task已經task之間的依賴關係都已經確立
void whenReady(Closure closure)
複製代碼
經過 gradle.getTaskGraph() 方法來獲取 task 執行圖:
TaskExecutionGraph taskGraph = gradle.getTaskGraph()
taskGraph.whenReady {
println "task whenReady"
}
taskGraph.beforeTask { Task task ->
println "任務名稱:${task.name} beforeTask"
}
taskGraph.afterTask { Task task ->
println "任務名稱:${task.name} afterTask"
}
複製代碼
生命週期回調的執行順序:
gradle.settingsEvaluated->
gradle.projectsLoaded->
gradle.beforeProject->
project.beforeEvaluate->
gradle.afterProject->
project.afterEvaluate->
gradle.projectsEvaluated->
gradle.taskGraph.graphPopulated->
gradle.taskGraph.whenReady->
gradle.buildFinished
複製代碼
類型 | 說明 |
---|---|
Build script | 把插件寫在 build.gradle 文件中,通常用於簡單的邏輯,只在該 build.gradle 文件中可見 |
buildSrc 項目 | 將插件源代碼放在 rootProjectDir/buildSrc/src/main/groovy 中,只對該項目中可見,適用於邏輯較爲複雜 |
獨立項目 | 一個獨立的 Groovy 和 Java 項目,能夠把這個項目打包成 Jar 文件包,一個 Jar 文件包還能夠包含多個插件入口,將文件包發佈到託管平臺上,供其餘人使用。本文將着重介紹此類。 |
具體從插件開發能夠參考
須要注意的是 在main目錄下建立
一、resources/META-INF/gradle-plugins文件夾,
二、在gradle-plugins文件夾下建立一個xxx.properties文件,(com.learntransform.testtransform.properties)
注意:這個xxx就是在app下的build.gradle中引入時的名字,例如:apply plugin: ‘xxx’(apply plugin:'com.learntransform.testtransform')
三、在文件書寫引用到插件 implementation-class=me.xsfdev.learntransform.Hotfix
group = 'com.learntranform'
version = '1.0.1'
uploadArchives {
repositories {
flatDir {
name "localRepository"
dir "../app/localRepository/libs"
}
}
}
複製代碼
buildscript {
ext.kotlin_version = '1.2.41'
repositories {
flatDir {
name 'localRepository'
dir "app/localRepository/libs"
}
mavenLocal()
jcenter()
google()
}
dependencies {
classpath(group: 'com.plugintest', name: 'hellodsl', version: '1.0.0') {
changing = true
}
classpath(group: 'com.learntransform', name: 'learntransform', version: '1.0.1') {
changing = true
}
classpath 'com.android.tools.build:gradle:3.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.novoda:bintray-release:0.8.0' //jcenter添加
classpath 'com.xsfdev:complexcriptdsl:1.0.0'
}
}
複製代碼
完整代碼能夠參考 LearnGradle
更多參考須要看源碼
android.applicationVariants 返回的是
public DomainObjectSet<ApplicationVariant> getApplicationVariants() {
return applicationVariantList;
}
複製代碼
一層層追 相關的有
每次都會編譯
outputs.upToDateWhen { true }
doesn't mean "the task is up-to-date." It just means that the outputs are up-to-date for that particular spec. Gradle will still do its own up-to-date checks.
The other thing that may be confusing is where the task's actions are defined. If the actions are defined in the build script, the build script itself is an input to the task. So changes to the build script will make the task out-of-date.
So if I had a task like:
task myTask {
def outputFile = file("output.txt")
outputs.file outputFile
doLast {
outputFile.text = "Done"
}
outputs.upToDateWhen { false }
}
複製代碼
Whenever I run this, myTask
is out-of-date. If I switch the false to true, the first time I run it, the task is out-of-date still (because the buildscript changed). When I run it again, it would be up-to-date (all inputs are the same). You'll see this at --info
level logging.
The versions of the Android Gradle plugin and Gradle are not compatible.