深入理解gradle編譯-Android基礎篇
導讀
Gradle基於Groovy的特定領域語言(DSL)編寫的一種自動化建構工具,Groovy作為一種高級語言由Java代碼實現,本文將對Gradle一些常見問題進行一一介紹:
- 理解Gradle與android app之間的關系,以及Gradle需要構建的build文件。
- 在Android Studio中執行Gradle命令。
- 在Android app添加java庫文件。
- 將eclipse工程導入Eclipse ADT工程
- 如何為一個APK文件進行數字簽名
1.Android Gradle基礎
android應用程序使用開源工具Gradle構建。Gradle一種藝術API,非常容易的支持定制,並且在java世界有着廣泛的應用。Android為了實現編譯、打包等,開發者開發了Android插件為Gradle添加了一系列的新特征,特別是在構建Android app上的應用,包括:構建類型、多樣化、簽名配置、庫文件工程等等功能。
1.1 Android Gradle構建文件
在我們使用Android Studio工具開發Android應用的時候,當創建一個新的Android工程,默認的Gradle構建文件包括了setting.gradle, build.gradle和app/build.gradle。具體位置如圖所示。

[--> setting.gradle]
include ':app'
[--> build.gradle]
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.5.0+' } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
其實原始的Gradle默認情況下並不包含Android功能。Google為Gradle提供了Android插件,允許用戶很容易的配置android工程編譯腳本。編譯腳本(buildscript)在編譯工程的根目錄,構建文件(build.gradle)用來告知Gradle去哪里下載對應的插件。
從上面列出的代碼中我們可以看到插件的默認下載是從jcenter中,意味着jcenter就是當前的目標庫。雖然jcenter倉庫是當前默認的,但是其它的倉庫也是支持的,尤其是mavenCenteral()作為maven的遠端默認倉庫。JCenter倉庫的所有內容通過一個CDN經由HTTPS連接傳輸,速度也是很快的。
上面代碼中的allprojects部分表示當前的根目錄工程和所屬的子工程默認情況下都使用jcenter()遠端倉庫用來支持java庫的依賴。
Gradle允許用戶定義很多任務(tasks),並插入到有向無環圖(directed acyclic graph,DAG)中,Gradle通過DAG來解析任務之間的依賴關系。在上面代碼中一個clean任務已經添加到根目錄的構建文件中。其中的type: Delete表示依賴Gradle中定制已有的Delete任務。在這種情況下,該任務會移除在工程根目錄下的構建目錄(也就是build目錄)。
app作為項目工程的module,內部需要包含build.gradle來支持module編譯,接下來來看一下app目錄下的build.gradle。
[--> app/build.gradle]
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.kousenit.myandroidapp" minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' }
這部分的代碼功能並非由Gradle工具提供,是由Android插件構建系統提供,通過加入android標簽,允許android塊使用DSL(Domin Specific Language)編寫配置。
dependencies部分包含了三行。
- 第一行使用
fileTree做依賴,表示所有在libs目錄下的以.jar為后綴名的文件添加到編譯路徑中。 - 第二行告訴Gradle去下載版本為4.12的JUnit並為其添加命名testCompile。依賴着JUnit類將在
src/androidTest/java路徑下生效,用來增加測試單元(本文沒有介紹測試)。 - 第三行告訴Gradle添加appcompat-v7,版本為23.3.0,從JCenter支持庫中獲取。
1.2 默認配置
[--> app/build.gradle]
apply plugin: 'com.android.application' android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.kousenit.myandroidapp" minSdkVersion 19 targetSdkVersion 23 versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 } }
在build.gradle文件的頂部添加Android應用程序插件。Module(模塊)編譯文件通過apply plugin: 'com.android.application',加載應用程序插件,從而使Gradle DSL支持android標簽。
android DSL使用模塊方式嵌入。必須指出編譯目標版本(compileSdkVersion)和編譯工具版本(buildToolsVersion)。兩個值盡量跟進較近的版本,而非老版本,因為新版本會修復老版本工具含的一些bug。
| 屬性 | 解釋 |
|---|---|
| applicationId | 應用的包名,該值必須唯一 |
| minSdkVersion | 應用支持的最小Android SDk版本 |
| targetSdkVersion | 應用目標版本,Android studio會根據當前版本提示相應的警告信息 |
| versionCode | 用一個整數表示當前app的版本,用以升級使用 |
| versionName | 用一個字符表示當前app版本名稱,用以升級使用 |
轉到Gradle之后,minSdkVersion和buildToolsVersion屬性被指定,這兩個屬性和Android Manifest中的<uses-sdk>標簽屬性內容一致。Android Manifest中的<uses-sdk>標簽已經被取消,如若值仍然存在在Manifest中,值將被Gradle中的值覆蓋。


1.3 使用命令行執行Gradle腳本
從命令行需要用戶提供Gradle wrapper或者安裝Gradle並直接運行。
[gradle-wrapper.properties]
#Thu Sep 22 19:09:25 CST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
其中distributionUrl屬性表示wrapper包將要下載和安裝gradle-2.14.1版本。在第一次執行結束之后,Gradle目標將被緩存在zipStorePath文件夾,在zipStoreBase目錄之下。以后每次構建將直接使用緩存的版本構建任務。
命令非常簡單:
./gradlew build
最后輸出的apk在app/build/outputs/apk目錄下。我們也可以執行多任務通過空格:
./gradlew lint assembleDebug
其中查看依賴樹通過:
./gradlew anDep
取消編譯任務: ./gradlew pro
如果不想使用編譯build.gradle文件,使用-b切換編譯腳本文件:
./gradlew -b app.gradle
1.4 在android Studio上執行編譯腳本
如何在Android Studio環境下,執行編譯任務?當我們創建Android工程后,Android Studio會為多個工程腳本生成Gradle構建文件。IDE提供了Gradle任務列表的可視化視圖,如下圖所示:

Gradle提供了很多分類,像android, build, install和other。執行某一個任務的時候只需要雙擊具體的名稱,在Gradle窗口中。每次運行一個特殊的task,運行配置就會被創建並存儲在Run Configurations菜單下,所以再次運行的時候可以在這里選擇。
Android studio提供了Gradle Console輸出相應的編譯信息。
1.5 為項目工程增加依賴庫
默認情況下,Android應用包含了兩個gradle文件:一個在根目錄下,一個在應用目錄下。在應用目錄下的gradle腳本需要增加依賴:
[app/build.gradle]
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' }
每一個依賴關系表示了一個配置。Android工程的依賴包含了編譯、運行、測試編譯、測試運行。完整的依賴包含三個部分:group, name, 和version信息。插件可以增加額外的配置信息,也能夠定義自己的信息。完整的信息如下:
testCompile group: 'junit', name: 'junit', version: '4.12'
簡寫為:
testCompile 'junit:junit:4.12'
版本簡寫:
testCompile 'junit:junit:4.+'
如果你想配置相應的文件,可通過files和fileTree增加依賴:
dependencies { compile files('libs/a.jar', 'libs/b.jar') compile fileTree(dir: 'libs', include: '*.jar') }

傳遞依賴:
./gradlew androidDependencies
Gradle默認情況下是開始網絡檢查依賴庫的,如果有特殊需求需要關閉,采用transitive標志關閉網絡請求:
dependencies {
runtime group: 'com.squareup.retrofit2', name: 'retrofit', version: '2.0.1', transitive: false }
將transitive標志改成false會阻止依賴的下載。所以如果需要的話必須加載自己的本地。如果希望模塊是jar文件,寫法如下:
dependencies {
compile 'org.codehaus.groovy:groovy-all:2.4.4[@jar](https://my.oschina.net/u/1767170)' compile group: 'org.codehaus.groovy', name: 'groovy-all',version: '2.4.4', ext: 'jar' }
1.6 為android studio增加依賴庫
有經驗的開發者可以很輕松的編輯build.gradle文件,而不需要借助IDE的幫助。但是IDE也給出了相應的編輯視圖。
打開File->Project Structure,選擇相應的modules即可對build.gradle進行編輯。如下圖所示:

- 選擇Porperties選項卡,可以查看Compile SDK Version 和 Build Tools Version等信息。
- 選擇Dependencies選項卡,可以查看依賴的庫、工程等信息。

其中Dependencies選項卡中的Scope行允許用戶配置依賴庫是提供到apk中,還是只是編譯的時候依賴:
- Compile
- Provided
- APK
- Test compile
- Debug compile
- Release compile
1.7 配置倉庫
在Gradle的dependencies是怎么樣精准的找到相應的依賴的呢?通過在repositories配置的,所有的dependencies都會去找到相應的依賴,才可以正常編譯。默認倉庫為JCenter。注意當前使用HTTPS連接。
repositories { mavenLocal() mavenCentral() }
一些Maven庫也可以通過URL添加,如下例添加一個maven庫:
repositories { maven { url 'http://repo.spring.io/milestone' } }
密碼保護倉庫可以使用credentials模塊來表示,通過用戶名和密碼的校驗來獲取依賴倉庫,代碼如下所示:
repositories { maven { credentials { username 'username' password 'password' } url 'http://repo.mycompany.com/maven2' } }
也可以將用戶名和密碼值移到gradle.properties文件中。Ivy和local倉庫語法類似,參考下例:
repositories { ivy { url 'http://my.ivy.repo' } }
當使用本地文件,可以通過flatDir語法來創建倉庫,。如下例所示:
repositories { flatDir { dirs 'libs' } }
使用flatDir是使用files或是fileTree方案的一種替代,fileTree需要指定路徑,就不再需要指定flatDir本地文件,但是aar文件依賴的時候需要指定本地庫,使用flatDir標簽。當我們為應用程序構建添加很多倉庫。Gradle輪詢每一個倉庫,直到解析完畢所有的依賴,找到所有依賴,否則會報出錯誤。
2.項目工程導入與發布
2.1 設置工程屬性
當你想為工程添加外部屬性或者是硬編碼值的時候,可以使用ext標簽來添加。要從編譯文件中刪除它們並放入到gradle.properties文件中,或者通過命令行使用-P標志設置。
Gradle構建文件(build.gradle)支持屬性定義,使用ext關鍵字,使用“ext”作為“extra”簡寫。這使得變量定義非常方便。這些屬性可以硬編碼到build.gradle文件,如下代碼所示:
ext {
AAVersion = '4.0-SNAPSHOT' // change this to your desired version } task printProperties << { println AAVersion }
使用ext是常規Groovy語法的應用,意味着類型化AAVersion為String類型,該變量通過printProperties任務打印。在構建文件中,使用def關鍵字實現本地變量聲明,而且只有當前構建文件可以使用。如果不加def關鍵字,變量可以在工程中使用,工程及子工程都是可以使用的。
def AAVersion = '4.0-SNAPSHOT' task printProperties << { println AAVersion }
對於我們依賴的倉庫,有時候需要校驗用戶身份,就需要我們輸入用戶名和密碼,此時也許你希望刪除在build.gradle構建文件中的實際值,考慮到Maven庫中的登錄憑證,如下所示:
repositories { maven { url 'http://repo.mycompany.com/maven2' credentials { username 'user' password 'password' } } }
這里不希望保留真實的用戶名和密碼值在build.gradle構建文件中,添加它們到工程根目錄下的gradle.properties文件中,如下所示:
[--> gradle.properties]
login='user' pass='my_long_and_highly_complex_password'
這樣credentials部分可以通過變量值來替代,如下:
repositories { maven { url 'http://repo.mycompany.com/maven2' credentials { username login password pass } } }
這里可以通過命令行設置對應是屬性值,使用-P參數:
gradle -Plogin=me -Ppassword=this_is_my_password assembleDebug
具體演示如下所示“
ext {
// 檢測工程屬性是否存在 if (!project.hasProperty('user')) { user = 'user_from_build_file' } if (!project.hasProperty('pass')) { pass = 'pass_from_build_file' } } task printProperties() { doLast { // 打印屬性 println "username=$user" println "password=$pass" } }
執行printProperties任務可以打印相應的屬性,不需要任何外部配置,這需要在ext塊設置相應的值。打印如下:
> ./gradlew printProperties :app:printProperties username=user_from_build_file password=pass_from_build_file
增加相應的-P標志后:
> ./gradlew -Puser=user_from_pflag -Ppass=pass_from_pflag printProperties :app:printProperties username=user_from_pflag password=pass_from_pflag
結合"extra"塊,屬性文件和命令行標志足以滿足我們的需求。
2.2 將Eclipse ADT程序移植到Android Studio
將Eclipse ADT程序移植到Android Studio中,將其變成Gradle目錄結構,具體如下所示:

在Eclipse ADT工程中,老的目錄結構為res, src和AndroidManifest.xml所有直接在root目錄下。導入過程中會將其老的工程變成新的目錄結構。在構建文件build.gradle中建立dependencise依賴關系,具體log信息如下:
ECLIPSE ANDROID PROJECT IMPORT SUMMARY ====================================== Ignored Files: -------------- The following files were *not* copied into the new Gradle project; you should evaluate whether these are still needed in your project and if so manually move them: * proguard-project.txt Moved Files: ------------ Android Gradle projects use a different directory structure than ADT Eclipse projects. Here's how the projects were restructured: * AndroidManifest.xml => app/src/main/AndroidManifest.xml * assets/ => app/src/main/assets * res/ => app/src/main/res/ * src/ => app/src/main/java/ Next Steps: ----------- You can now build the project. The Gradle project needs network connectivity to download dependencies. Bugs: ----- If for some reason your project does not build, and you determine that it is due to a bug or limitation of the Eclipse to Gradle importer, please file a bug at http://b.android.com with category Component-Tools. (This import summary is for your information only, and can be deleted after import once you are satisfied with the results.)
其中ProGuard文件被推薦使用,其余的變化就是文件目錄的調整變動。頂層生成的gradle.build文件和創建一個新的工程是一樣的。
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.5.0+' } } allprojects { repositories { jcenter() } }
App如下的構建文件build.gradle,如下代碼,如果需要額外的jar庫還需要增加dependencies模塊。
apply plugin: 'com.android.application' android { compileSdkVersion 17 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.example.tips" minSdkVersion 8 targetSdkVersion 17 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } } }
最后生成的settings.gradle文件,顯示了當前app工程包含的module目錄。
include ':app'
其中AndroidManifest.xml文件沒有任何改動。
2.3 使用Gradle輸出Eclipse ADT工程目錄結構
Android開發工具(ADT)的Eclipse插件,是構建Android工程主要的IDE。Gradle構建在2013引入。ADT的項目現在已經啟用,Android Studio是目前支持的IDE,但遺留項目仍然存在。ADT插件可以生成Gradle構建文件,基於當前的目錄結構和依賴。
注意:上一節中介紹從ADT到Android Studio的import過程。export過程不在被推薦
依賴於老的目錄結構不再被推薦,這里介紹只是做一下簡單說明,練習中我們可以使用。Gradle提供了sourceSet映射,下面展示了如果在Gradle中對老的目錄結構進行映射。
在Eclipse ADT結構中,所有的源代碼在一個目錄中src,在工程的根目錄。資源在res文件夾總,也放在根目錄中。Android的manifest.xml文件也在根目錄下。所有的這些在新的目錄結構中都已經變化。那么我們如何通過gradle映射原始的目錄結構呢?
android {
compileSdkVersion 18 buildToolsVersion "17.0.0" defaultConfig { minSdkVersion 10 targetSdkVersion 17 } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aild.ext.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] } // Move the tests to tests/java, tests/res, etc... instrumentTest.setRoot('tests') // Move the build types to build-types/<type> // For instance, build-types/debug/java, ... // This moves them out of them default location under src/<type>/... // which would conflict with src/ being used by the main source set. // Adding new build types or product flavors should be accompanied // by a similar customization. debug.setRoot('build-types/debug') release.setRoot('build-types/release') } }
2.4 更新最新版本的Gradle
Android Studio包含了Gradle構件庫。當我們創建一個新的android應用的時候,IDE自動為Linux系統生成gradlew腳本,為Window系統生成gradlew.bat腳本。這些包裝語法允許我們不安裝任何東西直接使用gradle。然而,包裝的語法會為我們下載相應版本的Gradle。
當項目已經持續了一段時間,但是,Gradle定期發布了新的版本。你也許希望更新當前工程的Gradle版本,譬如性能原因(希望更快)或是一些新特性需要添加到工程中。實現這些,有兩種選擇:
- 在根目錄的build.gradle中修改依賴,編譯的時候會主動拉取版本的gradle,並使用其編譯。
- 直接編輯gradle-wrapper.properties的distributionUrl的值。
第一種改動是在build.gradle,修改其dependencies中的classpath,gradle會自動到相應的jcenter()代碼庫中拉取當前對應版本的gradle,相應修改如下所示:
buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.5.0+' } }
第二種是通過更改gradle-wrapper.properties的distributionUrl的值來更改gradle版本,如下代碼所示:
distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
當然第一種方案是首選的方案。
2.5 工程之間共享配置
當我們想為多個module設置相同的配置時候,在頂層的Gradle build文件中,使用allprojects和subprojects進程聲明。當我們在Android Studio創建一個新的Android工程后,IDE創建兩個build.gradle,一個在頂層一個在module app中。在頂層的build.gradle文件有一個標簽叫做allprojects,如下所示:
allprojects { repositories { jcenter() } }
標簽來自Gradle DSL,因此可以為工程下所有gradle模塊工作,而不僅僅是android工程。allprojects屬性來自Gradle API,是org.gradle.api.Project類的一個屬性。這個屬性包含當前工程和所有子工程。還有一個屬性subprojects只允許子工程使用。
通過allprojects集合我們可以為每一個工程設置通用屬性,默認情況是根目錄工程和app module。這樣我們可以無需重復的為每一個module設置倉庫,因為我們可以全局設置。
使用subprojects模塊替換方案。例如,如果我們有多個Android庫工程,每一個工程的構建腳本中都需要加入apply plugin: 'com.android.library'。如果工程中都是Android庫工程,你可以去掉重復的聲明,直接在頂層加入subprojects聲明,實現子工程共享屬性。
subprojects { apply plugin: 'com.android.library' }
額外考慮
當我們查看Gradle DSL參考文檔的時候,在介紹allprojects的地方,會發現allprojects使用了org.gradle.api.Action作為了參數。
void allprojects(Action<? super Project> action)
方法中調用executes執行所有Action,調用subprojects. Action<T> 執行,這里就不再介紹了。
2.6 為一個APK簽名
Android APK在發布前都需要進行數字簽名。默認情況下Android會為我們簽一個debug的數字簽名,使用本地帶的一個key。我們也可以通過keytool命令簽名。debug密鑰庫存儲在用戶設備中中,在我們home文件夾中,命名為.android。默認密鑰庫名稱為debug.keystore,並且密鑰密碼為android。
keytool -list -keystore debug.keystore 輸入密鑰庫口令:("android") 密鑰庫類型: JKS 密鑰庫提供方: SUN 悅德財富:https://www.yuedecaifu.com 您的密鑰庫包含 1 個條目 androiddebugkey, 2016-9-28, PrivateKeyEntry, 證書指紋 (SHA1): 6A:A8:B7:B6:A6:AA:73:BD:EE:9D:31:96:68:21:47:A3:FA:2C:23:2B
keystore類型為JKS,代表了Java的KeyStore,使用了公私鑰機制。Java支持其他類型JCEKS(Java Cryptography Extensions KeyStore),用來做共享密鑰,但是沒用用在Android應用上。
當我們生成debug的APK的時候,安裝在設備和模擬器上是否需要證書呢?答案是肯定的,android為我們提供了通用證書(keystore),使用androiddbugkey作為序列用來給所有的debug apk提供簽名。如果一個app沒有簽名,是不能發布的,這就要求我們必須使用證書對其進行簽名。證書使用keytool工具生成。生成方法如下:
keytool -genkey -v -keystore myapp.keystore -alias my_alias -keyalg RSA -keysize 2048 -validity 10000
具體需要輸入內容,按要求輸入即可。生成證書之后怎樣在每次運行的時候進行簽名呢?配置如下所示:
android {
// ... other sections ... signingConfigs { release { keyAlias 'my_alias' keyPassword 'password' storeFile file('/Users/kousen/keystores/myapp.keystore') storePassword 'password' } } }
你也可能不想將密碼放入到構建文件中。幸運的是,你可以放入到gradle.properties文件中或者在命令行中設置。
在DSL文檔中,signingConfigs模塊是由signingConfig類進行實例化的同時也表示了signingConfig類實例,具體屬性參考如下:
| 屬性 | 解釋 |
|---|---|
| keyAlias | keytool中生成證書的時候的隨機值 |
| keyPassword | 在簽名過程中所需要的密鑰 |
| storeFile | keystore證書在磁盤中的位置 |
| storePassword | keystore證書的密碼 |
同時也需要在buildTypes的release中加入簽名過程,具體如下所示:
android { // ... other sections ... buildTypes { release { // ... other settings ... signingConfig signingConfigs.release } } }
2.7 借助Android Studio為一個APK簽名
當你想通過Android Studio配置簽名,並生成相應的簽名后的APK該如何呢?Android Studio提供了生成keystore的方法:Build → Generate Signed APK選項:

點擊“Create new…”,彈出生成keystore的信息填寫頁,填寫相應的信息即可:

如果你選擇一個已經存在的keystore,輸入相應的密鑰和隨機序列可以直接使用該keystore,如下圖所示:

