在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Android/ Gradle 編程模型及 API 實(shí)例詳解
Groovy 介紹
腳本類、文件 I/O 和 XML 操作
更多
一些前提知識(shí)
Gradle 工作流程
基本組件
題外話
總結(jié)
Gradle 編程模型及 API 實(shí)例詳解
閉包
Gradle 介紹
閑言構(gòu)建
Groovy 中的數(shù)據(jù)類型

Gradle 編程模型及 API 實(shí)例詳解

Gradle 編程模型及 API 實(shí)例詳解

希望你在進(jìn)入此節(jié)之前,一定花時(shí)間把前面內(nèi)容看一遍?。?!

https://docs.gradle.org/current/dsl/ <==這個(gè)文檔很重要

Gradle 基于 Groovy,Groovy 又基于 Java。所以,Gradle 執(zhí)行的時(shí)候和 Groovy 一樣,會(huì)把腳本轉(zhuǎn)換成 Java 對(duì)象。Gradle 主要有三種對(duì)象,這三種對(duì)象和三種不同的腳本文件對(duì)應(yīng),在 gradle 執(zhí)行的時(shí)候,會(huì)將腳本轉(zhuǎn)換成對(duì)應(yīng)的對(duì)端:

  • Gradle 對(duì)象:當(dāng)我們執(zhí)行 gradle xxx 或者什么的時(shí)候,gradle 會(huì)從默認(rèn)的配置腳本中構(gòu)造出一個(gè) Gradle 對(duì)象。在整個(gè)執(zhí)行過程中,只有這么一個(gè)對(duì)象。Gradle 對(duì)象的數(shù)據(jù)類型就是 Gradle。我們一般很少去定制這個(gè)默認(rèn)的配置腳本。
  • Project 對(duì)象:每一個(gè) build.gradle 會(huì)轉(zhuǎn)換成一個(gè) Project 對(duì)象。
  • Settings 對(duì)象:顯然,每一個(gè) settings.gradle 都會(huì)轉(zhuǎn)換成一個(gè) Settings 對(duì)象。

注意,對(duì)于其他 gradle 文件,除非定義了 class,否則會(huì)轉(zhuǎn)換成一個(gè)實(shí)現(xiàn)了 Script 接口的對(duì)象。這一點(diǎn)和 3.5 節(jié)中 Groovy 的腳本類相似

當(dāng)我們執(zhí)行 gradle 的時(shí)候,gradle 首先是按順序解析各個(gè) gradle 文件。這里邊就有所所謂的生命周期的問題,即先解析誰,后解析誰。圖 27 是 Gradle 文檔中對(duì)生命周期的介紹:結(jié)合上一節(jié)的內(nèi)容,相信大家都能看明白了。現(xiàn)在只需要看紅框里的內(nèi)容:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/29.jpg" alt="" />

Gradle 對(duì)象

我們先來看 Gradle 對(duì)象,它有哪些屬性呢?如圖 28 所示:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/30.jpg" alt="" />

我在 posdevice build.gradle 中和 settings.gradle 中分別加了如下輸出:

//在 settings.gradle 中,則輸出"In settings,gradle id is"
println "In posdevice, gradle id is " + gradle.hashCode() 
println "Home Dir:" + gradle.gradleHomeDir
println "User Home Dir:" + gradle.gradleUserHomeDir
println "Parent: " + gradle.parent

得到結(jié)果如圖 29 所示:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/31.jpg" alt="" />

  • 你看,在 settings.gradle 和 posdevice build.gradle 中,我們得到的 gradle 實(shí)例對(duì)象的 hashCode 是一樣的(都是 791279786)。
  • HomeDir 是我在哪個(gè)目錄存儲(chǔ)的 gradle 可執(zhí)行程序。
  • User Home Dir:是 gradle 自己設(shè)置的目錄,里邊存儲(chǔ)了一些配置文件,以及編譯過程中的緩存文件,生成的類文件,編譯中依賴的插件等等。

Gradle 的函數(shù)接口在文檔中也有。

Project 對(duì)象

每一個(gè) build.gradle 文件都會(huì)轉(zhuǎn)換成一個(gè) Project 對(duì)象。在 Gradle 術(shù)語中,Project 對(duì)象對(duì)應(yīng)的是 Build Script。

Project 包含若干 Tasks。另外,由于 Project 對(duì)應(yīng)具體的工程,所以需要為 Project 加載所需要的插件,比如為 Java 工程加載 Java 插件。其實(shí),一個(gè) Project 包含多少 Task 往往是插件決定的。

所以,在 Project 中,我們要:

  • 加載插件。
  • 不同插件有不同的行話,即不同的配置。我們要在 Project 中配置好,這樣插件就知道從哪里讀取源文件等
  • 設(shè)置屬性。

1.加載插件

Project 的 API 位于 https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html。加載插件是調(diào)用它的 apply 函數(shù).apply 其實(shí)是 Project 實(shí)現(xiàn)的 PluginAware 接口定義的:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/32.jpg" alt="" />

來看代碼:

[apply 函數(shù)的用法] apply 是一個(gè)函數(shù),此處調(diào)用的是圖 30 中最后一個(gè) apply 函數(shù)。注意,Groovy 支持

函數(shù)調(diào)用的時(shí)候通過 參數(shù)名 1:參數(shù)值 2,參數(shù)名 2:參數(shù)值 2 的方式來傳遞參數(shù)

apply plugin: 'com.android.library' <==如果是編譯 Library,則加載此插件

apply plugin: 'com.android.application' <==如果是編譯 Android APP,則加載此插件

除了加載二進(jìn)制的插件(上面的插件其實(shí)都是下載了對(duì)應(yīng)的 jar 包,這也是通常意義上我們所理解的插件),還可以加載一個(gè) gradle 文件。為什么要加載 gradle 文件呢?

其實(shí)這和代碼的模塊劃分有關(guān)。一般而言,我會(huì)把一些通用的函數(shù)放到一個(gè)名叫 utils.gradle 文件里。然后在其他工程的 build.gradle 來加載這個(gè) utils.gradle。這樣,通過一些處理,我就可以調(diào)用 utils.gradle 中定義的函數(shù)了。

加載 utils.gradle 插件的代碼如下:

utils.gradle 是我封裝的一個(gè) gradle 腳本,里邊定義了一些方便函數(shù),比如讀取 AndroidManifest.xml 中

的 versionName,或者是 copy jar 包/APK 包到指定的目錄

apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"

也是使用 apply 的最后一個(gè)函數(shù)。那么,apply 最后一個(gè)函數(shù)到底支持哪些參數(shù)呢?還是得看圖 31 中的 API 說明:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/33.jpg" alt="" />

我這里不遺余力的列出 API 圖片,就是希望大家在寫腳本的時(shí)候,碰到不會(huì)的,一定要去查看 API 文檔!

2.設(shè)置屬性

如果是單個(gè)腳本,則不需要考慮屬性的跨腳本傳播,但是 Gradle 往往包含不止一個(gè) build.gradle 文件,比如我設(shè)置的 utils.gradle,settings.gradle。如何在多個(gè)腳本中設(shè)置屬性呢?

Gradle 提供了一種名為 extra property 的方法。extra property 是額外屬性的意思,在第一次定義該屬性的時(shí)候需要通過 ext 前綴來標(biāo)示它是一個(gè)額外的屬性。定義好之后,后面的存取就不需要 ext 前綴了。ext 屬性支持 Project 和 Gradle 對(duì)象。即 Project 和 Gradle 對(duì)象都可以設(shè)置 ext 屬性

舉個(gè)例子:

我在 settings.gradle 中想為 Gradle 對(duì)象設(shè)置一些外置屬性,所以在 initMinshengGradleEnvironment 函數(shù)中

def initMinshengGradleEnvironment(){
    //屬性值從 local.properites 中讀取  
    Properties properties = new Properties()
    File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
    properties.load(propertyFile.newDataInputStream())
    //gradle 就是 gradle 對(duì)象。它默認(rèn)是 Settings 和 Project 的成員變量??芍苯荧@取  

   //ext 前綴,表明操作的是外置屬性。api 是一個(gè)新的屬性名。前面說過,只在  
   //第一次定義或者設(shè)置它的時(shí)候需要 ext 前綴  
    gradle.ext.api = properties.getProperty('sdk.api')

    println gradle.api  //再次存取 api 的時(shí)候,就不需要 ext 前綴了  
    ......
    }

再來一個(gè)例子強(qiáng)化一下:

我在 utils.gradle 中定義了一些函數(shù),然后想在其他 build.gradle 中調(diào)用這些函數(shù)。那該怎么做呢?

[utils.gradle]
//utils.gradle 中定義了一個(gè)獲取 AndroidManifests.xml versionName 的函數(shù)  
def  getVersionNameAdvanced(){
下面這行代碼中的 project 是誰?  

   def xmlFile = project.file("AndroidManifest.xml") 
   def rootManifest = new XmlSlurper().parse(xmlFile)
   return rootManifest['@android:versionName']   
}
//現(xiàn)在,想把這個(gè) API 輸出到各個(gè) Project。由于這個(gè) utils.gradle 會(huì)被每一個(gè) Project Apply,所以  

//我可以把 getVersionNameAdvanced 定義成一個(gè) closure,然后賦值到一個(gè)外部屬性  
下面的 ext 是誰的 ext?  
ext{ //此段花括號(hào)中代碼是閉包  
    //除了 ext.xxx=value 這種定義方法外,還可以使用 ext{}這種書寫方法。  

    //ext{}不是 ext(Closure)對(duì)應(yīng)的函數(shù)調(diào)用。但是 ext{}中的{}確實(shí)是閉包。  
    getVersionNameAdvanced = this.&getVersionNameAdvanced
 }

上面代碼中有兩個(gè)問題:

project 是誰?

ext 是誰的 ext?

上面兩個(gè)問題比較關(guān)鍵,我也是花了很長時(shí)間才搞清楚。這兩個(gè)問題歸結(jié)到一起,其實(shí)就是:

加載 utils.gradle 的 Project 對(duì)象和 utils.gradle 本身所代表的 Script 對(duì)象到底有什么關(guān)系?

我們?cè)?Groovy 中也講過怎么在一個(gè) Script 中 import 另外一個(gè) Script 中定義的類或者函數(shù)(見 3.5 腳本類、文件 I/O 和 XML 操作一節(jié))。在 Gradle 中,這一塊的處理比 Groovy 要復(fù)雜,具體怎么搞我還沒完全弄清楚,但是 Project 和 utils.gradle 對(duì)于的 Script 的對(duì)象的關(guān)系是:

  • 當(dāng)一個(gè) Project apply 一個(gè) gradle 文件的時(shí)候,這個(gè) gradle 文件會(huì)轉(zhuǎn)換成一個(gè) Script 對(duì)象。這個(gè),相信大家都已經(jīng)知道了。

  • Script 中有一個(gè) delegate 對(duì)象,這個(gè) delegate 默認(rèn)是加載(即調(diào)用 apply)它的 Project 對(duì)象。但是,在 apply 函數(shù)中,有一個(gè) from 參數(shù),還有一個(gè) to 參數(shù)(參考圖 31)。通過 to 參數(shù),你可以把 delegate 對(duì)象指定為別的東西。

  • delegate 對(duì)象是什么意思?當(dāng)你在 Script 中操作一些不是 Script 自己定義的變量,或者函數(shù)時(shí)候,gradle 會(huì)到 Script 的 delegate 對(duì)象去找,看看有沒有定義這些變量或函數(shù)。

現(xiàn)在你知道問題 1,2 和答案了:

問題 1:project 就是加載 utils.gradle 的 project。由于 posdevice 有 5 個(gè) project,所以 utils.gradle 會(huì)分別加載到 5 個(gè) project 中。所以,getVersionNameAdvanced 才不用區(qū)分到底是哪個(gè) project。反正一個(gè) project 有一個(gè) utils.gradle 對(duì)應(yīng)的 Script。

問題 2:ext:自然就是 Project 對(duì)應(yīng)的 ext 了。此處為 Project 添加了一些 closure。那么,在 Project 中就可以調(diào)用 getVersionNameAdvanced 函數(shù)了

比如:我在 posdevice 每個(gè) build.gradle 中都有如下的代碼:

tasks.getByName("assemble"){
    it.doLast{
        println "$project.name: After assemble, jar libs are copied to local repository"
        copyOutput(true)  //copyOutput 是 utils.gradle 輸出的 closure
     }
}

通過這種方式,我將一些常用的函數(shù)放到 utils.gradle 中,然后為加載它的 Project 設(shè)置 ext 屬性。最后,Project 中就可以調(diào)用這種賦值函數(shù)了!

注意:此處我研究的還不是很深,而且我個(gè)人感覺:

1 在 Java 和 Groovy 中:我們會(huì)把常用的函數(shù)放到一個(gè)輔助類和公共類中,然后在別的地方 import 并調(diào)用它們。

2 但是在 Gradle,更正規(guī)的方法是在 xxx.gradle 中定義插件。然后通過添加 Task 的方式來完成工作。gradle 的 user guide 有詳細(xì)介紹如何實(shí)現(xiàn)自己的插件。

  1. Task 介紹

Task 是 Gradle 中的一種數(shù)據(jù)類型,它代表了一些要執(zhí)行或者要干的工作。不同的插件可以添加不同的 Task。每一個(gè) Task 都需要和一個(gè) Project 關(guān)聯(lián)。

Task 的 API 文檔位于 https://docs.gradle.org/current/dsl/org.gradle.api.Task.html。關(guān)于 Task,我這里簡單介紹下 build.gradle 中怎么寫它,以及 Task 中一些常見的類型

關(guān)于 Task。來看下面的例子:

[build.gradle]
//Task 是和 Project 關(guān)聯(lián)的,所以,我們要利用 Project 的 task 函數(shù)來創(chuàng)建一個(gè) Task
task myTask  <==myTask 是新建 Task 的名字  

task myTask { configure closure }
task myType << { task action }  <==注意,<<符號(hào) 是 doLast 的縮寫  

task myTask(type: SomeType)
task myTask(type: SomeType) { configure closure }

上述代碼中都用了 Project 的一個(gè)函數(shù),名為 task,注意:

  • 一個(gè) Task 包含若干 Action。所以,Task 有 doFirst 和 doLast 兩個(gè)函數(shù),用于添加需要最先執(zhí)行的 Action 和需要和需要最后執(zhí)行的 Action。Action 就是一個(gè)閉包。
  • Task 創(chuàng)建的時(shí)候可以指定 Type,通過 type:名字表達(dá)。這是什么意思呢?其實(shí)就是告訴 Gradle,這個(gè)新建的 Task 對(duì)象會(huì)從哪個(gè)基類 Task 派生。比如,Gradle 本身提供了一些通用的 Task,最常見的有 Copy 任務(wù)。Copy 是 Gradle 中的一個(gè)類。當(dāng)我們:task myTask(type:Copy)的時(shí)候,創(chuàng)建的 Task 就是一個(gè) Copy Task。
  • 當(dāng)我們使用 task myTask{ xxx}的時(shí)候。花括號(hào)是一個(gè) closure。這會(huì)導(dǎo)致 gradle 在創(chuàng)建這個(gè) Task 之后,返回給用戶之前,會(huì)先執(zhí)行 closure 的內(nèi)容。
  • 當(dāng)我們使用 task myTask << {xxx}的時(shí)候,我們創(chuàng)建了一個(gè) Task 對(duì)象,同時(shí)把 closure 做為一個(gè) action 加到這個(gè) Task 的 action 隊(duì)列中,并且告訴它“最后才執(zhí)行這個(gè) closure”(注意,<<符號(hào)是 doLast 的代表)。

圖 32 是 Project 中關(guān)于 task 函數(shù)說明:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/34.jpg" alt="" />

陸陸續(xù)續(xù)講了這么些內(nèi)容,我自己感覺都有點(diǎn)煩了。是得,Gradle 用一整本書來講都嫌不夠呢。

anyway,到目前為止,我介紹的都是一些比較基礎(chǔ)的東西,還不是特別多。但是后續(xù)例子該涉及到的知識(shí)點(diǎn)都有了。下面我們直接上例子。這里有兩個(gè)例子:

  • posdevice 的例子
  • 另外一個(gè)是單個(gè) project 的例子

posdevice 實(shí)例

現(xiàn)在正是開始通過例子來介紹怎么玩 gradle。這里要特別強(qiáng)調(diào)一點(diǎn),根據(jù) Gradle 的哲學(xué)。gradle 文件中包含一些所謂的 Script Block(姑且這么稱它)。Script Block 作用是讓我們來配置相關(guān)的信息。不同的 SB 有不同的需要配置的東西。這也是我最早說的行話。比如,源碼對(duì)應(yīng)的 SB,就需要我們配置源碼在哪個(gè)文件夾里。關(guān)于 SB,我們后面將見識(shí)到!

posdevice 是一個(gè) multi project。下面包含 5 個(gè) Project。對(duì)于這種 Project,請(qǐng)大家回想下我們?cè)搫?chuàng)建哪些文件?

  • settings.gradle 是必不可少的
  • 根目錄下的 build.gradle。這個(gè)我們沒講過,因?yàn)?posdevice 的根目錄本身不包含代碼,而是包含其他 5 個(gè)子 project。
  • 每個(gè) project 目錄下包含對(duì)于的 build.gradle
  • 另外,我把常用的函數(shù)封裝到一個(gè)名為 utils.gradle 的腳本里了。

馬上一個(gè)一個(gè)來看它們。

  1. utils.gradle utils.gradle 是我自己加的,為我們團(tuán)隊(duì)特意加了一些常見函數(shù)。主要代碼如下:
[utils.gradle]
import groovy.util.XmlSlurper  //解析 XML 時(shí)候要引入這個(gè) groovy 的 package

def copyFile(String srcFile,dstFile){
     ......//拷貝文件函數(shù),用于將最后的生成物拷貝到指定的目錄  

}
def rmFile(String targetFile){
    .....//刪除指定目錄中的文件  

}
def cleanOutput(boolean bJar = true){
    ....//clean 的時(shí)候清理  

}

def copyOutput(boolean bJar = true){
    ....//copyOutput 內(nèi)部會(huì)調(diào)用 copyFile 完成一次 build 的產(chǎn)出物拷貝  

}

def getVersionNameAdvanced(){//老朋友  

   def xmlFile = project.file("AndroidManifest.xml")
   def rootManifest = new XmlSlurper().parse(xmlFile)
   return rootManifest['@android:versionName']   
}

//對(duì)于 android library 編譯,我會(huì) disable 所有的 debug 編譯任務(wù)  

def disableDebugBuild(){
  //project.tasks 包含了所有的 tasks,下面的 findAll 是尋找那些名字中帶 debug 的 Task。  

  //返回值保存到 targetTasks 容器中  

  def targetTasks = project.tasks.findAll{task ->
      task.name.contains("Debug")
  }
  //對(duì)滿足條件的 task,設(shè)置它為 disable。如此這般,這個(gè) Task 就不會(huì)被執(zhí)行  

  targetTasks.each{
     println "disable debug task  : ${it.name}"
     it.setEnabled false
  }
}
//將函數(shù)設(shè)置為 extra 屬性中去,這樣,加載 utils.gradle 的 Project 就能調(diào)用此文件中定義的函數(shù)了  

ext{
    copyFile = this.&copyFile
    rmFile = this.&rmFile
    cleanOutput = this.&cleanOutput
    copyOutput = this.&copyOutput
    getVersionNameAdvanced = this.&getVersionNameAdvanced
    disableDebugBuild = this.&disableDebugBuild
}

圖 33 展示了被 disable 的 Debug 任務(wù)的部分信息:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/35.jpg" alt="" />

  1. settings.gradle

這個(gè)文件中我們?cè)摳墒裁??調(diào)用 include 把需要包含的子 Project 加進(jìn)來。代碼如下:

[settings.gradle]
/*我們團(tuán)隊(duì)內(nèi)部建立的編譯環(huán)境初始化函數(shù)  

  這個(gè)函數(shù)的目的是  

  1  解析一個(gè)名為 local.properties 的文件,讀取 Android SDK 和 NDK 的路徑  

  2  獲取最終產(chǎn)出物目錄的路徑。這樣,編譯完的 apk 或者 jar 包將拷貝到這個(gè)最終產(chǎn)出物目錄中  

  3 獲取 Android SDK 指定編譯的版本  

*/
def initMinshengGradleEnvironment(){
    println "initialize Minsheng Gradle Environment ....."
    Properties properties = new Properties()
   //local.properites 也放在 posdevice 目錄下  

    File propertyFile = new File(rootDir.getAbsolutePath() + "/local.properties")
    properties.load(propertyFile.newDataInputStream())
    /*
      根據(jù) Project、Gradle 生命周期的介紹,settings 對(duì)象的創(chuàng)建位于具體 Project 創(chuàng)建之前  

      而 Gradle 底對(duì)象已經(jīng)創(chuàng)建好了。所以,我們把 local.properties 的信息讀出來后,通過  

      extra 屬性的方式設(shè)置到 gradle 對(duì)象中  

      而具體 Project 在執(zhí)行的時(shí)候,就可以直接從 gradle 對(duì)象中得到這些屬性了!  

    */
    gradle.ext.api = properties.getProperty('sdk.api')
    gradle.ext.sdkDir = properties.getProperty('sdk.dir')
     gradle.ext.ndkDir = properties.getProperty('ndk.dir')
     gradle.ext.localDir = properties.getProperty('local.dir')
    //指定 debug keystore 文件的位置,debug 版 apk 簽名的時(shí)候會(huì)用到  

    gradle.ext.debugKeystore = properties.getProperty('debug.keystore')
     ......
    println "initialize Minsheng Gradle Environment completes..."
}
//初始化  
initMinshengGradleEnvironment()
//添加子 Project 信息  
include 'CPosSystemSdk' , 'CPosDeviceSdk' , 'CPosSdkDemo','CPosDeviceServerApk', 'CPosSystemSdkWizarPosImpl'

注意,對(duì)于 Android 來說,local.properties 文件是必須的,它的內(nèi)容如下:

[local.properties]
local.dir=/home/innost/workspace/minsheng-flat-dir/
//注意,根據(jù) Android Gradle 的規(guī)范,只有下面兩個(gè)屬性是必須的,其余都是我自己加的  

sdk.dir=/home/innost/workspace/android-aosp-sdk/
ndk.dir=/home/innost/workspace/android-aosp-ndk/
debug.keystore=/home/innost/workspace/tools/mykeystore.jks
sdk.api=android-19

再次強(qiáng)調(diào),sdk.dir 和 ndk.dir 是 Android Gradle 必須要指定的,其他都是我自己加的屬性。當(dāng)然。不編譯 ndk,就不需要 ndk.dir 屬性了。

3.posdevice build.gradle

作為 multi-project 根目錄,一般情況下,它的 build.gradle 是做一些全局配置。來看我的 build.gradle

[posdevice build.gradle]
//下面這個(gè) subprojects{}就是一個(gè) Script Block
subprojects {
  println "Configure for $project.name"  //遍歷子 Project,project 變量對(duì)應(yīng)每個(gè)子 Project
  buildscript {  //這也是一個(gè) SB
    repositories { //repositories 是一個(gè) SB
        ///jcenter 是一個(gè)函數(shù),表示編譯過程中依賴的庫,所需的插件可以在 jcenter 倉庫中  

       //下載。 
        jcenter()
    }
    dependencies { //SB
        //dependencies 表示我們編譯的時(shí)候,依賴 android 開發(fā)的 gradle 插件。插件對(duì)應(yīng)的  

       //class path 是 com.android.tools.build。版本是 1.2.3
        classpath 'com.android.tools.build:gradle:1.2.3'
    }
   //為每個(gè)子 Project 加載 utils.gradle 。當(dāng)然,這句話可以放到 buildscript 花括號(hào)之后  

   apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
 }//buildscript 結(jié)束  

}

感覺解釋得好蒼白,SB 在 Gradle 的 API 文檔中也是有的。先來看 Gradle 定義了哪些 SB。如圖 34 所示:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/36.jpg" alt="" />

你看,subprojects、dependencies、repositories 都是 SB。那么 SB 到底是什么?它是怎么完成所謂配置的呢?

仔細(xì)研究,你會(huì)發(fā)現(xiàn) SB 后面都需要跟一個(gè)花括號(hào),而花括號(hào),恩,我們感覺里邊可能一個(gè) Closure。由于圖 34 說,這些 SB 的 Description 都有“Configure xxx for this project”,所以很可能 subprojects 是一個(gè)函數(shù),然后其參數(shù)是一個(gè) Closure。是這樣的嗎?

Absolutely right。只是這些函數(shù)你直接到 Project API 里不一定能找全。不過要是你好奇心重,不妨到 https://docs.gradle.org/current/javadoc/,選擇 Index 這一項(xiàng),然后 ctrl+f,輸入圖 34 中任何一個(gè) Block,你都會(huì)找到對(duì)應(yīng)的函數(shù)。比如我替你找了幾個(gè) API,如圖 35 所示:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/37.jpg" alt="" />

特別提示:當(dāng)你下次看到一個(gè)不認(rèn)識(shí)的 SB 的時(shí)候,就去看 API 吧。

下面來解釋代碼中的各個(gè) SB:

  • subprojects:它會(huì)遍歷 posdevice 中的每個(gè)子 Project。在它的 Closure 中,默認(rèn)參數(shù)是子 Project 對(duì)應(yīng)的 Project 對(duì)象。由于其他 SB 都在 subprojects 花括號(hào)中,所以相當(dāng)于對(duì)每個(gè) Project 都配置了一些信息。

  • buildscript:它的 closure 是在一個(gè)類型為 ScriptHandler 的對(duì)象上執(zhí)行的。主意用來所依賴的 classpath 等信息。通過查看 ScriptHandler API 可知,在 buildscript SB 中,你可以調(diào)用 ScriptHandler 提供的 repositories(Closure )、dependencies(Closure)函數(shù)。這也是為什么 repositories 和 dependencies 兩個(gè) SB 為什么要放在 buildscript 的花括號(hào)中的原因。明白了?這就是所謂的行話,得知道規(guī)矩。不知道規(guī)矩你就亂了。記不住規(guī)矩,又不知道查 SDK,那么就徹底抓瞎,只能到網(wǎng)上到處找答案了!

  • 關(guān)于 repositories 和 dependencies,大家直接看 API 吧。后面碰到了具體代碼我們?cè)賮斫榻B

4.CPosDeviceSdk build.gradle

CPosDeviceSdk 是一個(gè) Android Library。按 Google 的想法,Android Library 編譯出來的應(yīng)該是一個(gè) AAR 文件。但是我的項(xiàng)目有些特殊,我需要發(fā)布 CPosDeviceSdk.jar 包給其他人使用。jar 在編譯過程中會(huì)生成,但是它不屬于 Android Library 的標(biāo)準(zhǔn)輸出。在這種情況下,我需要在編譯完成后,主動(dòng) copy jar 包到我自己設(shè)計(jì)的產(chǎn)出物目錄中。

//Library 工程必須加載此插件。注意,加載了 Android 插件就不要加載 Java 插件了。因?yàn)?Android
//插件本身就是拓展了 Java 插件  
apply plugin: 'com.android.library'  
//android 的編譯,增加了一種新類型的 Script Block-->android
android {
       //你看,我在 local.properties 中設(shè)置的 API 版本號(hào),就可以一次設(shè)置,多個(gè) Project 使用了  

      //借助我特意設(shè)計(jì)的 gradle.ext.api 屬性  

       compileSdkVersion = gradle.api  //這兩個(gè)紅色的參數(shù)必須設(shè)置  

       buildToolsVersion  = "22.0.1"
       sourceSets{ //配置源碼路徑。這個(gè) sourceSets 是 Java 插件引入的  

        main{ //main:Android 也用了  

            manifest.srcFile 'AndroidManifest.xml'  //這是一個(gè)函數(shù),設(shè)置 manifest.srcFile
            aidl.srcDirs=['src'] //設(shè)置 aidl 文件的目錄  

            java.srcDirs=['src'] //設(shè)置 java 文件的目錄  

        }
     }
    dependencies {  //配置依賴關(guān)系  

       //compile 表示編譯和運(yùn)行時(shí)候需要的 jar 包,fileTree 是一個(gè)函數(shù),  

      //dir:'libs',表示搜索目錄的名稱是 libs。include:['*.jar'],表示搜索目錄下滿足*.jar 名字的 jar
     //包都作為依賴 jar 文件  

        compile fileTree(dir: 'libs', include: ['*.jar'])
   }
}  //android SB 配置完了  

//clean 是一個(gè) Task 的名字,這個(gè) Task 好像是 Java 插件(這里是 Android 插件)引入的。  
//dependsOn 是一個(gè)函數(shù),下面這句話的意思是 clean 任務(wù)依賴 cposCleanTask 任務(wù)。所以  
//當(dāng)你 gradle clean 以執(zhí)行 clean Task 的時(shí)候,cposCleanTask 也會(huì)執(zhí)行  
clean.dependsOn 'cposCleanTask'
//創(chuàng)建一個(gè) Task,  
task cposCleanTask() <<{
    cleanOutput(true)  //cleanOutput 是 utils.gradle 中通過 extra 屬性設(shè)置的 Closure
}
//前面說了,我要把 jar 包拷貝到指定的目錄。對(duì)于 Android 編譯,我一般指定 gradle assemble
//它默認(rèn)編譯 debug 和 release 兩種輸出。所以,下面這個(gè)段代碼表示:  
//tasks 代表一個(gè) Projects 中的所有 Task,是一個(gè)容器。getByName 表示找到指定名稱的任務(wù)。  
//我這里要找的 assemble 任務(wù),然后我通過 doLast 添加了一個(gè) Action。這個(gè) Action 就是 copy
//產(chǎn)出物到我設(shè)置的目標(biāo)目錄中去  
tasks.getByName("assemble"){
    it.doLast{
        println "$project.name: After assemble, jar libs are copied to local repository"
        copyOutput(true)
     }
}
/*
  因?yàn)槲业捻?xiàng)目只提供最終的 release 編譯出來的 Jar 包給其他人,所以不需要編譯 debug 版的東西  

  當(dāng) Project 創(chuàng)建完所有任務(wù)的有向圖后,我通過 afterEvaluate 函數(shù)設(shè)置一個(gè)回調(diào) Closure。在這個(gè)回調(diào)  

  Closure 里,我 disable 了所有 Debug 的 Task
*/
project.afterEvaluate{
    disableDebugBuild()
}

Android 自己定義了好多 ScriptBlock。Android 定義的 DSL 參考文檔在

https://developer.android.com/tools/building/plugin-for-gradle.html 下載。注意,它居然沒有提供在線文檔。

圖 36 所示為 Android 的 DSL 參考信息。

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/38.jpg" alt="" />

圖 37 為 buildToolsVersion 和 compileSdkVersion 的說明:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/39.jpg" alt="" />

從圖 37 可知,這兩個(gè)變量是必須要設(shè)置的.....

5.CPosDeviceServerApk build.gradle

再來看一個(gè) APK 的 build,它包含 NDK 的編譯,并且還要簽名。根據(jù)項(xiàng)目的需求,我們只能簽 debug 版的,而 release 版的簽名得發(fā)布 unsigned 包給領(lǐng)導(dǎo)簽名。另外,CPosDeviceServerAPK 依賴 CPosDeviceSdk。

雖然我可以先編譯 CPosDeviceSdk,得到對(duì)應(yīng)的 jar 包,然后設(shè)置 CPosDeviceServerApk 直接依賴這個(gè) jar 包就好。但是我更希望 CPosDeviceServerApk 能直接依賴于 CPosDeviceSdk 這個(gè)工程。這樣,整個(gè) posdevice 可以做到這幾個(gè) Project 的依賴關(guān)系是最新的。

[build.gradle]
apply plugin: 'com.android.application'  //APK 編譯必須加載這個(gè)插件  
android {
       compileSdkVersion gradle.api 
       buildToolsVersion "22.0.1"
       sourceSets{  //差不多的設(shè)置  

        main{
            manifest.srcFile 'AndroidManifest.xml'
          //通過設(shè)置 jni 目錄為空,我們可不使用 apk 插件的 jni 編譯功能。為什么?因?yàn)閾?jù)說  

          //APK 插件的 jni 功能好像不是很好使....暈菜  

           jni.srcDirs = []  
            jniLibs.srcDir 'libs'
            aidl.srcDirs=['src']
            java.srcDirs=['src']
            res.srcDirs=['res']
        }
    }//main 結(jié)束  

    signingConfigs { //設(shè)置簽名信息配置  

        debug {  //如果我們?cè)?local.properties 設(shè)置使用特殊的 keystore,則使用它  

           //下面這些設(shè)置,無非是函數(shù)調(diào)用....請(qǐng)務(wù)必閱讀 API 文檔  

           if(project.gradle.debugKeystore != null){ 
               storeFile file("file://${project.gradle.debugKeystore}")
               storePassword "android"
               keyAlias "androiddebugkey"
               keyPassword "android"
            }
        }
   }//signingConfigs 結(jié)束  

     buildTypes {
        debug {
            signingConfig signingConfigs.debug
            jniDebuggable false
        }
    }//buildTypes 結(jié)束  

    dependencies {
        //compile:project 函數(shù)可指定依賴 multi-project 中的某個(gè)子 project
        compile project(':CPosDeviceSdk')
        compile fileTree(dir: 'libs', include: ['*.jar'])
   } //dependices 結(jié)束  

  repositories {
   flatDir { //flatDir:告訴 gradle,編譯中依賴的 jar 包存儲(chǔ)在 dirs 指定的目錄  

            name "minsheng-gradle-local-repository"
            dirs gradle.LOCAL_JAR_OUT //LOCAL_JAR_OUT 是我存放編譯出來的 jar 包的位置  

   }
  }//repositories 結(jié)束  

}//android 結(jié)束  

/*
   創(chuàng)建一個(gè) Task,類型是 Exec,這表明它會(huì)執(zhí)行一個(gè)命令。我這里讓他執(zhí)行 ndk 的  

   ndk-build 命令,用于編譯 ndk。關(guān)于 Exec 類型的 Task,請(qǐng)自行腦補(bǔ) Gradle 的 API
*/
//注意此處創(chuàng)建 task 的方法,是直接{}喔,那么它后面的 tasks.withType(JavaCompile)
//設(shè)置的依賴關(guān)系,還有意義嗎?Think!如果你能想明白,gradle 掌握也就差不多了  

task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
        if(project.gradle.ndkDir == null) //看看有沒有指定 ndk.dir 路徑  

           println "CANNOT Build NDK"
        else{
            commandLine "/${project.gradle.ndkDir}/ndk-build",
                '-C', file('jni').absolutePath,
                '-j', Runtime.runtime.availableProcessors(),
                'all', 'NDK_DEBUG=0'
        }
  }
 tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn buildNative
  }
  ......   
 //對(duì)于 APK,除了拷貝 APK 文件到指定目錄外,我還特意為它們加上了自動(dòng)版本命名的功能  

 tasks.getByName("assemble"){
        it.doLast{
        println "$project.name: After assemble, jar libs are copied to local repository"
        project.ext.versionName = android.defaultConfig.versionName
        println "\t versionName = $versionName"
        copyOutput(false)
     }
}
  1. 結(jié)果展示

在 posdevice 下執(zhí)行 gradle assemble 命令,最終的輸出文件都會(huì)拷貝到我指定的目錄,結(jié)果如圖 38 所示:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/40.jpg" alt="" />

圖 38 所示為 posdevice gradle assemble 的執(zhí)行結(jié)果:

  • library 包都編譯 release 版的,copy 到 xxx/javaLib 目錄下

  • apk 編譯 debug 和 release-unsigned 版的,copy 到 apps 目錄下

  • 所有產(chǎn)出物都自動(dòng)從 AndroidManifest.xml 中提取 versionName。

實(shí)例 2

下面這個(gè)實(shí)例也是來自一個(gè)實(shí)際的 APP。這個(gè) APP 對(duì)應(yīng)的是一個(gè)單獨(dú)的 Project。但是根據(jù)我前面的建議,我會(huì)把它改造成支持 Multi-Projects Build 的樣子。即在工程目錄下放一個(gè) settings.build。

另外,這個(gè) app 有一個(gè)特點(diǎn)。它有三個(gè)版本,分別是 debug、release 和 demo。這三個(gè)版本對(duì)應(yīng)的代碼都完全一樣,但是在運(yùn)行的時(shí)候需要從 assets/runtime_config 文件中讀取參數(shù)。參數(shù)不同,則運(yùn)行的時(shí)候會(huì)跳轉(zhuǎn)到 debug、release 或者 demo 的邏輯上。

注意:我知道 assets/runtime_config 這種做法不 decent,但,這是一個(gè)既有項(xiàng)目,我們只能做小范圍的適配,而不是傷筋動(dòng)骨改用更好的方法。另外,從未來的需求來看,暫時(shí)也沒有大改的必要。

引入 gradle 后,我們?cè)撊绾翁幚砟兀?

解決方法是:在編譯 build、release 和 demo 版本前,在 build.gradle 中自動(dòng)設(shè)置 runtime_config 的內(nèi)容。代碼如下所示:

[build.gradle]
apply plugin: 'com.android.application'  //加載 APP 插件  

//加載 utils.gradle
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
//buildscript 設(shè)置 android app 插件的位置  

buildscript {
    repositories { jcenter() }
    dependencies { classpath 'com.android.tools.build:gradle:1.2.3' }
}
//android ScriptBlock
android {
    compileSdkVersion gradle.api
    buildToolsVersion "22.0.1"
   sourceSets{ //源碼設(shè)置 SB
        main{
            manifest.srcFile 'AndroidManifest.xml'
            jni.srcDirs = []
            jniLibs.srcDir 'libs'
            aidl.srcDirs=['src']
            java.srcDirs=['src']
            res.srcDirs=['res']
            assets.srcDirs = ['assets'] //多了一個(gè) assets 目錄  

        }
    }
    signingConfigs {//簽名設(shè)置  

        debug {  //debug 對(duì)應(yīng)的 SB。注意  

            if(project.gradle.debugKeystore != null){
                storeFile file("file://${project.gradle.debugKeystore}")
                storePassword "android"
                keyAlias "androiddebugkey"
                keyPassword "android"
            }
        }
    }
    /*
     最關(guān)鍵的內(nèi)容來了: buildTypes ScriptBlock.
     buildTypes 和上面的 signingConfigs,當(dāng)我們?cè)?build.gradle 中通過{}配置它的時(shí)候,  

     其背后的所代表的對(duì)象是 NamedDomainObjectContainer<BuildType> 和  

     NamedDomainObjectContainer<SigningConfig>
     注意,NamedDomainObjectContainer<BuildType/或者 SigningConfig>是一種容器,  

     容器的元素是 BuildType 或者 SigningConfig。我們?cè)?debug{}要填充 BuildType 或者  

    SigningConfig 所包的元素,比如 storePassword 就是 SigningConfig 類的成員。而 proguardFile 等  

    是 BuildType 的成員。  

    那么,為什么要使用 NamedDomainObjectContainer 這種數(shù)據(jù)結(jié)構(gòu)呢?因?yàn)橥@種容器里  

    添加元素可以采用這樣的方法: 比如 signingConfig 為例  

    signingConfig{//這是一個(gè) NamedDomainObjectContainer<SigningConfig>
       test1{//新建一個(gè)名為 test1 的 SigningConfig 元素,然后添加到容器里  

         //在這個(gè)花括號(hào)中設(shè)置 SigningConfig 的成員變量的值  

       }
      test2{//新建一個(gè)名為 test2 的 SigningConfig 元素,然后添加到容器里  

         //在這個(gè)花括號(hào)中設(shè)置 SigningConfig 的成員變量的值  

      }
    }
    在 buildTypes 中,Android 默認(rèn)為這幾個(gè) NamedDomainObjectContainer 添加了  

    debug 和 release 對(duì)應(yīng)的對(duì)象。如果我們?cè)偬砑觿e的名字的東西,那么 gradle assemble 的時(shí)候  

    也會(huì)編譯這個(gè)名字的 apk 出來。比如,我添加一個(gè)名為 test 的 buildTypes,那么 gradle assemble
    就會(huì)編譯一個(gè) xxx-test-yy.apk。在此,test 就好像 debug、release 一樣。  

   */
    buildTypes{
        debug{ //修改 debug 的 signingConfig 為 signingConfig.debug 配置  

            signingConfig signingConfigs.debug
        }
        demo{ //demo 版需要混淆  

            proguardFile 'proguard-project.txt'
            signingConfig signingConfigs.debug
        }
       //release 版沒有設(shè)置,所以默認(rèn)沒有簽名,沒有混淆  

    }
      ......//其他和 posdevice 類似的處理。來看如何動(dòng)態(tài)生成 runtime_config 文件  

   def  runtime_config_file = 'assets/runtime_config'
   /*
   我們?cè)?gradle 解析完整個(gè)任務(wù)之后,找到對(duì)應(yīng)的 Task,然后在里邊添加一個(gè) doFirst Action
   這樣能確保編譯開始的時(shí)候,我們就把 runtime_config 文件準(zhǔn)備好了。  

   注意,必須在 afterEvaluate 里邊才能做,否則 gradle 沒有建立完任務(wù)有向圖,你是找不到  

   什么 preDebugBuild 之類的任務(wù)的  

   */
    project.afterEvaluate{
      //找到 preDebugBuild 任務(wù),然后添加一個(gè) Action  
      tasks.getByName("preDebugBuild"){
            it.doFirst{
                println "generate debug configuration for ${project.name}"
                def configFile = new File(runtime_config_file)
                configFile.withOutputStream{os->
                    os << I am Debug\n'  //往配置文件里寫 I am Debug
                 }
            }
        }
       //找到 preReleaseBuild 任務(wù)  

        tasks.getByName("preReleaseBuild"){
            it.doFirst{
                println "generate release configuration for ${project.name}"
                def configFile = new File(runtime_config_file)
                configFile.withOutputStream{os->
                    os << I am release\n'
                }
            }
        }
       //找到 preDemoBuild。這個(gè)任務(wù)明顯是因?yàn)槲覀冊(cè)?buildType 里添加了一個(gè) demo 的元素  

      //所以 Android APP 插件自動(dòng)為我們生成的  

        tasks.getByName("preDemoBuild"){
            it.doFirst{
                println "generate offlinedemo configuration for ${project.name}"
                def configFile = new File(runtime_config_file)
                configFile.withOutputStream{os->
                    os << I am Demo\n'
                }
            }
        }
    }
}
 .....//copyOutput

最終的結(jié)果如圖 39 所示:

http://wiki.jikexueyuan.com/project/deep-android-gradle/images/41.jpg" alt="" />

幾個(gè)問題,為什么我知道有 preXXXBuild 這樣的任務(wù)?

答案:gradle tasks --all 查看所有任務(wù)。然后,多嘗試幾次,直到成功