Android Gradle 看這一篇就夠了

目前, 大多數講解 Gradle 的文章都是先從複雜的 Gradle 語法開始. 而實際上, 對於 Android 人員, 掌握這些語法細節並沒有卵用, 我們僅需要能看懂隨用隨查即可. 那本文也是遵照 ‘實用’ 這個原則介紹 Android Gradle. 相信, 讀過本文, 你至少應該不在畏懼 Build Script 了.

如果你對 build.gradle 已經很熟悉, 那麼直接參考 gooogle 官方的 Android Plugin DSL Reference 即可.

下面進入主題. 先來看下 As 幫我們生成的有關於 Gradle 的幾個文件(夾).

%e6%97%a0%e6%a0%87%e9%a2%98

如上圖, 標準的 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 scriptDelegates to instance of
Build scriptProject
Init scriptGradle
Settings scriptSettings

通過上面的知識我們可以知道, 在任何 build.gradle 中都有一個內置的變量 —project(就是代表這個 build.gradle 的 delegate object).project

我們可以通過 gradle -q properties 查看腳本中所有的內建屬性.

下面, 我們一步一步來看看這個 Top-level Build Script:

buildscript

1
2
3
4
5
6
7
8
9
10
11
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 the build script classpath for this project.

The given closure is executed against this project’sScriptHandler. The ScriptHandler is passed to the closure as the closure’s delegate.

Delegates to:
ScriptHandler from buildscript

簡單來說, buildscript 就是運行構建腳本所需要用到的依賴的 classpath. 其內部會把 configuration closure 傳遞給 ScriptHandler (你不用關心這是什麼) 然後實現設置 dependencies(構建腳本運行所依賴的文件) 和 repositories(依賴文件的查找位置).

一般而言, 這個 script block 都寫在開頭, 聲明這個腳本本身所需要的依賴.

allprojects

1
2
3
4
5
allprojects {
    repositories {
        jcenter()
    }
}

allprojects { }

Configures this project and each of its sub-projects.

This method executes the given closure against this project and its sub-projects. The target Project is passed to the closure as the closure’s delegate.

Delegates to:
Each Project in allprojects

這個 configuration closure 裏的內容會被設置爲當前 project 以及其所有 sub-project 中依賴文件的查找路徑.

這裏的寫法意味着, 所有的 project 都會在 jcenter() 中尋找依賴. 除此之外, 還可以指定 flatDirmavenivy 等. 可以參考 RepositoryHandler.

task clean

1
2
3
task clean(type: Delete) {
    delete rootProject.buildDir
}

這是定義了一個新的 task, 叫做 clean. 其類型是 Delete. 實際上, Android Plugin 內置了 clean 方法, 該方法位於 module 中. Module 中內置的 clean 方法只會清理 Module 中的文件並刪除 Module 中的 build 目錄, 但是工程根目錄下的 build 文件是沒人清理的, 所以這裏定義的 clean 方法即刪除項目目錄下的 build 文件夾.

順便來看一下 settings.gradle:

1
include ':app'

這文件內容一看就懂, 是管理 sub-project. 凡是要涉及到編譯的子項目, 都要寫在這裏, 這樣 gradle 就按照這個配置遞歸編譯子項目了.

Module-level Build Script

模塊級 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-level Build Script 大部分都是相應插件自行實現的內容, 所以我們就不能再 gradle 文檔中找了, 我們要到 google 系的 refs 中搜索 (見 refs).

你是不是好奇這個文件到底有多少個屬性? 可以看看這個文件的源碼

ANDROID

1
2
3
4
android {
    compileSdkVersion 24
    ....
}

其實 application 插件支持的屬性遠遠比這個要多, 咱們來看一個全集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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 服務器中來保障每次編譯都在同樣的環境下.

1
2
3
4
5
6
#Wed Nov 1121:08:45CST 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
1
2
# 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
1
2
# gradle.properties
xxx=yyy

也可以通過命令行中指定 -P 來設置 project property

b) Get
1
2
// build.gradle
printlnxxx

3.2.設置代理

1
2
3
4
5
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

1
2
3
compile('com.crashlytics.sdk.android:answers:1.3.10@aar') {
    transitive = true;
}

簡單地說, 由於該語句使用 @aar notation, 所以 gradle 只會下載這一個 aar 文件, 而不會順帶着下載這個 aar 所需要的依賴文件. 所以需要 transitive 讓依賴能夠自動被下載. 一般而言, 去掉 @aar 以及 { transitive = true } 不會有任何問題.

Refs:

一般而言, google 系的文章只會告訴你如何配置會產生怎樣的效果, 而 gradle 系的文章會告訴你這麼配置的原理是什麼. 個人撰寫的博客用於快速打通.

個人系:

深入理解Android(一):Gradle詳解

google 系:

Android 官方: New Build System

Android 官方: Configure Your Build 

Android 官方: Android Plugin for Gradle Release Notes

Android Plugin DSL (有了這個, 媽媽再也不怕我找不到函數了)

Android Plugin 源碼倉庫

com.android.application 的源碼

Android Plugin 的 Bintray 倉庫

gradle 系:

Gradle Build Language Reference

gradle.properties 中的變量: The Build Environment


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