轉載:http://www.apkbus.com/forum.php?mod=viewthread&tid=255064&extra=page%3D2%26filter%3Dauthor%26orderby%3Ddateline&_dsign=276e9e2e
問題與解決方案
回想我在 Gradle 的學習過程中遇到的問題與及其解決方案,總結出下面三點:
- 原理不懂:學習 Groovy 與 Gradle 的基礎原理
- Gradle 實踐不懂:學會找示例,學習開源例子
- 方法和屬性不懂:學會查文檔
下面的我將以解決三個問題為線索,介紹 Groovy 和 Gradle。
學習 GroovyGroovy 概述
Gradle 采用了 Groovy 語言作為主要的腳本語言。一個 build.gradle 文件,其實是一個 Groovy 類。
Groovy 是一個基於 JVM 的語言,代碼最終編譯成字節碼(bytecode)在 JVM 上運行。它具有類似於 Java 的語法風格,但是語法又比 Java 要靈活和方便,同時具有動態語言(如 ruby 和 Python)的一些特性。
Groovy 的諸多特定,很適合用來定義 DSL(Domain Specific Language)。
簡單的來講 DSL 是一個面向特定小領域的語言,如常見的 HTML、CSS 都是 DSL,它通常是以配置的方式進行編程,與之相對的是通用語言(General Purpose Language),如 Java 等。
既然是一門語言,就肯定有自己的特性。我們要從下面幾個步驟來介紹 Groovy:
- 環境安裝
- 語言基礎
- 語言特性及其本質
環境安裝
Groovy 官方安裝文檔提供多種方式進行安裝,確保你不會在跪在環境配置的路上 ^-^ :
- Windows 下推薦 binary 包配置環境變量
- Mac 下推薦使用 sdkman 或者 Brew 進行安裝
- Linux 下推薦 sdkman
- 嵌入在程序中,則推薦使用 Maven 遠程依賴
初學者也沒有必要使用IDE,平添障礙,后期用 Intellij IDEA Community 版本足矣。
下面只介紹 Mac 下使用 sdkman 的安裝方式。
- 下載安裝 sdkman,執行下面命令,按照提示安裝即可
1 | $ curl -s http://get.sdkman.io | bash |
- 使環境變量生效
1 | $ source "$HOME/.sdkman/bin/sdkman-init.sh" |
- 安裝 Groovy
1 | $ sdk install groovy |
- 查看當前版本,如果能否運行且輸出對應版本,就是成功了
1 2 |
$ groovy -version Groovy Version: 2.4.4 JVM: 1.8.0_25 Vendor: Oracle Corporation OS: Mac OS X |
初探
安裝好環境之后,先來一個 hello, world!
- 新建文件
1 | $ vim test.groovy |
- 在其中寫上內容
1 | println "hello, world!" |
- 保存退出,執行
1 2 |
$ groovy test.groovy hello, world! |
Wow, So easy!
語言基礎
下面將會用一些實際的例子,介紹一些最重要的點,
例子都已經傳到 github 的 demo 項目中。
第一次使用 demo 項目的時候,需要等待自動下載幾個遠程包。
筆者一個 Java 程序員,可以你能夠看到很多 Java 的習性還是留在代碼中。
文件與類,變量與函數
Groovy 代碼文件,支持不顯式聲明類:
ScriptClass.groovy
1 | println 'hello,world' |
這樣一個 Groovy 腳本,經過編譯之后,會產生一個繼承自 groovy.lang.Script 類的子類:
是不是能看出點什么?
groovy/build/classes/main/io/kvh/as/groovy/ScriptClass.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class ScriptClass extends Script { public ScriptClass() { CallSite[] var1 = $getCallSiteArray(); } public ScriptClass(Binding context) { CallSite[] var2 = $getCallSiteArray(); super(context); } public static void main(String... args) { CallSite[] var1 = $getCallSiteArray(); var1[0].call(InvokerHelper.class, ScriptClass.class, args); } public Object run() {//關鍵方法 CallSite[] var1 = $getCallSiteArray(); return var1[1].callCurrent(this, "hello,world");// got it? } } |
Groovy 支持如下的方式來定義變量和函數:
VarAndMethod.groovy
1 2 3 4 5 6 7 8 9 10 |
def varAndMethod() { def a = 1//不顯式聲明變量類型 a = "abc"//運行時改變類型 println a//無需;結束一行代碼 a = 4//最后一行作為返回值 } def ret = varAndMethod()//文件內運行方法 println ret//輸出4 |
字符串
Groovy 支持單引號,雙引號,三單引號聲明一個字符串;
Quoted.groovy
1 2 3 4 5 6 7 8 9 10 |
def quoted() { def singleQ = 'hello, single quot'// 聲明為java.lang.String def doubleQ = "hello, double quot ${singleQ}"// 如果有${},則為groovy.lang.GString,支持變量替換;否則為java.lang.String def tripleQ = '''hello, triple quot'''// 允許多行,而不需要+號 println singleQ println doubleQ println tripleQ } |
Groovy 還支持以:
1 2 3 |
"""...""" /.../ $/.../$ |
來聲明字符串,詳情參見參考文檔。
List,Array 和 Map
Groovy 默認使用 java.util.ArrayList 來提供 List 服務,但提供了更加靈活易用的操作方式:
Collections.groovy
1 2 3 4 5 6 7 |
def playList() { def lst = ["a",2,true]//支持不同類型元素 println(lst) } playList() |
要使用 Array,需要顯式聲明:
1 2 3 4 5 6 7 8 9 |
def playArray() { def intArr = [1, 2, 3] as int[]//顯示聲明 String[] strArr = ["a", "b"]//另外一種方式 println(intArr) println(strArr) } playArray() |
使用 key:value 的方式定義 Map,注意 key 的正確使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def playMap() { def map = [a: "a", b: "b"] println(map) def key = "name" def map2 = [key: 'a']//未使用 def map3 = [(key): 'a']//使用 println(map2) println(map3) } playMap() |
import
Groovy 提供了更強大的 import
- 默認 import,這些類都是被默認 import 到代碼中的,可以直接使用
1 2 3 4 5 6 7 8 |
import java.lang.* import java.util.* import java.io.* import java.net.* import groovy.lang.* import groovy.util.* import java.math.BigInteger import java.math.BigDecimal |
- import alias
引入一個類,通過 as 關鍵字賦予一個別名,有點 JavaScript 的意思么?
Import.groovy
1 2 3 |
import java.lang.String as KString println(new KString("aaa")) |
語言特性及其本質Closure(閉包)
閉包的概念不再贅述,大概就是可以將函數作為參數傳遞和使用,詳情參見 wikipedia。
1 | { [closureParameters -> ] statements } |
可以省略方括號內的內容,也就是說,可以沒有參數列表。
Closure.groovy
當閉包不聲明參數列表,默認參數是 it;閉包被定義之后,是一個 Closure 對象,可以對其調用 call 方法使其執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def defaultIt() { 3.times { println it //默認參數 it } } defaultIt() def closureObj() { def obj = { a -> ++a } println obj.call(1) } closureObj() |
面向對象特性
- 定義和實例化一個類
GroovyCLass.groovy
1 2 3 4 5 6 7 |
class People{ String name int age } People p1 = new People(); People p2 = new People(name:"Luis",age: 29)//通過類似 map 的方式賦值參數 |
- 方法的默認參數
1 2 3 4 5 6 |
def foo(String p1, int p2 = 1) { println(p1) println(p2) } foo("hello") |
- Field 和 Property
Field 是以各種修飾符修飾的變量。Property是私有變量和自帶的 gettters/setters,
下面的類具有私有變量 name、age,並自帶這兩個變量的 getter 和 setter:
1 2 3 4 |
class People{ String name int age } |
當變量聲明為 final 的時候,默認就沒有 setter
- Trait
Groovy 提供了一個叫做 Trait 特性實現了多繼承,還有很多強大的功能,讀者可以自己探索。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
trait Fly { void fly() { println("fly") } } trait Walk { void walk() { println("walk") } } class Duck implements Fly, Walk { } Duck duck = new Duck() duck.fly() duck.walk() |
Groovy 基礎小結
至此,我們已經熟悉了 Groovy 的基本語法和特性,相信你也能夠使用 Groovy 寫一些基礎程序了。Groovy 還有很多深入的內容,請用到的時候,參考這本這個 pdf: 《Programming Groovy 2》。
下面開始介紹使用 Groovy 寫 Gradle 程序,主要的內容來自 《Gradle Sser Guide》。
學習 GradleGradle 安裝
三種方式安裝 Gradle:
-
下載 zip 安裝包
-
Mac 下使用 home brew
1 | brew install gradle |
- 推薦:使用 IntelliJ IDEA(Android Studio)自帶的 wrapper 結構來下載 Gradle
Wrapper 是為了讓不同版本的插件能夠使用其對應版本的 Gradle 的一個機制Gradle Wrapper 會把不同的版本 Gradle 安裝在:
1 | $USER_HOME/.gradle/wrapper/dists |
Gradle Build 的生命周期
1 2 3 4 5 6 7 8 |
. ├── app //app module │ ├── build.gradle //app module 的 build.gradle ├── build.gradle //項目 build.gradle,通常配置項目全局配置,如 repositories 和 dependencies ├── gradle.properties //項目屬性文件,通常可以放置一些常量 ├── lib //lib module │ ├── build.gradle //lib module 的 build.gradle └── settings.gradle //項目總體設置,通常是配置項目中所有的 module |
Gradle 構建三個階段:
-
初始化:Gradle 支持單 module 構建和多 module 構建(Android Studio 創建的項目默認是多 module)。初始化階段,Gradle 會為每一個 module 中的 build.gradle 文件創建一個 Project 實例。
-
配置:項目根目錄的 build.gradle 會首先被執行
-
執行:執行所選取的 task
Settings.gradle
多 module 構建要求在項目根目錄下有一個 settings.gradle,用來指定哪些 module 參與構建,如:
settings.gradle
1 2 3 |
include ':app', ':groovy' println 'print in settings.gradle' |
在 settings.gradle 文件中,添加一行打印語句,在控制台中,切換到當前項目根目錄下執行:
1 | ./gradlew -p groovy build |
可以在看出 settings.gradle 的代碼每次都會率先執行。
Task
接下來,我們開始學習 Gradle 的核心 Task。
groovy/build.gradle
定義一個 Task:
1 2 3 4 5 |
task hello { doLast { println 'Hello,' } } |
執行命令,查看輸出:
1 2 |
$ ./gradlew hello Hello, |
Task 也可以這樣定義:
1 2 3 |
task World << { println 'World!' } |
注意,如果定義成這樣:
1 2 3 |
task hi { println 'description hi' } |
在進行初始化和配置的時候,下面語句就會運行。
1 | println 'hi' |
這種語法通常是用來定義 task 的描述信息。
Task 可設置 dependsOn 和 finalizedBy:
1 2 3 4 5 6 7 8 9 10 11 |
task hello { doLast { println 'Hello,' } } task intro(dependsOn: hello) << { println 'intro' } World.finalizedBy hello |
執行 intro 之前,會先執行 hello;執行 World 之后,會自動執行 hello。
Plugin
Gradle 的核心代碼,只提供了一個框架,具體的功能(如構建 Android 工程)是通過插件機制來實現的。
Gradle 提供了大量官方的插件,如 Maven、Groovy、Java、Publishing、Signing等,也有大量第三方的插件(Android),甚至每個人都可以自己實現一個插件(如 筆者開發的 Bugtags 插件,這個將在最后一篇講述)。
這些 plugin 定義了一系列的 task、DSL 和約定,在build.gradle 文件使用這些 plugin:
1 | apply plugin: java |
當你寫了一個獨立的 file_uri.gradle 文件,你可以通過:
1 | apply from: 'file_uri.gradle' |
來引入你的 gradle 文件,這個文件甚至可以在某個服務器上。
Gradle 實踐參考
學習了基礎理論之后,如果你還是不知道如何開始寫,那就先來實現一個自定義 apk 名稱的功能吧!
1 2 3 4 5 6 7 8 9 10 11 12 13 |
android.applicationVariants.all { variant ->//獲取 variant 參數,就是 productFlavor x buildType variant.outputs.each { output ->//獲取輸出文件 def file = output.outputFile//修改實例 output.outputFile = new File( (String) file.parent, (String) file.name.replace( file.name, // alter this string to change output file name "Your_Apk_Name_" + variant.name + "_" + variant.versionName + ".apk" ) ) } } |
你問我怎么知道 android 下有個 applicationVariants?其實我也不知道的,也得找文檔。
因為使用的是 Android 的插件,那就得在谷歌搜 “android gradle plugin dsl”,果然有個 Android Plugin DSL Reference。
點進去找找,里面有關於 build variant 的文檔: applicationVariants,既然是一個 Set,那就可以調用 all 方法。
寫代碼調試,再配合文檔,你就曉得該怎么寫了。
總結
從 Gradle 入門到現在略懂,經歷了大量懵懂的時光。最后狠下心去系統學習了 Groovy 和 Gradle 的基礎之后,最終茅塞頓開。希望讀者遇到類似的情況,一定要沉下心,多學多練。
在接下來的兩篇,我將分別介紹將發布遠程庫和編寫 Gradle 插件。