Gradle入門到實戰(一) — 全面了解Gradle


聲明:本文來自汪磊的博客,轉載請注明出處

可關注個人公眾號,那里更新更及時,閱讀體驗更好:

 友情提示由於文章是從個人公眾號拷貝過來整理的,發現圖片沒有正常顯示,沒關注公眾號的同學可通過如下鏈接查看:https://mp.weixin.qq.com/s?__biz=Mzg2NzAwMjY4MQ==&mid=2247483789&idx=1&sn=4b3bb2ab721c8ed7e05f1e8b2e0fbf70&chksm=ce4371dbf934f8cd7c484e8c5356d299bbd5d7790ee11bb0da9725068fa8e4b895f87379949f&token=655420148&lang=zh_CN#rd

本文目錄

一、前言

從本篇起我們來系統學習一下Gradle相關知識,為什么要寫本系列呢?因為我發現在平時工作中大部分開發同學只是僅僅會簡單的配置Gradle,說白了就是用的時候配置一下,出問題查找一下然后配置上就完了,至於Gradle基本的工作流程完全不理解,其實我本人剛用AS開發的時候也是對Gradle完全不理解,甚至厭惡,為什么非要用這玩意?還要我花時間學習額外的玩意,但是當我慢慢熟悉Gradle之后就慢慢對其"刮目相看"了,簡單,可擴展等方面非常友好,項目的構建我們可以編寫一些自己的插件來完成一些重復無意義的操作。

本系列我會從最基本的groovy語言開始慢慢學習,直到最后編寫我們自己的多渠道打包插件(美團也有同樣功能的開源解決方案),圖片壓縮轉換插件(jpg,png轉換webp, 不能轉換則進一步壓縮),相信最后你一定會有所收獲。

二、學習Gradle的基礎---Groovy語言的學習

學習Geadle我們需要先來學習一下Groovy,很多同學估計聽說過Groovy語言,有的也許沒聽說過,其實我們每天在配置Gradle的時候就是在跟Groovy接觸了。

Groovy是一門jvm語言,最終編譯成class文件然后在jvm上執行,Java語言的特性Groovy都支持,我們可以混寫Java和Groovy,就是在同一文件中可以混寫, 注意,這里與Java和Kotlin可不一樣,Java和Kotlin是可以互相調用,而不能在同一文件混寫 。

Groovy的優勢是什么呢?Groovy增強了Java的很多功能。比如解析xml文件,文件讀寫等,Groovy只需要幾行代碼就能搞定,而如果用Java則需要幾十行代碼,簡單說就是使用Groovy實現某些功能比Java要少些很多代碼。

好了,說了那么多"廢話",下面就具體來了解一下Groovy語言,我們只需要了解其中核心的一些使用就可以了,沒必要完全了解,作為一門語言Groovy也算比較復雜的,但是我們學習Groovy是為了編寫Gradle插件那就只需要了解其核心部分就可以了,其實從我的經驗來說學習Groovy更多是為了寫更少的代碼來實現一些功能,否則我直接用Java寫不就完了。

關於Groovy環境的配置很簡單,自己查詢配置一下就可以了,使用自帶的編輯器學習即可。

Groovy的變量和方法聲明

Groovy使用def來聲明變量,聲明變量可以不加類型,運行的時候可以自動推斷出變量類型,並且聲明語句可以不加分號結尾,如下:

1 def  i = 10
2 def  str = "hello groovy"
3 def  d = 1.25

def同樣用來定義函數,Groovy中函數返回值可以是無類型的:

1 //定義函數
2 def showName(){
3    "wang lei" //最后一行執行結果默認作為函數返回值,可以不加return
4 }

如果我們指定了函數的返回值類型,那么可以不加def關鍵字:

1 //定義函數
2 String showName(){
3    "wang lei" //最后一行執行結果默認作為函數返回值
4 }
Groovy的字符串

Groovy中字符串分為三種,我們一個個來看。
單引號字符串,單引號字符串不會對$進行轉義,原樣輸出

1 def i = 10
2 def str1 = 'i am $i yuan'//單引號不會轉義
3 println str1 //輸出  i am $i yuan

雙引號字符串對$號進行轉義,如下:

1 def i = 10
2 def str2 = "i am $i yuan"//雙引號會轉義
3 println str2 //輸出  i am 10 yuan

三引號字符串,三引號字符串支持換行,原樣輸出,如下:

1 //三個引號原樣輸出
2 def str3 = '''
3    public static void main(){
4        println "miss you"
5    }
6 '''
7 println str3

輸出:

1    public static void main(){
2        println "miss you"
3    }
Groovy的數據類型

Groovy數據類型主要包括以下三種:

  • Java中基本數據類型

  • 增強的List,Map,Range容器類

  • 閉包

    我們分別看一下

Groovy中的所有事物都是對象。int,double等Java中的基本數據類型,在Groovy中對應的是它們的包裝數據類型。比如int對應為Integer。比如:

1 def x = 23
2 println x.getClass().getCanonicalName()//java.lang.Integer
3
4 def f = true
5 println f.getClass().getCanonicalName()//java.lang.Boolean

增強的List,Map,Range容器類

List
List:鏈表,其底層對應Java中的List接口,一般用ArrayList作為真正的實現 類。
使用如下:

 1 def myList = [5,'werw',true]//看到了吧,可以放任意數據,就是那么任性
 2 myList.add(34)
 3 //myList.add(12,55)//這樣會報角標越界異常
 4 myList[6] = true
 5 println myList.size()
 6 //遍歷
 7 myList.each{
 8    println "item: $it"
 9 }
10 myList.eachWithIndex {
11    it, i -> // `it` is the current element,`i` is the index
12    println "$i: $it"
13 }
14 println myList.getClass().getCanonicalName()

打印信息如下:

 1 7
 2 item: 5
 3 item: werw
 4 item: true
 5 item: 34
 6 item: null
 7 item: null
 8 item: true
 9 0: 5
10 1: werw
11 2: true
12 3: 34
13 4: null
14 5: null
15 6: true
16 java.util.ArrayList

看到了吧,Groovy中List用起來方便多了,擴展了Java中的一些功能。

單獨說明一下,List還有一種方式可以加入元素:<<操作。如下:

1myList << 34//這樣也是加入元素

剛接觸是不是感覺很別扭,慢慢來,習慣就好了,對於<<文檔描述如下:


一定要學會查文檔來學習,而不是遇見問題就查詢,向這種<< 只有文檔才說的最明白,況且我這篇文章也只是寫出來最核心的一些資料,如果你想實現一些功能而我沒寫出來就需要自己去查文檔。

文檔地址:http://www.groovy-lang.org/groovy-dev-kit.html與 http://www.groovy-lang.org/api.html 建議還是瀏覽一下文檔,里面有很多好玩的東西

Map
直接看示例:

 1 //Map類
 2 def map = ['key1':true,name:"str1",3:"str3",4:"str4"]
 3 println map.name // 相當於 map.get('name')
 4 println map.get(4)
 5 map.age = 14 //加入 age:14 數據項
 6 println map.age //輸出14
 7 println map.getClass().getCanonicalName()
 8 //遍歷
 9 map.each{
10    key,value -> 
11       println key +"--"+ value
12    }
13
14 map.each{
15    it -> 
16       println "$it.key ::: $it.value"
17    }
18 // `entry` is a map entry, `i` the index in the map
19 map.eachWithIndex { entry, i ->
20    println "$i - Name: $entry.key Age: $entry.value"
21 }
22
23 // Key, value and i as the index in the map
24 map.eachWithIndex { key, value, i ->
25    println "$i - Name: $key Age: $value"
26 }
27
28 println map.containsKey('key1')  //判斷map中是否包含給定的key
29 println map.containsValue(1)  //判斷map中是否包含給定的value
30 //返回所有
31 println map.findAll{
32    entry ->
33         entry.value instanceof String//value是String類型的
34 }
35 //返回第一個符合要求的
36 println map.find{
37    entry ->
38        entry.key instanceof Integer && entry.key > 1
39 } //返回符合條件一個entry
40
41 //清空
42 map.clear()
43
44 println map.size()

Ranges
關於Ranges文檔描述如下:


簡單來說Ranges就是對List的擴展,用..或者..<來定義,具體使用如下:

 

 1 //Range
 2 def Range1 = 1..10//包括10
 3 println Range1.contains(10)
 4 println Range1.from +"__"+Range1.to
 5 def Range2 = 1..<10//不包括10
 6 println Range2.contains(10)
 7 println Range2.from +"__"+Range2.to
 8 //遍歷
 9 for (i in 1..10) {
10    println "for ${i}"
11 }
12
13 (1..10).each { i ->
14    println "each ${i}"
15 }
16 def years = 23
17 def interestRate
18 switch (years) {
19    case 1..10: interestRate = 0.076; break;
20    case 11..25: interestRate = 0.052; break;
21    default: interestRate = 0.037;
22 }
23 println interestRate//0.052

 

好了,以上就是Groovy中的容器類,最核心的我已經列出來了,如果想繼續查看其余功能請自己查看文檔,當初看完這部分我的感覺就是這尼瑪不就是kotlin嗎(學習Groovy之前已經看了Kotlin),二者其實都是對Java的擴展,使用起來都更加簡單,功能更加強大,好了,學習Gradle,掌握以上就已經基本夠用了。

Groovy中的閉包

閉包Closure一開始接觸的時候真是有點蒙圈的感覺,這是什么玩意,Kotlin中也有這玩意,接觸多了也就慢慢習慣了,其實就是一段可執行的代碼,我們可以像定義變量一樣定義可執行的代碼。

閉包一般定義如下:

1 def xxx = {
2    paramters -> 
3        code
4 }

比如,我們定義一下閉包:

1 def mClosure = {
2    p1,p2 ->
3         println p1 +"..."+p2
4 }

p1,p2也可以指定數據類型,如下:

1 //閉包定義
2 // ->前是參數定義, 后面是代碼
3 def mClosure = {
4    String p1, int p2 ->
5         println p1 +"..."+p2
6 }

調用上述定義的閉包:

1 //調用 均可以
2 mClosure("qwewe",100)
3 mClosure.call("qwewe",100)

看到這里有C經驗的是不是瞬間想到函數指針了,連調用都非常像。

定義閉包我們也可以不指定參數,如不指定則默認包含一個名為it的參數:

1 def hello = { "Hello, $it!" }
2 println hello("groovy")//Hello, groovy!

注意一點,在Groovy中,當函數的最后一個參數是閉包或者只有一個閉包參數,可以省略調用時的圓括號。比如:

 1 def fun(Closure closure){
 2    println "fun1"
 3    closure()
 4 }
 5 //調用方法
 6 fun({
 7    println "closure"
 8 })
 9 //可以不寫()直接調用
10 fun{
11    println "closure"
12 }

或者如下:

 1 def fun(int a , Closure closure){
 2    println "fun1 $a"
 3    closure()
 4 }
 5 //調用方法
 6 fun(1, {
 7    println "closure"
 8 })
 9 //可以不寫()直接調用,這就有點奇葩了
10 fun 1, {
11    println "closure"
12 }

關於閉包,剛接觸肯定不習慣,自己一定要花時間慢慢體會,閉包在Gradle中大量使用,后面講Gradle的時候會大量接觸閉包的概念。

Groovy中的文件操作
在我電腦有如下文件:


接下來我們對in.txt與out.txt文件進行IO操作。

讀文件
讀文件有三種操作:一次讀取一行,讀取全部返回字節數據,數據流的形式讀取,我們分別看一下:

一次讀取一行:

1 def src = new File("C:\\Users\\wanglei55\\Desktop\\Groovy\\in.txt")
2 //每次讀取一行
3 src.eachLine{
4    //it就是每一行數據
5    it ->
6        println it
7 }

讀取全部返回字節數據:

1 //一次讀取全部:字節數組
2 def bytes = src.getBytes()
3 def str = new String(bytes)
4 println "str::"+str

數據流的形式讀取:

1 //返回流,不用主動關閉
2 src.withInputStream{
3    inStream ->
4        //操作。。。。
5 }

看到這里是不是不禁感嘆:so easy!!!。是的,Groovy的IO操作就是那么瀟灑,任性。

寫文件
寫文件的操作同樣很簡單,我們看下將in.txt文件拷貝到out.txt中怎么操作:

1 //寫數據
2 def des = new File("C:\\Users\\wanglei55\\Desktop\\Groovy\\out.txt")
3 des.withOutputStream{ 
4    os->
5        os.leftShift(bytes)//左移在這里可以起到寫入數據的作用
6 }

我們也可以逐行寫入數據:

1 des.withWriter('utf-8') { writer ->
2    writer.writeLine 'i'
3    writer.writeLine 'am'
4    writer.writeLine 'wanglei'
5 }

我們也可以這樣寫入數據:

1 des << '''i love groovy'''

是不是爽歪歪?IO操作就是這么簡單,像遍歷文件夾內容等操作自己可以查詢文檔寫寫看,一定要學會自己查文檔,上面很多代碼都是從文檔示例上看到的。

好了,以上就是IO操作的一些核心,接下來我們看下Groovy怎么操作XML以及Json數據。

XML解析
同樣我電腦有如下xml文檔:


內容如下:

 1<response version-api="2.0">
 2    <value>
 3        <persons>
 4            <person id = "1">
 5                <name>zhangsan</name>
 6                <age>12</age>
 7            </person>
 8            <person id = "2">
 9                <name>lisi</name>
10                <age>23</age>
11            </person>
12        </persons>
13    </value>
14</response>

Groovy解析xml文檔同樣非常簡單,我們可以像操作對象一樣操作xml:

1 //xml解析
2 def xmlFile = new File("C:\\Users\\wanglei55\\Desktop\\Groovy\\test.xml")
3 def parse = new XmlSlurper()
4 def result =parse.parse(xmlFile)
5
6 def p0 = result.value.persons.person[0]
7 println p0['@id']
8 println p0.name
9 println p0.age

很簡單,沒什么要特別說明的,這里稍微記一下,后面寫插件的時候會解析安卓的AndroidManifest.xml文件

Json

直接看代碼吧,很簡單:

 1 //Person類
 2 class Person{
 3    def first
 4    def last
 5 }
 6
 7 def p = new Person(first: 'wanglei',last: '456')
 8 //轉換對象為json數據
 9 def jsonOutput = new JsonOutput()
10 def json = jsonOutput.toJson(p)
11 println(json)
12 //格式化輸出
13 println(jsonOutput.prettyPrint(json))
14
15 //解析json
16 def slurper = new JsonSlurper()
17 Person person = slurper.parseText(json)
18 println person.first
19 println person.last

打印輸出如下:

1 {"first":"wanglei","last":"456"}
2 {
3    "first": "wanglei",
4    "last": "456"
5 }
6 wanglei
7 456

好了,以上就是Groovy的一些核心了,對於學習Gradle掌握上面這些就差不多了。下面進入Gradle的學習。

三、認識Gradle

Gradle到底是個什么玩意?為什么要先拋出這個問題,從我經驗來說,大量開發者都只是會配置Gradle,配置這個,配置那個,至於為什么這么配置,出了問題怎么查找等等完全扒瞎,很多開發者都僅僅停留在配置這個階段,我認為Gradle的學習有三個層次:簡單配置->認為Gradle是個腳本工具->Gradle同樣是編程框架。

我個人更傾向認為Gradle是一個編程框架,其實無論你認為是框架還是腳本,只要能夠為我所用即可。

Gradle學習主要參考文檔如下:

Gradle API:https://docs.gradle.org/current/javadoc/org/gradle/api/package-summary.html

Gradle DSL:https://docs.gradle.org/current/dsl/org.gradle.api.Project.html

Gradle 用戶手冊:https://docs.gradle.org/current/userguide/userguide.html

Android插件DSL參考:http://google.github.io/android-gradle-dsl/current/index.html

到這里可能你還有點蒙圈,一看文檔這都是什么玩意啊,別着急,一步步來慢慢認識Gradle。

Gradle環境搭建

安裝Gradle開發環境很簡單,參考官方文檔即可:安裝Gradle
安裝Gradle前最好將JDK先升級到1.8版本,Gradle最新版本需要的JDK版本為1.8。

請自行安裝好Gradle環境。

初識Gradle

Gradle是一個開源的構建自動化工具,既然是用於項目構建,那它肯定要制定一些規則,在Gradle中每一個待編譯的工程都稱為Project,每個Project都可以引入自己需要的插件,引入插件的目的其實就是引入插件中包含的Task,一個Project可以引入多個插件,每個插件同樣可以包含一個或多個Task,關於插件和Task我們后面還會單獨細說。

比如,如下項目:


工程GradleLearn包含三個module:app,library1,library2。其中app是App module,而library1,library2均是library module。

相信現在大部分都是這種多module項目了,對於Gradle來說這種叫做multiprojects,看這意思是多projects的項目,那么對於Gradle來說有多少個項目呢?

我們可以通過gradle projects命令來查看有多少個projects:


這里我是在命令行里執行的。

看到了吧,對於Gradle來說有4個project,其中GradleLearn叫做Root project,其余三個moudule均叫做Project。

那Gradle怎么知道有多少個Project呢?在我們創建工程的時候在根目錄下有個settings.gradle文件,我們看下其中內容:

1include ':app', ':library1', ':library2'

默認只有app模塊,之后我們新建的library1,library2模塊都會自動包含進來,Gradle就是通過查看這個配置文件來確定我們工程有多少了project的,我們修改settings.gradle文件:

1include ':app', ':library1'

去掉library2模塊,然后在執行gradle projects命令,如下:


看到了吧,已經沒有library2模塊了。

好了,現在我們對settings.gradle有了初步的認識,我們在看看build.gradle文件的作用,工程根目錄下有個build.gradle,每個module下也有自己的build.gradle文件:


記得剛接觸Gradle的時候這幾個build.gradle文件真是讓我蒙圈,怎么這么多,都干什么的,相信很多同學剛開始都有這疑問。

上面我們提到GradleLearn叫做Root project,其余三個moudule均叫做Project,其實在Gradle中GradleLearn被當做三個module的父級project,在父project的build.gradle中配置的信息可以作用在子project中,比如根目錄build.gradle內容如下:

 1 ..........
 2
 3 allprojects {
 4    repositories {
 5        google()
 6        jcenter()
 7    }
 8 }
 9
10 .........

這里的意思就是配置此項目及其每個子項目的倉儲庫為google()與 jcenter(),這樣我們就不用在每個子project的build.gradle中再單獨配置了,否則我們需要為每個子project單獨配置一下需要引入的插件的倉儲庫。

根project的build.gradle文件並不是必須的,我們甚至可以刪除掉,其存在的意義主要就是將子project公共的配置提取到父build.gradle來統一管理,是不是有點像“基類”的意思。

好了,以上只是大概了解了一下settings.gradle與build.gradle,都是及其簡單的。

settings.gradle與build.gradle的本質

大家有沒有想過這樣一個問題為什么settings.gradle可以include子模塊,又為什么build.gradle可以寫如下配置呢:

 1 buildscript {
 2    repositories {
 3        google()
 4        jcenter()
 5    }
 6    dependencies {
 7        classpath 'com.android.tools.build:gradle:3.3.1'
 8    }
 9}
10
11 allprojects {
12    repositories {
13        google()
14        jcenter()
15    }
16 }
17
18 task clean(type: Delete) {
19    delete rootProject.buildDir
20 }

其實我們在settings.gradle與build.gradle中看似配置的信息其實都是調用對應對象的方法或者腳本塊設置對應信息。

對應對象是什么玩意?其實settings.gradle文件最終會被翻譯為Settings對象,而build.gradle文件最終會被翻譯為Project對象,build.gradle文件對應的就是每個project的配置。

Settings與Project在Gradle中都有對應的類,也就是說只有Gradle這個框架定義的我們才能用,至於Settings與Project都定義了什么我們只能查看其官方文檔啊。

Settings對象
比如Settings類中定義了include方法: 


include方法api說明為:


看到了吧,我們每一個配置都是調用對應對象的方法。

我還發現Settings類中定義了如下方法:


那我們在setting.gradle文件里面寫上如下代碼試一下:

1 include ':app', ':library1', ':library2'
2
3 def pro = findProject(':app')
4 println '----------------------------'
5 println pro.getPath()
6 println '----------------------------'

然后執行gradle assembleDebug命令編譯我們的項目輸出如下:


輸出了app這個project的信息。

Project對象
每個build.gradle文件都會被翻譯為對應的Project對象,build.gradle文件最重要的作用就是:

  • 引入插件並配置相應信息

  • 添加依賴信息

引入插件重要的就是引入插件中包含的tasks,至於插件與tasks后面會詳細講到。

那怎么引入插件呢?

Project定義了apply函數供我們調用:


平時我們看到的如下:

apply plugin: 'com.android.library'

最終都是調用那個的上面apply方法。

在Project類中有個很重要的方法,如下:


這個方法在配置完當前project后立刻調用,並且傳給我們project參數,我們調用試試,在根build.gradle中添加如下代碼:

1 afterEvaluate{
2
3    project ->
4        println "root module -----> $project.name"
5 }

app module的build.gradle添加如下代碼:

1 afterEvaluate{
2
3    project ->
4        println "app module -----> $project.name"
5 }

同樣,執行assemble debug命令,打印如下:


輸出了相應的信息,上面的都不難,我只是想告訴大家Gradle有它自己的規則,怎么配置,配置什么都在文檔中有對應規定,我們配置xxx.gradle都有對應的類,最終都翻譯成對應對象,有問題查閱相關文檔即可。

Gradle對象

在我們執行gradle相關命令的時候,Gradle框架會為我們創建一個gradle對象,並且整個工程只有這一個gradle對象,這個對象不像上面兩個一樣有對應文件,這個對象一般不需要我們配置什么,主要用於給我們提供一些工程信息,具體有哪些信息呢?查看文檔中Gradle類包含以下信息:


我們可以打印一些信息看看,在根build.gradle中添加如下代碼:

1 println "homeDir = $gradle.gradleHomeDir"
2 println "UserHomeDir = $gradle.gradleUserHomeDir"
3 println "gradleVersion = $gradle.gradleVersion"

執行gradle assembleDebug命令打印如下信息:


gradle對象最重要的就是在構建工程的時候加入各種回調,通過加入回調我們可以監聽工程構建的各個時期,這部分后面會講到。

好了,到這里Gradle主要的三個類就基本介紹完了,至於每個類定義了什么屬性,方法等等還需要自己去看看文檔。

四、Gradle工作時序

Gradle執行分為三個過程:

Initiliazation
初始化階段只要為每個module創建project實例。這個階段settings.gradle文件會被解析執行。

Configration
這個階段解析每個模塊的build.gradle文件,這個階段完成后整個項目的tasks執行順序也就確定了並且task准備就緒處於待執行狀態,整個tasks任務會構成一個有向無環圖。

執行任務
這階段就是按照順序執行具體任務了。

在每個階段我們可以通過gradle對象添加回調監聽。

我們在settings.gradle文件與每個module的build.gradle文件添加如下信息:

settings.gradle:

1 println "settings start"
2 include ':app', ':library1', ':library2'
3 println "settings end"

根目錄的build.gradle:通過gradle對象添加了一些回調監聽

 1 。。。。。。。。
 2 afterEvaluate{
 3
 4    project ->
 5        println "root module afterEvaluate -----> $project.name"
 6 }
 7
 8 gradle.beforeProject{
 9    project ->
10        println "beforeProject $project.name"
11 }
12
13 gradle.afterProject{
14    project ->
15        println "afterProject $project.name"
16 }
17
18 gradle.taskGraph.whenReady {
19    println "taskGraph.whenReady"
20 }
21
22 gradle.buildFinished{
23    result ->
24        println "buildFinished"
25 }

app module的build.gradle:

 1 apply plugin: 'com.android.application'
 2 println "app start"
 3 。。。。。。。。。。
 4 afterEvaluate{
 5
 6    project ->
 7        println "app module afterEvaluate -----> $project.name"
 8 }
 9
10 println "app end"

library1 module的build.gradle:

 1 apply plugin: 'com.android.library'
 2 println "library1 start"
 3
 4 。。。。。。。。。。。
 5
 6 afterEvaluate{
 7
 8    project ->
 9        println "library1 module afterEvaluate -----> $project.name"
10 }
11
12 println "library1 end"

library2 module的build.gradle:

 1 apply plugin: 'com.android.library'
 2 println "library2 start"
 3
 4 。。。。。。。。。。。。。。。
 5
 6 afterEvaluate{
 7
 8    project ->
 9        println "library2 module afterEvaluate -----> $project.name"
10 }
11
12 println "library2 end"

接下來我們執行gradle assembleDebug命令,打印如下信息:


以上就是Gradle構建工程的大體過程,在這個過程我們可以加入各種回調監聽。

五、Gradle中的task

task可以說Gradle中很重要的部分,task是Gradle的執行單元,一個task就是具體執行某一任務。

在AS中我們一創建工程的時候就存在很多task:


這些task都是我們引入安卓插件幫我們自動引入的,關於插件馬上就講到了。

自定義task
我們可以定義自己的task,在app模塊的build.gradle中添加如下代碼:

1 task aTask{
2    println ">>>>>>>>>>>>>>>>task..."
3 }

這樣我們就定義了名稱為aTask的任務了,在AS中同步之后我們可以看到:


由於我們沒有指明分組所以這里默認屬於other分組,我們可以指定自己的分組:

1 task aTask(group:'myGroup'){
2    println ">>>>>>>>>>>>>>>>task..."
3 }

同步之后,如下:


上面我們說過build.gradle對應Gradle中的Project類,Project類中定義了如下方法:


正式由於Project類中定義了上面方法,我們才可以以上面方式來創建task任務。

我們可以通過gradle task名稱的命令來執行某一個task任務,比如執行上述任務:gradle aTask


上述打印信息的代碼是在Configure階段來執行的,也就是我們可以提前配置一下我們的task,如果我們不需要配置想在執行我們task的時候來執行一些操作,我們也可以修改代碼如下:

 1 task aTask(group:'myGroup'){
 2    println ">>>>>>>>>>>>>>>>config..."
 3    //任務開始執行初期執行
 4    doFirst{
 5        println ">>>>>>>>>>>>>>>>aTask doFirst..."
 6    }
 7    //任務開始執行末期執行
 8    doLast{
 9        println ">>>>>>>>>>>>>>>>aTask doLast..."
10    }
11 }

執行gradle aTask命令來執行任務:


此外,有時我們也會看到如下寫法:

1 task B << {
2    println 'B'
3 }

這種寫法和如下寫法都是一樣效果:

1 task B {
2    doLast{
3        println 'B'
4    }
5 }

task的依賴關系
task之間可以指定依賴關系,比如指定A task依賴B task那么在執行A task的時候需要先執行B task,我們修改代碼如下:

 1 task aTask(group:'myGroup'){
 2    println ">>>>>>>>>>>>>>>>a config..."
 3
 4    doFirst{
 5        println ">>>>>>>>>>>>>>>>aTask doFirst..."
 6    }
 7
 8    doLast{
 9        println ">>>>>>>>>>>>>>>>aTask doLast..."
10    }
11 }
12
13 task bTask(group:'myGroup'){
14    println ">>>>>>>>>>>>>>>>b config..."
15
16    doFirst{
17        println ">>>>>>>>>>>>>>>>bTask doFirst..."
18    }
19
20    doLast{
21        println ">>>>>>>>>>>>>>>>bTask doLast..."
22    }
23}
24 //這里指定aTask 依賴於bTask
25 aTask.dependsOn bTask

我們也可以創建task的時候就直接指定依賴:

 1 //通過dependsOn指定依賴
 2 task aTask(group:'myGroup', dependsOn: ["bTask"]){
 3    println ">>>>>>>>>>>>>>>>a config..."
 4
 5    doFirst{
 6        println ">>>>>>>>>>>>>>>>aTask doFirst..."
 7    }
 8
 9    doLast{
10        println ">>>>>>>>>>>>>>>>aTask doLast..."
11    }
12 }
13
14 task bTask(group:'myGroup'){
15    println ">>>>>>>>>>>>>>>>b config..."
16
17    doFirst{
18        println ">>>>>>>>>>>>>>>>bTask doFirst..."
19    }
20
21    doLast{
22        println ">>>>>>>>>>>>>>>>bTask doLast..."
23    }
24 }
25
26 //aTask.dependsOn bTask

二者同樣效果,我們執行aTask:


我們也可以定義task類,此類需要繼承DefaultTask:

 1 class IncrementTask extends DefaultTask {
 2
 3    String msg = 'default'
 4
 5    IncrementTask() {
 6        group '自定義任務' //指定任務的分組
 7        description '任務描述'//指定描述
 8    }
 9
10    @TaskAction
11    void run() {
12        println "IncrementTask __$msg"
13    }
14 }

上面用@TaskAction標注方法run(),這樣我們在執行任務的時候會調用run()方法。

接下來我們可以通過如下方式創建實例:

1 task increment2(type: IncrementTask) {
2    msg = 'create task'
3 }

創建task的時候我們可以指定type,也就是指定其父類,如果我們不制定默認為DefaultTask的子類。

我們也可以通過如下方式創建:

1 tasks.create('increment1', IncrementTask){
2    msg = 'tasks.create'//重新指定msg信息
3 }

這里我在說一下查文檔的重要性,tasks是什么鬼?由於我們是在build.gradle中使用的,其對應Project類,所以我們去Project類中查一下,屬性描述有對其描述:


原來tasks是一個屬性,點進去看一下:


其對應的是TaskContainer類,我們還需要再看一下這個類都包含什么:


看到了吧,這個類中包含各種create方法用來創建task任務。

好了,task就說這么多,有什么問題希望你自己去查文檔解決,比如創建的時候可以指定type,那還可以指定其余的信息嗎?都可以指定什么信息,這些文檔中都有描述。

六、Gradle中任務的增量構建

作為構建工具我們要防止做重復工作。例如AS編譯出APK文件,如果我們已經編譯出了APK文件,再次編譯的時候如果我們沒有刪除APK文件並且代碼資源等都沒變化那么就沒必要在重新走一遍流程執行各個task任務最重輸出APK文件,中間的任務都可以跳過,這樣可以提升效率縮短編譯時間。

Gradle是通過增量構建的特性來支持這個功能的。

Gradle在執行任務的時候會檢查任務的輸入輸出是否有變化,如果任務的輸入輸出均沒有變化則認為任務是up-to-date的,會跳過任務不去執行它,任務至少有一個輸出否則增量構建不起作用

我們改造IncrementTask,使其成為可以增量構建的任務,指明其輸入輸出:

 1 class IncrementTask extends DefaultTask {
 2
 3    @Input //指明輸入
 4    String msg = 'default'
 5
 6    @OutputFile //指明輸出
 7    File file
 8
 9    IncrementTask() {
10        group '自定義任務'
11        description '任務描述'
12    }
13
14    @TaskAction
15    void run() {
16        println "IncrementTask __$msg"
17    }
18 }

我們在創建任務的時候指明其輸入輸出:

1 tasks.create('increment1', IncrementTask){
2    msg = 'tasks.create1'//重新指定msg信息
3    file = file('path.txt')
4 }

我們在第一次執行任務的時候,輸出如下:


執行了run方法輸出相應信息,接着我們再次執行:


看到了吧,沒有執行run方法,並且提示:up-to-date。

我們也可以關閉任務的增量構建,使其每次執行的時候都會執行run方法:

1 。。。。。。。
2 IncrementTask() {
3        group '自定義任務'
4        description '任務描述'
5        outputs.upToDateWhen { false }//返回false關閉增量構建
6    }
7 。。。。。。

增量構建功能主要是為了提升編譯效率,否則一個大的項目包含幾十幾百的任務不能每次編譯都要挨個重新執行每次任務吧,這里只是簡單介紹了一下Gradle的增量構建功能,詳細學習可以自己查閱文檔看一下。

七、自定義插件

官方自定義插件教程:編寫自定義插件

引入插件最大的作用就是引入其中包含的一個個任務,一個任務往往完成一項工作,比如java代碼編譯為class字節碼的任務,處理ndk的任務等等。
接下來我們先看看怎么自定義插件,說那么多顯得很蒼白。

Gradle提供了三種方式:

  • build.gradle腳本中直接編寫

  • buildSrc中編寫

  • 獨立Module中編寫

    其中前兩種方式只能用在自己項目,而第三種方式比較靈活,可以發布到jcenter倉庫供別人引入使用,這里我采用獨立module的方式編寫。

我們新建plugin module,在其build.gradle中引入Groovy插件,並將Gradle API添加為編譯時依賴項:

 1 apply plugin: 'java-library'
 2 apply plugin: 'groovy'
 3
 4 dependencies {
 5    compile gradleApi()
 6    compile localGroovy()
 7    implementation fileTree(dir: 'libs', include: ['*.jar'])
 8 }
 9
10 sourceCompatibility = "1.7"
11 targetCompatibility = "1.7"

下面我們就可以編寫自己的插件了,自定義插件需要實現Plugin接口並實現apply方法:

新建Plugin1.groovy類編寫如下代碼:

1 class Plugin1 implements Plugin<Project>{
2
3    @Override
4    void apply(Project project) {
5        println "我是自定義插件"
6    }
7 }

很簡單就是一句輸出,別急慢慢來,慢慢就復雜了。

這時別的模塊怎么使用我們的插件呢?想想我們安卓中我們怎么引入插件的,我們需要指定倉庫的地址,插件的版本,然后在調用apply方法引入插件,這里我們也需要這樣指定。

我們先指定我們插件的完整版本,怎么還完整版本?我們看看怎么引入安卓插件版本的:

1 classpath 'com.android.tools.build:gradle:3.3.1'

這里包含三部分:


三部分以分號: 分開,所以我們的插件也需要指定這些信息,怎么指定呢?很簡單在build.gradle文件中指定即可,如下:


我們再看引入安卓插件的apply寫法:

1 apply plugin: 'com.android.application'

'com.android.application'這個字符串又是什么?我的理解就是給我們的插件起個別名通過這個別名引入就可以了,那怎么配置這個別名呢?
如下:


看到了吧,在main的同級目錄下新建resources目錄,然后新建META-INF目錄,在新建gradle-plugins目錄,最后創建xxx.properties配置文件,這個文件的名字就是我們插件的別名 我這里的文件名是:com.wanglei.plugin所以可通過如下方式引入插件:

1 apply plugin : "com.wanglei.plugin"

xxx.properties文件中需要配置如下信息:

1 implementation-class=插件實現類的完整路徑

我這里Plugin1的完整實現類為:com.wanglei55.plugin.Plugin1,所以配置如下:

1 implementation-class=com.wanglei55.plugin.Plugin1

到這,所有的插件相關配置都已經配置完了。

接下來我們就可以將我們的插件發布到倉庫了,這里為了簡便我發布到本地倉庫,本地倉庫就是發布到我們自己的電腦上,引入maven-publish插件用來發布到本地倉庫,配置如下,這里我列出plugin模塊下build.gradle文件所有內容:

 1 apply plugin: 'java-library'
 2 apply plugin: 'groovy'
 3
 4 dependencies {
 5    compile gradleApi()
 6    compile localGroovy()
 7    implementation fileTree(dir: 'libs', include: ['*.jar'])
 8 }
 9
10 sourceCompatibility = "1.7"
11 targetCompatibility = "1.7"
12
13 group 'com.wanglei.plugin'
14 version '1.0'
15 //用於發布到本地倉庫
16 apply plugin: 'maven-publish'
17 //發布配置
18 publishing{
19    publications{
20        plugin(MavenPublication){
21            from components.java
22            artifactId 'testplugin'
23        }
24    }
25 }

同步工程后,就多了如下任務:


雙擊publishPluginPublicationToMavenLocal任務執行,就將我們插件發布到本地倉庫了。

比如我的這台電腦發布完如下:


這樣我們就可以在別的module引入自己寫的插件了,比如我在app模塊下引入:

1 buildscript{
2    repositories{
3        mavenLocal()
4    }
5    dependencies{
6        classpath 'com.wanglei.plugin:testplugin:1.0'
7    }
8 }
9 apply plugin : "com.wanglei.plugin"

執行gradle assembleDebug編譯命令,輸出如下:


輸出了相應信息。

上面只是輸出簡單的信息,還沒有為我們的插件添加任何任務,下面為插件添加任務,新建MyTask.groovy類,編寫如下代碼:

 1 class MyTask extends DefaultTask{
 2
 3    String msg = 'default'
 4
 5    MyTask() {
 6        group '自定義任務'
 7        description '任務描述'
 8    }
 9
10    @TaskAction
11    void run() {
12        println "MyTask2__$msg"
13    }
14 }

新建了任務類,然后我們就可以在創建這個任務了:

 1 class Plugin1 implements Plugin<Project>{
 2
 3    @Override
 4    void apply(Project project) {
 5        println "我是自定義插件"
 6
 7        project.afterEvaluate {
 8            //創建任務
 9            MyTask task = project.tasks.create('myTask',MyTask)
10            //添加任務依賴關系
11            project.tasks.getByName('checkDebugManifest').dependsOn task
12        }
13    }
14 }

這里在afterEvaluate后也就是project評估配置完在創建我們的task,因為只有評估配置完才知道我們配置了什么啊,並且我們讓checkDebugManifest這個任務依賴於我們的任務,這個是個注意點,如果不加這個依賴我們執行gradle assembleDebug命令是不會執行我們的任務的,為什么?gradle assembleDebug就是讓安卓編譯出APK,編譯APK需要執行一連串任務,那一連串任務都是安卓插件定義好的依賴關系,而我們自己定義的任務跟人家那些任務有什么關系呢?當然不會執行我們的任務,添加上面依賴相當於把我們的任務加入進上面的任務圖中,使人家玩起來的時候能帶上我們玩玩。

接下來執行gradle assembleDebug命令:


很簡單,就不在多余解釋了。

下面我們看看怎么像插件中傳遞參數。
引入安卓插件我們可以配置一些信息,如下:

 1 android {
 2    compileSdkVersion 28
 3    defaultConfig {
 4        applicationId "com.wanglei55.gradlelearn"
 5        minSdkVersion 15
 6        targetSdkVersion 28
 7        versionCode 1
 8        versionName "1.0"
 9        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
10    }
11    buildTypes {
12        release {
13            minifyEnabled false
14            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
15        }
16    }
17 }

這些配置最后都是傳遞進安卓插件的,我們也可以這樣玩玩。

定義兩個類:

1 class AExtensions{
2    def compileSdkVersion
3 }
4
5 class BExtensions {
6   def  applicationId
7 }

然后我們就可以為插件創建擴展了:

 1 class Plugin1 implements Plugin<Project>{
 2
 3    @Override
 4    void apply(Project project) {
 5        println "我是自定義插件"
 6        //創建擴展
 7        project.extensions.create('aExtensions',AExtensions)
 8        //為aExtensions創建擴展
 9        project.aExtensions.extensions.create('bExtensions',BExtensions)
10        //gradle分析完成之后
11        project.afterEvaluate {
12            //獲取我們配置的compileSdkVersion與applicationId並打印
13            println project.aExtensions.compileSdkVersion
14            println project.aExtensions.bExtensions.applicationId
15            //創建任務
16            MyTask task = project.tasks.create('myTask',MyTask){
17                //為任務中msg賦值
18                msg = project.aExtensions.bExtensions.applicationId
19            }
20            //添加任務依賴關系
21            project.tasks.getByName('checkDebugManifest').dependsOn task
22        }
23    }
24 }

我們在引入插件的時候就可以為插件配置一些參數了:

1 apply plugin : "com.wanglei.plugin"
2
3 aExtensions{
4    compileSdkVersion 29
5    bExtensions {
6        applicationId "com.wl.gradle"
7    }
8 }

執行gradle assembleDebug命令:


到此自定義插件的各個方面就介紹完了,其實都不難。

八、總結

零零散散寫了一大篇,介紹了Gradle的各個方面,有些地方我自己都覺得寫的太啰嗦,既然寫出來了就這樣吧,Gradle作為構建工具或者框架或者腳本我們有必要深入了解一些,為我們更高效的構建自己工程打好基礎,學習Gradle一定要自己學會查閱文檔,我剛接觸Gradle的時候build.gradle文件都不敢動一個冒號,生怕出問題,配置的什么完全不懂,沒辦法只能自己硬着頭皮一點點學習,到現在不敢說精通起碼很多方面我明白是干什么的了,還有一點我們不是在配置腳本是在寫代碼背后都有對應的類,好了,不啰嗦了。

本篇到此結束,希望對你有用。

 

第一時間獲取最新博文可關注個人公眾號,那里閱讀體驗更好:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM