Gradle是Android Studio默認的構建工具,如果是基本的APP開發,不會涉及到Gradle太多內容,畢竟它的誕生就不是專為Android服務的。
日常開發需要涉及到使用Gradle的場景相當有限,比較頻繁的就是對應庫,如jar,.so文件的導入,如果應用本身方法數比較多,尤其是導入太多第三方庫就容易出現這個問題,就需要用到MultiDex的相關內容,如果需要在編譯的時候區分debug和release等版本,是否混淆或者自動打包等,這些都會涉及到Gradle的編寫,但網上都有現成的例子,直接拿來用就可以一直保持Gradle文件在應用版本迭代中基本保持不變。
所以這里有一個關鍵的地方:如果要學習Gradle,界限在哪里。
從Android開發人員角度來看,這是一個相當重要的問題,畢竟Gradle也是Android開發中相當重要的部分,如果對這塊不熟悉的話,就有種表面上走在康庄大道,但實際上卻是被固定住頭部不能往下看腳底踩的到底是不是正常的路的感覺。
首先,我們在創建Android應用程序的時候,Android Studio默認的Android結構已經顯示得很清楚的了:
我們來看看Gradle Scripts的相關文件。
看一下第一個build.gradle文件。
它后面的說明表明這是Project的build.gradle文件,也就是根目錄的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:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
buildScript在Groovy(Gradle的DSL,domain specific language,領域專門語言)中是一種method的調用,傳入的參數為configuration closure,執行后會對Project的屬性進行配置。
Closure,閉包,對於程序員來說,是一個相當熟悉的概念,不同語言都有閉包的實現和它們各自的意義,而Groovy是基於JVM的語言,它的閉包和Java是一致的,相當於一個匿名函數。
在Groovy中,我們可以這樣寫一個閉包:
{ a, b -> a + b }
如果參數只有一個,我們可以用it來替代:
{ it -> print it}
甚至可以連這個it都可以省略,直接寫成:
{ print it }
因此,我們可以理解上面這個閉包為什么會這樣寫。
repositories表示代碼倉庫的下載來源,這里的來源是jcenter。
Gradle支持的代碼倉庫有幾種類型:
(1)Maven中央倉庫,不支持https訪問,聲明方法為mavenCentral()
(2)JCenter中央倉庫,實際上也是用Maven搭建,通過CDN分發,並且支持https訪問,也就是我們上面默認的聲明方法:jcenter,如果我們想要切換成http訪問,就要修改配置:
repositories { jcenter { url "http://jcenter.bintray.com" } }
(3)Maven本地倉庫,可以通過本地配置文件配置,通過USER_HOME/.m2/
下的settings.xml配置文件修改默認路徑位置,聲明方法為mavenLocal()
(4)常規的第三方maven庫,需要設置訪問url,聲明方法為maven,這個一般是有自己的maven私服
(5)Ivy倉庫,可以是本地倉庫,也可以是遠程倉庫
(6)直接使用本地文件夾作為倉庫,聲明如下:
repositories { flatDir { dirs 'lib' } flatDir { dirs 'lib1', 'lib2' } }
Gradle會優先使用服務器倉庫,如果沒有才會去找本地倉庫。
一般的項目需求采用的第三方基本都會提交到JCenter,所以大部分情況下直接使用默認的就行,考慮到國內訪問速度,可以提前下載好對應的庫,然后放到自己或者公司的maven私服上,再指定對應的maven地址。
dependencies表明項目依賴對應版本的Gradle構建工具,但更加具體的版本信息卻是在gradle-wrapper.properties這個文件中,具體如:
#Mon Dec 28 10:00:20 PST 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-2-10。
allprojects指定所有參與構建的項目使用的倉庫的來源。
這里我們有一個疑問:buildscript和allprojects都指定使用的倉庫的來源,它們的真正區別在哪里呢?
實際上,allprojects是用於多項目構建,在Android中,使用多項目構建,其實就是多Module構建。
我們看settings.gradle這個文件:
include ':app'
通過include將app這個module添加進來。
從根目錄開始,一直到include進來的所有module,都會執行allprojects的內容。
我們也可以通過subprojects來指定module和根目錄不同的行為。
我們是否可以指定某個module執行它特有的行為呢?
當然可以,通過project(':moduleName'){}就能指定該module自己的行為。
在Android中,多項目的構建是很正常的,因為我們添加的library也是一個module,不過一般而言,子項目和根項目的配置大部分情況下都是一樣的,大部分操作都是在app module中,而其他library module只是提供API而已。
buildscript主要是為了Gradle腳本自身的執行,獲取腳本依賴插件,在Android中,我們的構建腳本就是Gradle。
repositories也可以是根級別的,為當前項目提供所需的依賴包,但在Android中,這個跟allprojects的repositories的作用是一樣的。
同樣dependencies也可以是根級別的。
我們在Android Studio 2.0正式版本中生成應用的根目錄的build.gradle文件中,結尾還看到這樣的代碼:
task clean(type: Delete) {
delete rootProject.buildDir
}
這里聲明了一個clean的task,它會在我們執行gradle clean時,刪除根目錄的build目錄。
看一下這個面板:
可以看到各種task,我們的應用之所以能夠編譯,運行和安裝,都是這些task在發揮作用。
我們再看一下proguard-rules.pro這個文件,這個是混淆文件,在這里添加項目的混淆規則。
gradle.properties可以設置gradle喚起的daemon進程,比如JVM參數,如果Gradle編譯速度和網速沒有關系,那么有可能是Gradle的daemon進程的JVM參數太小了,因此可以在這里進行配置。
local.properties是指定SDK的存放地址,在Android開發中,.gitignore文件默認就有忽略這個文件的設置,因為每個人的SDK位置都有可能是不同的。
Android Studio提供了多種工程目錄結構,其中最常見的是Android和Project,Project和Eclipse是一樣的,而.gitignore也只有在Project工程結構下才能看到。
根目錄的builde.gradle文件我們一般都不會太多去改,絕大部分的工作都是在對應module的builde.gradle文件。
我們看一下app的build.gradle文件:
apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "com.example.weber_zheng.myapplication" minSdkVersion 15 targetSdkVersion 25 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:25.3.1' }
apply plugin表明應用的插件的類型,這里是com.android.application類型,而庫工程則是com.android.library,如果是java項目,則是java。
Gradle中的plugin可以理解為一個已經定義好的模塊,我們只要通過apply添加進來即可,對於Android開發來說,只要知道自己的module需要引入的插件是application還是library就可以了,當然,如果確實需要第三方插件,也可以導進來,類似bugtags這種插件。
android中就是Android插件的相關配置,我們很多操作基本都是在這里。
我們關注buildTypes,這里只有release一種編譯類型,實際上,可以添加debug等其他自定義的類型。
在buildTypes中,minifyEnabled控制是否開啟混淆,而proguardFiles指定了混淆文件,就是前面提到的proguard-rules.pro文件。
dependencies和build.gradle文件中的dependencies是一樣的作用,如果我們想要編譯工程,可以compile project(':projectName'),編譯aar包,可以compile(name: 'aarName', ext: 'aar')。
一般build.gradle在配置完成后,都不會有太大的改動,而經常改動的地方就是添加依賴的時候。
我們可以在dependencies中添加依賴,依賴的類型常見是三種:jar,.so,aar和project。
默認的builde.gralde文件都會有這一句:compile fileTree(dir: 'libs', include: ['*.jar']),它表示編譯libs目錄下的.jar文件,所以如果我們有新的jar包,都放在libs目錄里面,如果make工程都沒反應,可以在Gradle那里點擊同步。
但如果有需求,需要放在libs的子目錄呢?
這時候是找不到這個.jar文件的,可以在compile中顯示的指定.jar文件的具體路徑,不過也可以更加簡單點,fileTree的參數是一個Map,dir只能指定一個目錄,但是include可以指定多個,因此可以修改為類似這樣['*.jar'], ['test/*.jar']。
如果我們想要編譯.so文件,就要指定jinLibs的目錄,而這個動作是通過sourceSets類執行:
sourceSets {
main {
jniLibs.srcDirs = ['libs']//指定lib庫目錄
}
}
這里指定了jinLibs的目錄是libs,因此我們可以將.so文件直接放在libs目錄下。
Android開發中,不可避免的會導入各種第三方庫,但如果第三方庫本身也導入了和我們主工程一樣的庫,就會報錯,因此要排除第三方庫中重復的庫:
compile('org.eclipse.paho:org.eclipse.paho.android.service:1.0.2') { exclude(group: 'com.google.android', module: 'support-v4') }
以上是創建一個APP時候,Android Studio默認的Gradle相關的內容,接下來我們來看一下在具體的生產環境中如何對應具體的需求修改build.gradle文件。
在應用開發中,正式環境和測試環境的ip地址肯定是不一樣的,因此測試版本和正式版本在打包的時候需要切換對應的環境。
我們以前采用很笨的做法:設置一個靜態變量,表示服務器的ip地址,然后有一個被注釋掉的同名變量,表示測試地址,打包時候分別注釋對應的地址。
很顯然,這樣的做法相當愚蠢。
所以,后來我們采取了另一種做法:設置一個debug開關,控制服務器地址的切換。
現在好多了,但還是需要去修改代碼設置debug的值。
當然,我們可以在測試的時候,指定build的類型是debug,Android Studio的debug包和release包是有區分的,但很多第三方都要有簽名才能測試,而debug包的默認簽名是無法測試的,所以問題的症結就在於:如何打包都有正式簽名的測試包和發布包,而且不需要修改代碼。
Gradle可以通過buildTypes做到這一點:
buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "int", "CONFIG_MODE", "1" buildConfigField "int", "LOG_SWITCH", "1" } release { signingConfig signingConfigs.myConfig minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' buildConfigField "int", "CONFIG_MODE", "2" buildConfigField "int", "LOG_SWITCH", "0" } }
我們可以添加buildConfigField,這里添加了一個CONFIG_MODE表示配置的模式,0表示relese,1表示debug,然后通過BuildConfig.CONFIG_MODE獲取這個值,在代碼中進行判斷,這樣只要在這里進行設置就可以:
BuildConfig是自動生成的文件,它里面存放build.gradle配置的值:
public final class BuildConfig { public static final boolean DEBUG = Boolean.parseBoolean("true"); public static final String APPLICATION_ID = "com.wenjiang.http"; public static final String BUILD_TYPE = "debug"; public static final String FLAVOR = ""; public static final int VERSION_CODE = 1; public static final String VERSION_NAME = "1.0"; // Fields from build type: debug public static final int CONFIG_MODE = 1; public static final int LOG_SWITCH = 1; }
看到這里,我們想到了可以更好的精簡代碼。
我們可以重新添加一個CONFIG_IP,這樣代碼中就不需要設置ip常量,也不用寫判斷語句,不過要注意String類型要添加一對雙引號,這樣生成的時候才會是正確的字符類型。
buildTypes還有其他的屬性可以配置,例如shrinkResources可以用於指定刪除無效的Resource。
但是這個屬性有個問題:我們應用中有些資源是主題指定資源,是在確認加載主題的時候才會去添加,類似這樣的使用方式:
getResources().getIdentifier(key, “drawable”,getPackageName()));
所以這些資源在編譯打包的時候,因為沒有引用,會被認為是無用資源而被移除,這樣肯定是有問題的。
但是目前我們無法還原真實的場景,所以暫時跳過這部分。
buildTypes還可以指定生成的apk的名字后綴,這樣我們就能在同一部測試機上同時安裝debug和release版本,這個可以通過applicationIdSuffix ".debug"進行指定。
applicationId用於標識我們的應用,默認情況下是應用的包名,不分debug和release,所以在兩個環境切換的時候,都需要重新安裝,如果不想重新安裝,就要為其中一個版本指定后綴。
同樣還有一個versionNameSuffix可以在版本名字后面添加后綴。
應用在正式發布后,可以通過debuggable,jniDebuggable和renderscriptDebuggable設置是否可調試。
這里有一個zipAlignEnabled屬性要值得注意。
這個屬性設置是否對APK包執行ZIP對齊優化,而這個跟應用程序運行時優化有關。
ZipAlign在Android 1.6的時候引入,能夠對打包的應用程序進行優化,使Android操作系統與應用程序間的交互作用更有效率,因此運行得更快。
它到底是怎么做到的呢?
ZipAlign對apk文件中未壓縮的數據在4個字節邊界上對齊,這樣Android系統就可以通過調用mmap函數讀取文件,這樣進程就可以通過映射同一個普通文件實現內存共享,因此可以像訪問普通內存一樣對文件進行訪問,不必調用read()和write()等操作。
而4個字節邊界上對齊,編譯器就能按照4個字節為單位進行讀取,CPU就能對變量進行高效快速的訪問。
Android系統中的Davlik虛擬機使用的是自己專有的DEX格式,DEX的結構是緊湊的,而對齊可以進一步優化。
因此,ZipAlign能夠加快系統從APK文件中讀取資源,並且減少這個過程中耗費的內存。
buildTypes可以設置renderScript的相關屬性,而renderScript一般的應用都不會涉及到,常見的例子就是使用到高斯模糊的時候,為了優化高斯模糊效果和性能,所以這部分就跳過。
buildTypes還可以通過proguardFiles來指定多個混淆文件,但這個需求也是比較少用到。
signingConfig用於指定簽名的配置文件,我們可以在build.gradle文件中設置對應的配置:
signingConfigs {
myConfig {
storeFile file("android.keystore") //簽名文件
storePassword "****"
keyAlias "android"
keyPassword "****" //簽名密碼
}
然后通過signingConfig來指定。
如果新的buildTypes大部分和另一個buildTypes一樣,我們可以通過jnidebug.initWith(buildTypes.debug)來復制debug的內容,然后在jnidebug中設置自己的屬性。
有關buildTypes的大概內容就是這樣,常用的屬性和相關的配置,意義也大概介紹了,一般的應用也大概只需要了解到這種程度,至少我們的應用就是這樣。