Groovy 和 Gradle (Android Studio)基礎

1 參考

2 資源

3 基本

  • Gradle雖然使用的是Groovy語言,可是其其實是基於Java語言的,因此在編寫腳本的時候能夠直接使用Java語言。

4 Groovy語言基礎

雖然在編寫腳本的時候能夠直接使用java代碼,可是使用腳本化語言更加的清晰。android

Groovy開發環境配置

參看:http://www.jianshu.com/p/777cc61a6202git

前置基礎知識

  • Groovy註釋標記和Java同樣,支持//或者/**/ github

  • Groovy語句能夠不用分號結尾web

  • Groovy中支持動態類型,即定義變量的時候能夠不指定其類型,Groovy中,變量定義可使用關鍵字def,注意,雖然def不是必須的,可是爲了代碼清晰,建議仍是使用def關鍵字api

  • Groovy中每個對象的成員變量會自動爲它生成gettersetter函數,因此咱們能夠直接調用其成員變量或者對應方法。

var0 = "no def no type"
    def var1 = 1   //能夠不使用分號結尾 
    def var2 = "I ama person"  
    def int x = 1  //變量定義時,也能夠直接指定類型
  • 函數定義時,參數的類型也能夠不指定,好比
String testFunction(arg1,arg2){//無需指定參數類型  
    ...  
    }
  • 除了變量定義能夠不指定類型外,Groovy中函數的返回值也能夠是無類型的。好比:
//無類型的函數定義,必須使用def關鍵字 
    def  nonReturnTypeFunc(){  
        last_line   //最後一行代碼的執行結果就是本函數的返回值 
    }  
    //若是指定了函數返回類型,則可沒必要加def關鍵字來定義函數 
    String getString(){  
       return"I am a string"  
    }
  • Groovy中的函數調用能夠不加括號,可是在定義函數的時候不能夠省略,不然會被編譯器認爲是變量
println("123") ->    println "123"

對字符串的支持

  • 單引號」,表示嚴格的字符串,等同於java的String類型
  • 雙引號」「,與普通字符串的區別是會對 $表達式先求值
def var = 123
println "i am a $var"  // 最後打印 i am a 123
  • 三個引號」’xxxx」’中的字符支持隨意換行
str = ''' 1223 122 556 '''

五種特殊運算符

  • ?.運算符,至關於不爲空,則執行某個操做
list?.size()
//等同於
if(list != null){
    list.size()
}
  • *.運算符,用於對集合類型的對象的每一個元素執行相同的方法,返回值爲大小和該集合元素相同的List。
def smaple = ['123','1','12345']
println smaple*.size()
  • .&方法做爲一個閉包參數傳遞
def list = {1, 2, 3}

list.each{
    println it
}

int printNumber(int number){
    println number
}

//做爲方法做爲一個閉包傳遞
list.each(this.&printNumber)
  • .@直接調用字段,在groovy中直接使用.調用的是對應的getter方法
class Todo {  
    String name  
    def getName() {  
      println "Getting Name"  
      name  
    }  
}  

def todo = new Todo(name: "Jim")  
println todo.name  
println todo.@name  

========  
result:  
Getting Name  
Jim  
Jim
  • ?:替代Java的三目運算符
String name=  person.name ? person.name : 'unknown'// java的寫法 

def name2= person.name ?: "unknown" // Groovy 的寫法

Groovy中的數據類型

主要介紹一下三種數據類型:

  • Java中的基本數據類型
  • Groovy中的容器類
  • 閉包

基本數據類型

Groovy做爲動態語言,其中一切事物都是對象,因此即便是基本數據類型,其也是被轉化爲相應的對象的。

def int var = 123
println var.getClass().getName()  //打印的結果爲 java.lang.Integer

容器類

Groovy中的容器類就三種:

  • List:鏈表,其底層對應Java中的List接口,通常用ArrayList做爲真正的實現類。
  • Map:鍵-值表,其底層對應Java中的LinkedHashMap。
  • Range:範圍,它實際上是List的一種拓展。
List
//定義list變量,使用[]方式來定,其元素能夠是任何的對象
def testList = [123, "string", true]  

//元素的存儲,注意咱們不須要擔憂越界的問題,當越界了,groovy會自動增長list的容量
assert testList[1] == "string"
assert testList[3] == null  //此時第4個元素是不存在的,能夠自動增長

//直接設置越界索引的元素的值
testList[999] = 999

//打印testList的大小
println testList.size    //結果爲 1000
Map
//變量的定義,使用[key:value,......]的方式定義,value能夠是任何類型的對象,key可使用''或者""包裹起來,若是不作處理則表示默認爲字符串
def map = [key1: "value1", "321": true, 123:"value2"]  //key1被默認爲'key1'

//若是key要表示特定的類型或外部定義的變量的值,則使用"()"包裹起來。
def test = "999"
def map1 = [(test):"1000"]

//元素的存取,兩種方式, map.keyName map[keyName]
println map."key1" + " " + map."123"   //結果爲 value1 null, 注意使用map."123" 方式獲取到的值爲null
println map1[test] + " " + map[123]    //1000 value2,使用map[123]才能正確的獲取到key值

//新增/修改元素 map.keyName = value
map."321" = false
println map."321"

map1["test2"] = "I an test 2"
println map1["test2"]
Range

Range繼承自List,一種更方便的有序集合類型。

//def
def range = 10..1000  //表示從10到1000之間的元素
def range2 = 10..<1000 //使用'<'表示包含最後一個元素值

//打印
println range.from
println range.get(2)
println range.to

閉包

閉包其表示一段能夠執行的代碼塊,看起來是函數與對象的結合體,閉包的最後一行是閉包的返回值,關於閉包能夠參考:

閉包的基礎知識
  • 閉包的定義樣式
def closure = {
   parm1, parm2 ->     // ->前是閉包的入參,若是沒有參數,則能夠直接省略這一行代碼

   code
   ...
   code                //最後一行代碼,無論有沒有return關鍵詞,都表示閉包的返回值            
}

//example
def testClosure = {
    x, y ->
    x = x + y
    x
}
  • 閉包的調用
//使用closure.call
def val = testClosure.call(3, 4)
println val

//使用closure(參數)
def val2 = testClosure(5, 6)
println val2
  • 當閉包沒有參數時,具備一個隱含參數it,它的做用和this類似。
def itClosure = {"hello kitty $it"}

//其等同於:
// def itClosure = {it -> "hello kitty $it "}
println itClosure('funny')

//可是若是在定義閉包時,顯式的使用 ->,並且前面沒有任何的參數,則表示正真沒有任何參數,若是調用時傳入參數會報錯
def noParamClosure = { -> "no param"}

println noParamClosure('it')//該行代碼會報錯
閉包在groovy中的使用
  • 閉包做爲函數的最後一個參數時,在調用函數時,它的」()」號能夠省略
def simpleFunction(int x, String str, Closure c){
    c.call(x, str)  //在函數中調用閉包,並使用它做爲返回值
}

def val = simpleFunction 4, " is a four", {x, str -> x + str}

println val
  • 閉包做爲函數的參數傳入時,其閉包內部具體可以使用的參數取決於該方法回調閉包時傳入的參數個數和類型,也就是閉包是上下文強相關的,因此當Groovy Api 和 Gradle Api等使用閉包做爲參數時,很是有必要去閱讀文檔,肯定閉包的參數。
//仍是上面的例子,使用閉包作爲參數
def simpleFunction(int x, String str, Closure c){
    c.call(x, str)  //注意此處調用閉包時傳遞了兩個參數,分別爲x, str
}

//咱們在調用該方法時,傳入閉包參數時,注意參數的個數,類型要與函數調用時保持一致
def val = simpleFunction 4, " is a four", {x, str -> x + str}  //此爲正常調用

//如下都會報錯,groovy.lang.MissingMethodException: No signature of method: ConsoleScript3$_run_closure2.call() is applicable for argument types:(java.lang.Integer, java.lang.String) values: [5, tsttt]
Possible solutions: any(), any(), doCall(調用時傳入的參數類型)......

def val1 = simpleFunction 5, "tsttt", {x-> x}                  
def val2 = simpleFunction 5, "tsttt", {->'hahahh'} 
def val3 = simpleFunction 5, "tsttt", {String str, String str2 -> str1 + str2}

Groovy綜合

xxx.groovy文件編譯成class文件

//在命令行執行以下命令,編譯成class文件
groovyc-d classes xxx.groovy

編譯成class以後可使用jd-gui工具反編譯成java類進行查看。

文件I/O操做

讀文件
//獲取文件對象
def file = new File('e:/source/groovy/test.groovy')
 //讀取每一行並打印
file.eachLine{
    String strLine -> println strLine
}
 //一次性獲取全部的文件內容
byte[] bytes = file.getBytes()   //注意此處能夠直接使用file.bytes
println bytes.length
 //使用閉包的方式用inputstream流來讀取,注意它的好處不用手動去關閉輸入流
file.withInputStream{
    is -> println(is.getText())
}
寫文件
//
def writeFile = new File('e:/source/groovy/test2.groovy')
//寫入數據,從新開始寫,原來的數據會被清除,若文件不存在,則自動建立 
writeFile.write("this is the first line") 

//添加數據到最末尾
writeFile.append("this is add by append")

//使用流從一個文件讀入寫入到這個文件,原文件中的數據會別清除
def readFile = new File('e:/source/groovy/test.groovy')
writeFile.withOutputStream{
    os -> 
    readFile.witInputStream{
        is ->
        os << is                        //重載了<< 符號,將輸入流的數據傳遞給輸出流
    }
}

未完待續。

5 Gradle

Gradle概述

  • Gradle是一種DSL(特定領域)腳本構建框架,編寫腳本使用的是Groovy語言(也能夠直接使用Java語言)。
  • 咱們在腳本中配置屬性時,本質是在使用GradleGroovyJava的API方法。
  • 咱們使用Gradle構建項目時必須使用相應的插件:
//咱們構建Android APP項目使用的插件是:
apply plugin: 'com.android.application'

//若是是構建一個library項目讓其餘的項目引用
apply plugin: 'com.android.library'

因此咱們在腳本中可以使用的特定的方法、屬性都是各類腳本決定的。

GradleAndroid Gradle Plugin的官方幫助文檔

基本組件

  • Project,每個項目對於Gradle都是一個Project,它能夠包含一些子sub-projects或者modules,每個project或者modules根目錄下面都有一個build.gradle文件與之對應,查看project命令:
gradle projects
  • Task,gradle中的每個task對應一個任務,全部的事務都以Task的形式存在,執行gradle腳本,實際上就是一個個task,gradle腳本被解析完成後是一個有向無循環的圖,因此若是咱們要自定義一些行爲,須要自定義task,或者將行爲依附到已有task中,若是使用自定義的task,還須要給其添加執行的依賴順序,查看task命令:
gradle tasks

//多項目查看某個項目下的tasks,或者直接cd 到想查看的項目目錄下面去,執行上面的命名。
gradle project_path:tasks
  • Configurations,項目的全部配置項(Configuration)的集合,每個Configuration都是一個文件的集合,包括dependencies(依賴項),artifacts(自定義添加的文件,如打包某些class等等),configuration能夠做爲Copy、Zip等task的from參數的值(參考DSL中Configuration, ConfigurationContainer, ArtifactHandler三個章節):
//拷貝complie 這個configuration的文件到指定目錄中
task copyAllDependencies(type: Sync) {
    //referring to the 'compile' configuration
    //拷貝全部編譯時依賴的文件如jar,aar等等
    from configurations.compile
    into 'build/output'
    exclude 'recyclerview-v7-24.0.0-alpha1.aar'  //過濾掉這個包
}

添加自定義的artifacts到指定的Configuration中:

//Configuration的申明:
configurations {
    testConfig

    //也能夠繼承
    //testConfig.extendsFrom(compile)
}

//壓縮so任務
task testZip(type:Zip){
    baseName = 'armeabi'
    println 'ready to copy!!!!'
    println(rootProject.projectDir.absolutePath + '\\app\\libs\\armeabi\\')
    from rootProject.projectDir.absolutePath + '\\app\\libs\\armeabi\\'
}


//關聯一個自定義的文件Map
def map = [file:new File(rootProject.projectDir.absolutePath + '/app/debug.jks')]
artifacts{
    testConfig map
    //這裏也能夠直接傳入AbstractArchiveTask類型的task(如Jar,Zip),也能夠直接傳入File對象
    //testConfig new File(rootProject.projectDir.absolutePath + '/app/debug.jks')
    //testConfig testZip
}

//使用配置項,用於Copy task的from參數
//copying all dependencies attached to 'compile' into a specific folder
task copyAllDependencies(type: Sync) {
    //referring to the 'compile' configuration
    //注意不能直接傳遞allArtifacts對象,要調用其getFiles()方法
    from configurations.testConfig.allArtifacts.getFiles()
    into 'build/output'
}

Gradle執行流程

流程分析

先看一個簡單的執行流程圖:

Gradle執行流程圖

  • 建立Gradle對象,在執行任何一條gradle命令時,都會先建立一個全局惟一的Gradle對象,該對象能夠保存一些全局的基本的值。

  • 建立Setting對象,Gradle每次構建都會建立一個Setting對象,若是存在settings.gradle文件則會去解析該文件配置Setting對象。一個setting.gradle文件對應一個Setting對象,當項目依賴編譯多個項目時,就須要在settings.gradle中include須要編譯的與依賴的項目。後面看DSL文檔會Setting對象持有一個rootProject對象,注意該對象的類型爲ProjectDescriptor,而不是Project。

  • 建立Project對象,在配置完Setinng對象後,開始建立Project對象,默認的建立的順序:先建立rootProject 對象(注意這裏的類型爲Project),子模塊的對象的建立依照settings.gradle文件中include的順序進行。

  • 解析上一步建立對象對應的build.gradle文件,按序添加task執行順序,在解析完全部的Project對象後,最終會生成一個有向無環圖表示全部須要執行task的執行順序,注意解析文件是在其對應的Project對象後進行,而不是全部的Project對象建立後再進行。

  • 依據上一步生成的有向無環圖,執行腳本。

DSL中對Project生命週期的描述:

Lifecycle

- There is a one-to-one relationship between a Project and a build.gradle file. During build initialisation, Gradle assembles a Project object for each project which is to participate in the build, as follows:

- Create a Settings instance for the build.
- Evaluate the settings.gradle script, if present, against the Settings object to configure it.
- Use the configured Settings object to create the hierarchy of Project instances.
- Finally, evaluate each Project by executing its build.gradle file, if present, against the project. The projects are evaluated in breadth-wise order, such that a project is evaluated before its child projects. This order can be overridden by calling Project.evaluationDependsOnChildren() or by adding an explicit evaluation dependency using Project.evaluationDependsOn(java.lang.String).

流程鉤子函數

在上面步驟中的3-4之間,Gradle給咱們提供了一個鉤子函數gradle.beforeProject,能夠作一些額外的自定義操做:

//若是該鉤子在setting.gradle中申明,每個對象建立都會執行一次
gradle.beforeProject{rootProject ->
    println 'in xxx build.gradle beforeProject called!!!'
    println rootProjct.rootDir
}

一樣在4-5之間也可使用鉤子函數gradle.taskGraph.whenReady,該函數在創建完有向圖後執行腳本以前調用:

gradle.taskGraph.whenReady {
    println 'in xxx build.gradle whenReady called!!!'
    if(rootProject == project){   //若在rootProject的build.gradle中調用該方法,則會打印
        println 'xxx project = rootProject'
    }
}

在執行完腳本以後,gradle給咱們提供了一個gradle.buildFinished鉤子函數供咱們作一些自定義操做:

gradle.buildFinished {
    println 'in xxx build.gradle buildFinished called!!!'
}

注意上面的所列舉的三個鉤子函數傳遞的參數都是閉包,是能夠在多個.gradle文件中調用的,能夠各自實現不一樣的閉包邏輯,可是決定其什麼時候生效取決於調用該方法的.gradle文件是否已經被解析,文件中調用的鉤子函數是否被添加到監聽器列表中,好比在rootProjectbuild.gradle中是沒法監聽到它本身的beforeProject鉤子的,由於調用該鉤子函數時,build.gradle都尚未被解析,因此咱們須要在setting.gradle中聲明鉤子。

經常使用Gradle對象

Gradle對象

Gradle對象每次執行腳本時,建立的一個惟一的對象,它表示一次腳本的執行,它一直存在於腳本執行的各個階段。如下是它屬性的截圖,前面說到的不少鉤子函數都屬於這個類。
Gradle對象屬性截圖

Settings對象

肯定本次build須要配置Project以及執行順序和依賴關係,多個項目關聯編譯時須要用到這個對象,全局惟一,在Android Studio項目一般根目錄下存在一個settings.gradle文件,咱們能夠經過它來配置Settings對象。
Setting對象

經常使用Gradle命令

參考文檔: Gradle命令行用戶指導:https://docs.gradle.org/current/userguide/gradle_command_line.html
- 查看Gradle版本號

gradle -v
  • 執行task
gradle task1
//執行多個task
gradle task1 task2
//靜默執行task,還有其餘三種模式
gradle -q task
//過濾task2不執行,加-x參數
gradle task1 -x task2
  • 顯示全部的project
gradle projects
  • 顯示全部的task
gradle tasks
//顯示全部
  • 顯示Gradle的GUI
gradle --gui
  • 顯示Gradle幫助
gradle --help

特性