第一部分 — Android Gradle語法
目前,大多數講解 Gradle的文章都是先從複雜的 Gradle語法開始.而實際上, 對於 Android人員,掌握這些語法細節並沒有卵用,我們僅需要能看懂, 隨用隨查即可.那本文也是遵照 ‘實用’這個原則介紹 Android Gradle.相信,讀過本文, 你至少應該不在畏懼 Build Script了.
如果你對 build.gradle已經很熟悉,那麼直接參考 gooogle官方的 Android Plugin DSL Reference 即可.
下面進入主題.先來看下 As幫我們生成的有關於 Gradle的幾個文件(夾).
如上圖,標準的 As項目中, 包含三大部分:
· Top-level Gradle:用於配置所有的 Module屬性
· Moudle-level Gradle:配置獨立 Moudle的屬性
· Gradle Wrapper:用於統一編譯環境,一般供 CI 使用
下面來具體看看.
Top-level Build Script
有幾個概念,都來自於 Gradle Reference:
gradle script 都是 configuration scripts.這話的意思是說,運行起來後, 每個腳本文件最終都會對應到一個程序中的對象,這個對象叫做 delegate object.比如, build.gradle對應爲程序中的 Project.整個 Gradle 有三種類型的代理對象,分別是:
Type of script |
Delegates to instance of |
Build script |
|
Init script |
|
Settings script |
通過上面的知識我們可以知道,在任何 build.gradle中都有一個內置的變量 —project.
我們可以通過 gradle -qproperties 查看腳本中所有的內建屬性.
下面,我們一步一步來看看這個 Top-level Build Script:
buildscript
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.0'
// NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } |
buildscript { }
Configures thebuild script classpath for this project.
The givenclosure is executed against this project’s ScriptHandler.The ScriptHandler ispassed to the closure as the closure’s delegate.
Delegates to:
ScriptHandler from buildscript
簡單來說, buildscript 就是配置構建腳本所需要用到的 class的 path. 其內部會把 configuration closure傳遞給 ScriptHandler (你不用關心這是什麼)然後實現設置 repositories和 dependencies.
一般而言,這個 script block都寫在開頭,聲明這個腳本本身所需要的依賴.
allprojects
allprojects { repositories { jcenter() } } |
allprojects { }
Configures thisproject and each of its sub-projects.
This methodexecutes the given closure against this project and its sub-projects. Thetarget Project is passed tothe closure as the closure’s delegate.
Delegates to:
Each Project in allprojects
這個 configurationclosure裏的內容會被包括當前的 project以及其所有 sub-project執行.你在這裏配置的 repositories會在上述地方都生效.
這裏的寫法意味着,所有的 project都會在 jcenter()中尋找依賴.除此之外, 還可以指定 flatDir, maven, ivy 等.可以參考 RepositoryHandler.
task clean
|
task clean(type: Delete) { delete rootProject.buildDir } |
這是定義了一個新的 task,叫做 clean.其類型是 Delete.實際上, Android Plugin內置了 clean方法, 該方法位於 module中. Module中內置的 clean 方法只會清理 Module 中的文件並刪除 Module 中的 build 目錄,但是工程根目錄下的 build文件是沒人清理的,所以這裏定義的 clean方法即刪除項目目錄下的 build文件夾.
Module-level Build Script Build Variant
模塊級 script用於描述該模塊的編譯過程.一般而言, Android能用到的一共有三種:
· Application:對應 com.android.application.編譯的結果是一個 apk
· Android Library:對應 com.android.library.編譯結果爲 aar
· JavaLibrary:對應的 java.編譯結果爲 jar
你是不是好奇 android一共有多少種插件?可以看看 google android plugin 的倉庫
java 這個插件一般用不到,就不提了. com.android.library 和com.android.application 內容配置差不太多,這裏以com.android.application 爲例講解.
由於 Module-levelBuild Script大部分都是相應插件自行實現的內容,所以我們就不能再 gradle文檔中找了,我們要到 google系的 refs 中搜索 (見 refs).
你是不是好奇這個文件到底有多少個屬性?可以看看這個文件的源碼
ANDROID
android { compileSdkVersion 24 .... } |
其實 application 插件支持的屬性遠遠比這個要多,咱們來看一個全集:
apply plugin: 'com.android.application'
android { /** * 設置編譯 sdk 和編譯工具的版本 */ compileSdkVersion 24 buildToolsVersion "24.0.3"
/** * 關於簽名, 請參考 google 官方文檔: <a href="https://developer.android.com/studio/publish/app-signing.html#debug-mode">Sign Your App</a> */ signingConfigs { /** * As 會自動幫我們使用 debug certificate 進行簽名. 這個 debug certificate 每次安裝 As 都會變, * 因此不適合作爲發佈之用. */ debug { }
/** * 由於 Module-level Build Script(本文件) 也要放在 VCS 中管理, 所以不將密碼等信息寫在這裏. * 一般的做法是: 在本機設置環境變量, 然後通過下面代碼中演示的這種方式讀取. * 當然, 最佳實踐也指導我們將 `gradle.properties` <strong><em>排除</em>在 VCS 之外</strong>, * 此時, 也在該文件中將密碼設置爲變量, 然後在此讀取使用. */ release { storeFile file("$System.env.STORE_FILE") storePassword "$System.env.STORE_PASSWORD" keyAlias "$System.env.KEY_ALIAS" keyPassword "$System.env.KEY_PASSWORD" } }
/** * 爲所有的 build variants 設置默認的值. 關於 build variant, 我們後面會用一張圖片說明 */ defaultConfig { applicationId "com.walfud.myapplication" minSdkVersion 23 targetSdkVersion 24 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" }
/** * type 默認會有 debug 和 release. 不管你寫不寫都如此. * 通常, 我們在 debug 中保留默認值, release 中開啓混淆, 並使用私有的簽名 */ buildTypes { debug { // 使用默認值 }
release { // 混淆 minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// 簽名 signingConfig signingConfigs.release } }
/** * flavor 強調的是不同的版本, 比如付費版和免費版. * 在國內, 這個字段更多被用於區分不同的渠道, 即 360 渠道, 小米渠道等等. */ productFlavors { m360 {} xiaomi {} }
/** * 這個選項基本不用. * <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">官方說</a>: 使用 splits 可以比使用 flavor 更加有效創建多 apk. * 目前而言, 僅支持 Density 和 ABIs 這兩個分類. */ splits { // 按屏幕尺寸 density { enable true
// 默認包含全部分辨率, 這裏是剔除一些我們不要的 exclude "ldpi", "mdpi", "xxxhdpi", "400dpi", "560dpi", "tvdpi" }
// 按架構 abi { enable true
// 使用 `reset()` 後, 我們就相當於不包含任何架構, // 這種情況下我們就可以通過 `include` 指定想要使用的架構 reset()
include 'x86', 'armeabi-v7a' universalApk true // 是否同時生成一個包含全部 Architecture 的包 } } }
/** * 這個項目的依賴 */ dependencies { /** * `fileTree` 導入 libs 目錄下的所有 jar 文件 */ compile fileTree(dir: 'libs', include: ['*.jar'])
/** * 想導入本地 aar, 首先需要指明本地 aar 的位置, 如下 `repositories` 中所示, 我們把 aar 放在了 * Module-level 的 libs 目錄下. 然後引用這個文件即可. */ compile(name: 'components', ext: 'aar') }
/** * 配置了去哪裏查找這個模塊依賴文件 */ repositories { flatDir { dirs 'libs' } } |
Build Variant
簡單的說,就是爲了不同的渠道或者版本自動生成相應的 apk.其算法是 buildType * productFlavor * density * abi 做一個笛卡爾積.
至於優先級,參考 google 關於Build Variants的文檔.
第二部分 — As裏的 Gradle
1. 自動下載
每一次重新導入工程時,因爲默認會使用 Use default gradle wrapper (recommended) 設置,所以 As 會下載 gradle-wrapper.properties 中distributionUrl 所指定的 gradle 版本.這個過程在國內普遍巨慢無比.我的建議是找到上述文件,動手修改 distributionUrl 爲已下載過的 gradle版本(比如你之前已經下載過 gradle-2.14.1了),這樣就能直接打開了.
實際上,這個配置項位於 As自動生成的配置文件(.idea/gradle.xml),我們來看看:
對於新建 (‘File ->New -> New Project’)的項目, As是這樣給我們生成的:
它在設置中的表現是這樣的:
可見, As幫我限定了 gradle版本.當然, 如果這個指定的 gradle版本不存在的話,一樣會去下載.
再來看看被 ‘File ->Open’ (注意,不是 Reopen)打開的項目:
也就是說,每次你 Open一個項目的時候, As會忽略你之前設置的 gradle版本, 而使用 wrapper文件中所指定的版本.所以你 clone 下來的代碼第一次打開都會卡很久的原因,就是因爲他們在下載 wrapper中的 gradle啊…
2. gradle-wrapper.properties
AS 創建的工程,在根目錄下有 gradlew.bat 和 gradlew.sh 文件.這兩個文件會讀取 gradle/wrapper/gradle-wrapper.properties 並使用其中指定的 gradle 版本進行編譯.一般而言這套機制用於 CI服務器中來保障每次編譯都在同樣的環境下.
#Wed Nov 11 21:08:45 CST 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip |
GRADLE_USER_HOME 默認值是你的 USER_HOME/.gradle (win下是 c:\Users\<username>\.gradle, linux下是 ~/.gradle ).
對於 gradlew 而言,如果上述路徑沒有找到可執行的 gradle文件, 則會使用distributionUrl 中所指定的 url下載後在執行.
3. gradle.properties
主要作用配置一些與當前機器 gradle編譯相關的屬性.這些屬性是每個編譯機器根據自己情況決定的(比如,分配多大內存),因此不適合放在 git中.
3.1 property 分爲兩種
3.1.1. SYSTEM PROPERTY
類似於系統的環境變量.作爲 jvm啓動參數.
a) Set
|
# gradle.properties systemProp.xxx=yyy |
system property 都以 systemProp.xxx 爲模板.
只有 top-level gradle.property 才能設置 system property.
系統提供瞭如下幾個內置變量:
org.gradle.daemon
是否開啓 daemon.一般來說,本機編譯建議打開, CI上建議關閉.
org.gradle.java.home
指定 gradle
進程的 java home.
沒什麼卵用.
org.gradle.jvmargs
daemon 進程的 jvm參數.當你編譯報錯
OOM 的時候,
可以調整這個參數 (見後面的例子)
org.gradle.configureondemand
自行 google.
沒卵用
org.gradle.parallel
project 之間並行編譯
當然,我們也可以通過命令行中指定 -D 來設置 system property.
b) Get
1 2 |
// build.gradle System.properties['xxx'] |
3.1.2. PROJECT PROPERTY
一般用作 project內部的變量,保存用戶名密碼之類的私有值.
a) Set
|
# gradle.properties xxx=yyy |
也可以通過命令行中指定 -P 來設置 project property
b) Get
|
// build.gradle println xxx |
3.2.設置代理
systemProp.https.proxyHost=www.proxyhost.org systemProp.https.proxyPort=8080 systemProp.https.proxyUser=userid systemProp.https.proxyPassword=password systemProp.https.nonProxyHosts=*.nonproxyrepos.com|localhost |
3.3. 優先級 (下面的優先級更高)
project 目錄下的 gradle.properties
$GRADLE_USER_HOME/gradle.properties
環境變量或者命令行中使用 -Dsystem.property或 -Pproject.property設置
常見的 ‘這是什麼玩意兒’
transitive = true
|
compile('com.crashlytics.sdk.android:answers:1.3.10@aar') { transitive = true; } |
簡單地說,由於該語句使用 @aar notation,所以 gradle 只會下載這一個 aar文件, 而不會順帶着下載這個 aar所需要的依賴文件.所以需要 transitive 讓依賴能夠自動被下載.一般而言,去掉 @aar 以及 { transitive = true } 不會有任何問題.
原文地址:http://android.walfud.com/android-gradle-%E7%9C%8B%E8%BF%99%E4%B8%80%E7%AF%87%E5%B0%B1%E5%A4%9F%E4%BA%86/