gradle學習總結,偷窺android studio的底褲

原文來自微涼一季的博客http://jijiaxin89.com/2015/08/29/gradle-use-note/

用過android studio的對gradle應該都不陌生了,gradle文件的基本配置大同小異,略做了解使用應該是沒什麼問題了。但是深入細緻的瞭解一下對於理解項目還是很有幫助的,尤其是遇到一些配置複雜的github項目,不瞭解gradle可能會遇到跑不起來又束手無策的情形。下面對gradle相關知識、用法做一下總結。

DSL (domain specific language)

即所謂領域專用語言,其基本思想是“求專不求全”,不像通用目的語言那樣目標範圍涵蓋一切軟件問題,而是專門針對某一特定問題的計算機語言。
-DSL之於程序員正如伽南地之於以色列人,是最初也是最終的夢想。幾乎自計算機發明伊始,人們就開始談論DSL使用DSL了。
-前幾年迅速走紅的Ruby on Rails就被譽爲“Web開發領域專用語言”

DSL 約等於 整潔的代碼

從概念上說,程序的編寫過程就是把業務領域中的問題通過代碼或者程序模型表達出來:
計算機的程序模型較爲單一(歸根結底都是運算和存儲)
在面向對象技術成爲主流的今天,通常情況下,計算機程序不太可能做到與業務領域中的概念一致,或者具有某些直覺的對應。因此,軟件的修改和可維護性並沒有想象中的容易。我們必須不斷地將業務領域中的概念轉換成相應的代碼模型,然後再進行修改。這種間接性直接造成了軟件的複雜度。
而DSL的主要目的就是要消除這樣的複雜度(或者說,以構造DSL的複雜度代替這種複雜度),DSL就要是要以貼近業務領域的方式來構造軟件。因此,DSL的簡潔性往往是一種思維上的簡潔性,使我們不用費太多的氣力就能看懂代碼所對應的業務含義。

DSL多以文本代碼的形式出現

多年來軟件工程實踐表明文本代碼是最有效率的編輯形式。但是一些特殊領域,文本代碼並不是最佳的表現形式,爲了更好的貼近業務領域中的概念,我們可能會選擇使用一些圖形化的DSL。如:[DSM(Domain Specific Modeling)工具GEMS(Generic Eclipse Modeling System)中就大量地使用了不同的圖形化的DSL來表述系統的各個不同側面。

Gradle向我們提供了一整套DSL,所以在很多時候我們寫的代碼似乎已經脫離了groovy,但是在底層依然是執行的groovy
爲了從命令行運行gradle測試樣例,首先

配置環境變量

  1. 創建變量名:GRADLE_HOME ,變量值:
    C:\Users\jjx.gradle\wrapper\dists\gradle-2.5-all\d3xh0kipe7wr2bvnx5sk0hao8\gradle-2.5
  2. 加入path
    ;%GRADLE_HOME%\bin;
  3. 檢查,如下就ok。
C:\Users\jjx>gradle -v

------------------------------------------------------------
Gradle 2.5
------------------------------------------------------------

Build time:   2015-07-08 07:38:37 UTC
Build number: none
Revision:     093765bccd3ee722ed5310583e5ed140688a8c2b

Groovy:       2.3.10
Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM:          1.7.0_75 (Oracle Corporation 24.75-b04)
OS:           Windows 7 6.1 amd64

初嘗禁果

在d:盤建個文件夾Test,文件夾下建個文件build.gradle
打開文件,寫個簡單的代碼

task helloWorld << {
    println "Hello World"
}

打開cmd, d: 執行gradle helloWorld

C:\Users\jjx>d:

D:\>cd Test

D:\Test>gradle helloWorld
:helloWorld
Hello World

BUILD SUCCESSFUL

Total time: 3.714 secs
D:\Test>

同時,這時候發現已經自動在Test目錄下創建了.gradle文件。
上面helloWorld後的“<<”表示追加的意思,即向helloWorld中加入執行過程
使用doLast可以達到同樣效果

task helloWorldTwo {
   doLast {
      println 'helloWorldTwo'}
}

如果需要向Task的最前面加入執行過程,我們可以使用doFirst:

task helloWorldThree {
   doFirst {
      println 'helloWorldThree'}
}

耶,懂了!

關於 task

Gradle將當前目錄下的build.gradle文件作爲項目的構建文件。在上面的例子中,我們創建了一個名爲helloWorld的Task,在執行gradle命令時,我們指定執行這個helloWorld Task。這裏的helloWorld是一個DefaultTask類型的對象,這也是定義一個Task時的默認類型,當然我們也可以顯式地聲明Task的類型,甚至可以自定義一個Task類型
下面再看一個小case:
我們在Test文件夾下建一個src目錄,建一個dst目錄,src目錄下建立一個文件,命名爲here.txt
然後在build.gradle中append一個task:

task helloWorld << {
    println "Hello World"
}
task copyFile(type: Copy){
    from "src"
    into "dst"
}

代碼中(type:Copy)就是“顯式地聲明Task的類型”,helloworld沒有就是默認得DefaultTask類型咯。

然後cmd中執行命令

D:\Test>gradle copyFile
:copyFile

BUILD SUCCESSFUL

Total time: 2.645 secs
D:\Test>gradle copyFile
:copyFile

BUILD SUCCESSFUL

Total time: 3.361 secs
D:\Test>

好了! here.txt也跑到dst中去啦!簡單吧!

加一把火,我們來看一下當前目錄下(即Test目錄文件,這裏也可以將這個目錄理解爲一個project,不過還沒寫有生產力的代碼,哈哈哈)定義的task

D:\Test>gradle tasks
:tasks

------------------------------------------------------------
All tasks runnable from root project
------------------------------------------------------------

Build Setup tasks
-----------------
init - Initializes a new Gradle build. [incubating]
wrapper - Generates Gradle wrapper files. [incubating]

Help tasks
----------
components - Displays the components produced by root project 'Test'. [incubating]
dependencies - Displays all dependencies declared in root project 'Test'.
dependencyInsight - Displays the insight into a specific dependency in root project 'Test'.
help - Displays a help message.
model - Displays the configuration model of root project 'Test'. [incubating]
projects - Displays the sub-projects of root project 'Test'.
properties - Displays the properties of root project 'Test'.
tasks - Displays the tasks runnable from root project 'Test'.

Other tasks
-----------
copyFile
helloWorld

To see all tasks and more detail, run gradle tasks --all

To see more detail about a task, run gradle help --task <task>

BUILD SUCCESSFUL

Total time: 2.895 secs
D:\Test>

是不是很清晰呢,展示了各種task類型,task的作用,以及另外兩個task相關的命令,相信聰明的你也一看就懂。

Gradle本身的領域對象主要有Project和Task。
Project爲Task提供了執行上下文,所有的Plugin要麼向Project中添加用於配置的Property,要麼向Project中添加不同的Task。
一個Task表示一個邏輯上較爲獨立的執行過程,比如編譯Java源代碼,拷貝文件,打包Jar文件,甚至可以是執行一個系統命令或者調用Ant。另外,一個Task可以讀取和設置Project的Property以完成特定的操作。是不是很屌的樣子。
task關鍵字其實是一個groovy中方法的調用,該方法屬於Project,而大括號之間的內容則表示傳遞給task()方法的一個閉包。

task間的依賴

task之間是可以存在依賴關係,比如TaskA依賴TaskB,那麼在執行TaskA時,Gradle會先執行TaskB,再執行TaskA。我們可以在定義一個Task的同時聲明它的依賴關係:

task helloWorldFour(dependsOn:helloWorldThree) << {
    println 'hellohelloWorldFour'
}

或者

task helloWorldFour << {
    println 'hellohelloWorldFour'
}
helloWorldFour.dependsOn helloWorldThree
可配置的task

一個Task除了執行操作之外,還可以包含多個Property,其中有Gradle爲每個Task默認定義了一些Property,比如description,logger等。
另外,每一個特定的Task類型還可以含有特定的Property,比如Copy的from和to等。
當然,我們還可以動態地向Task中加入額外的Property。在執行一個Task之前,我們通常都需要先設定Property的值。

task helloWorld << {
   description = "this is helloWorld" 
   println description
}

或者通過調用Task的configure()方法完成Property的設置:

task helloWorld << {
   println description
}
helloWorld.configure {
   description = "this is helloWorld" 
}
花式task
task showDescription1 << {
   description = 'this is task showDescription'
   println description
}

task showDescription2 << {
   println description
}
showDescription2.description = 'this is task showDescription'

task showDescription3 << {
   println description
}
showDescription3 {
   description = 'this is task showDescription'
}

對於每一個Task,Gradle都會在Project中創建一個同名的Property,所以我們可以將該Task當作Property來訪問,showDescription2便是這種情況。另外,Gradle還會創建一個同名的方法,該方法接受一個閉包,我們可以使用該方法來配置Task,showDescription3便是這種情況。
耶!簡單吧

關於 Groovy

Gradle是一種聲明式的構建工具。
在執行時,Gradle並不會一開始便順序執行build.gradle文件中的內容,而是分爲兩個階段,第一個階段是配置階段,然後纔是實際的執行階段。
配置階段,Gradle將讀取所有build.gradle文件的所有內容來配置Project和Task等,比如設置Project和Task的Property,處理Task之間的依賴關係等。
Gradle的DSL只是Groovy語言的內部DSL,也必須遵循Groovy的語法規則。
Groovy語言中的兩個概念,一個是Groovy中的Bean概念,一個是Groovy閉包的delegate機制。

bean

Groovy中的Bean和Java中的Bean有一個很大的不同,即Groovy動態的爲每一個字段都會自動生成getter和setter,並且我們可以通過像訪問字段本身一樣調用getter和setter

class GroovyBeanExample {
   private String name
}

def bean = new GroovyBeanExample()
bean.name = 'this is name' //實際調用的是"bean.setName('this is name')"
println bean.name  //實際調用的是
"println bean.getName()"

採用像直接訪問的方式的目的是爲了增加代碼的可讀性,使它更加自然,而在內部,Groovy依然
是在調用setter和getter方法。

閉包的delegate機制

簡單來說,delegate機制可以使我們將一個閉包中的執行代碼的作用對象設置成任意其他對象。

class Child {
   private String name
}
class Parent {
   Child child = new Child();
   void configChild(Closure c) {
      c.delegate = child
      c.setResolveStrategy Closure.DELEGATE_FIRST
      c()
   }
}

def parent = new Parent()
parent.configChild {
name = "child name"
}

println parent.child.name

在上面的例子中,當調用configChild()方法時,並沒有指出name屬性是屬於Child的,但是它的確是在設置Child的name屬性。
事實上光從該方法的調用中,我們根本不知道name是屬於哪個對象的,你可能會認爲它是屬於Parent的。
真實情況是,在默認情況下,name的確被認爲是屬於Parent的,但是我們在configChild()方法的定義中做了手腳,使其不再訪問Parent中的name(Parent也沒有name屬性),而是Child的name。
在configChild()方法中,我們將該方法接受的閉包的delegate設置成了child,然後將該閉包的ResolveStrategy設置成了DELEGATE_FIRST。這樣,在調用configChild()時,所跟閉包中代碼被代理到了child上,即這些代碼實際上是在child上執行的。
此外,閉包的ResolveStrategy在默認情況下是OWNER_FIRST,即它會先查找閉包的owner(這裏即parent),如果owner存在,則在owner上執行閉包中的代碼。這裏我們將其設置成了DELEGATE_FIRST,即該閉包會首先查找delegate(本例中即child),如果找到,該閉包便會在delegate上執行。

聯想gradle中聲明的方法

在使用Gradle時,我們並沒有像上面的parent.configChild()一樣指明方法調用的對象,而是在build.gradle文件中直接調用task(),apply()和configuration()等方法。這是
因爲在沒有說明調用對象的情況下,Gradle會自動將調用對象設置成當前Project。
比如調用apply()方法和調用project.apply()方法的效果是一樣的。查查Gradle的Project文檔,你會發現這些方法都是Project類的方法。
對於configurations()方法,該方法實際上會將所跟閉包的delegate設置成ConfigurationContainer,然後在該ConfigurationContainer上執行閉包中的代碼。再比如,dependencies()方法,該方法會將所跟閉包的delegate設置成DependencyHandler。

終於到了gradle

自定義Property

Gradle還爲我們提供了多種方法來自定義Project的Property。

在build.gradle文件中定義Property

添加一個名爲property1的Property:

ext.property1 = "this is property1"

或者採用閉包的形式

ext {
   property2 = "this is property2"
}

定義了Property後,使用這些Property時我們則不需要ext,而是可以直接訪問:

task showProperties << {
   println property1
   println property2
}

還可以在執行命令行的時候加屬性

task showCommandLieProperties << {
   println property3
}
//以下是cmd中執行命令
gradle -Property3="this is property3" showCommandLieProperties

//通過JVM系統參數定義Property,與java類似,但是前面要約定以“org.gradle.project”爲前綴
gradle -D org.gradle.project.property3="this is another property3" showCommandLieProperties

此外還可以通過環境變量來爲Gradle設置Property,但是每一個Property都需要以“ORG_GRADLE_PROJECT_”爲前綴:

ORG_GRADLE_PROJECT_property3="this is yet another property3"

Gradle 的 Plugin

Gradle最常用的Plugin便是java Plugin了。和其他Plugin一樣,java Plugin並沒有什麼特別的地方,只是向Project中引入了多個Task和Property。當然,java Plugin也有比較與衆不同的地方,其中之一便是它在項目中引入了構建生命週期的概念,就像Maven一樣。但是,和Maven不同的是,Gradle的項目構建生命週期並不是Gradle的內建機制,而是由Plugin自己引入的。

依賴管理

一個項目總會依賴於第三方,要麼是一個第三方類庫,要麼是自己開發的另一個module
配置Gradle的Repository,就是告訴Gradle在什麼地方去獲取這些依賴

repositories {
   mavenCentral()
   jCentral()
}

jCentral()是大於mavenCentral()的一個倉庫,現在是studio默認的倉庫

Gradle對依賴進行分組,允許編譯時使用一組依賴,運行時使用另一組依賴。每一組依賴稱爲一個Configuration,在聲明依賴時,我們實際上是在設置不同的Configuration。

要定義一個Configuration,我們可以通過以下方式完成:studio一般不需要設置,應該是有默認的,即爲classpath

configurations {
   myDependency
}

通過dependencies()方法向myDependency中加入實際的依賴項:

dependencies {
//下面的myDependency是關鍵
   myDependency 'org.apache.commons:commons-lang3:3.0'
}
//類似studio中的classpath
dependencies {
   classpath 'com.android.tools.build:gradle:1.3.0'
}
//還有 這裏的compile,testCompile
dependencies {
    compile project(':library')
    compile 'com.android.support:recyclerview-v7:22.2.1'
    compile 'com.android.support:design:22.2.1'
    compile 'com.evernote:android-intent:1.0.1'
    testCompile 'junit:junit:4.8.2' 
}

myDependency,classpath,compile,testCompile都是Configuration(一組依賴)。
除了myDependency都不使我們定義的,爲啥呢,android Plugin會自動定義compile和testCompile分別用於編譯Java源文件和編譯Java測試源文件。classpath應該是用於所有,我類推的。
Gradle還允許我們聲明對其他Project或者文件系統的依賴。

dependencies {
//library是另一個module的名字
   compile project(':library')
}

對於本地文件系統中的Jar文件,我們可以通過以下方式聲明對其的依賴:

dependencies {
   //java
   compile files('spring-core.jar', 'spring-aap.jar')
   compile fileTree(dir: 'deps', include: '*.jar')
   //studio中一般這麼寫
   compile fileTree(dir: 'libs', include: ['*.jar'])
}

構建多module的project

Gradle爲每個build.gradle都會創建一個相應的module領域對象,在編寫Gradle腳本時,我們實際上是在操作諸如module這樣的Gradle領域對象。在多module的項目中,我們會操作多個module領域對象。Gradle提供了強大的多module構建支持
要創建多module的Gradle項目,我們首先需要在根(Root)Project中加入名爲settings.gradle的配置文件,該文件應該包含各個子module(其實就是一個子project)的名稱。如setting.gradle中:

include 'library', 'demo'

類似module(子project)的build.gradle,(Root)Project也有自己的build.gradle,在裏面通常設置:

allprojects {
    repositories {
        jcenter()
    }
    //通常studio項目沒有,咱自己加的
   apply plugin: 'idea'
   task allTask << {
      println project.name
   }
}

allprojects()方法將repositories配置一次性地應用於所有的module(子Project)和root-project本身,當然也包括定義的Task,這個task配置到所有module裏面了和root-project。

subprojects()方法用於配置所有的子Project(不包含根Project)

步入巔峯

Gradle本身只是一個架子,真正起作用的是Task和Plugin。

自定義Task

Gradle中的Task要麼是由不同的Plugin引入的,要麼是我們自己在build.gradle文件中直接創建的。

  • 在build.gradle文件中直接定義
    需要定義的Task類型不多時
    Gradle其實就是groovy代碼,所以在build.gradle文件中,我們便可以定義Task類。
class HelloWorldTask extends DefaultTask {
    //@Optional,表示在配置該Task時,message是可選的。
    @Optional
    String message = 'I am jjx'
    //@TaskAction表示該Task要執行的動作,即在調用該Task時,hello()方法將被執行
    @TaskAction
    def hello(){
        println "hello world $message"
    }
}

//hello使用了默認的message值
task hello(type:HelloWorldTask)

//重新設置了message的值
task helloOne(type:HelloWorldTask){
   message ="I am a android developer"
}
  • 在當前工程中定義Task類型
    只能應用在當前module中,沒什麼卵用,下面是全局可用的
  • 在單獨的項目中定義Task類型
    項目中存在大量的自定義Task類型時,在另外的一個gradle文件中定義這些Task,然後再apply到build.gradle文件中。
    可以參考印象筆記的demo:https://github.com/evernote/evernote-sdk-android
    中的:
//這是插件
apply plugin: 'com.android.application'
//這裏gradle-quality.gradle就是另外單獨定義了task的gradle
apply from: '../build-config/gradle-quality.gradle'
自定義Plugin

與自定義task極其類似,可以類推理解,也是有3中方式定義,只是代碼不一樣:

apply plugin: DateAndTimePlugin

dateAndTime {
    timeFormat = 'HH:mm:ss.SSS'
    dateFormat = 'MM/dd/yyyy'
}

class DateAndTimePlugin implements Plugin<Project> {
    //該接口定義了一個apply()方法,在該方法中,我們可以操作Project,
    //比如向其中加入Task,定義額外的Property等。
    void apply(Project project) {
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)

        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}
//每個Gradle的Project都維護了一個ExtenionContainer,
//我們可以通過project.extentions進行訪問
//比如讀取額外的Property和定義額外的Property等。
//向Project中定義了一個名爲dateAndTime的extension
//並向其中加入了2個Property,分別爲timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}

每一個自定義的Plugin都需要實現Plugin接口,除了給Project編寫Plugin之外,我們還可以爲其他Gradle類編寫Plugin。
以上是在build.gradle文件中直接定義Plugin,還可以在當前工程中、單獨的項目中創建Plugin,一般情況不需要了解。

怎麼樣,通過以上學習,再看android studio的gradle代碼是不是小case了呢!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章