參考資料:
http://gold.xitu.io/post/580c85768ac247005b5472f9
http://www.lxweimin.com/p/9df3c3b6067a
http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650661971&idx=1&sn=3fb69537bbc5fbb14d152ba6381c3b83#rd
https://segmentfault.com/a/1190000004229002
http://wiki.jikexueyuan.com/project/deep-android-gradle/
dex 打包
http://blog.csdn.net/mynameishuangshuai/article/details/52703029
For Android 準(zhǔn)備的基礎(chǔ)
構(gòu)建工具
Gradle是一種構(gòu)建工具,Android Studio默認(rèn)采用Gradle來構(gòu)建,Eclipse中使用的ant來構(gòu)建Android;
構(gòu)建工具指的是對項(xiàng)目進(jìn)行編譯、運(yùn)行、簽名、打包、依賴管理等一系列功能的合集,另外一個非常重要的功能是管理依賴(第三方庫管理);
Google 開發(fā)了Andriod Studio 插件 Android Gradle Plugin 用來構(gòu)建 Android App;
新增內(nèi)容(基礎(chǔ)知識)##
Gradle是一個框架,定義了自己一些規(guī)則,我們需要遵循她的設(shè)計規(guī)則:
- 在Gradle中,每一個待編譯的工程都叫做一個Project,如:Android Project目錄下的各種lib引入庫,都是一個Project。
- 在每個Project在構(gòu)建時,又包含了一系列Task,比如:Android APK的編譯包含:java源代碼編譯Task、Android資源編譯Task、簽名Task等等;
- 一個Project有多少個task,由其編譯腳本指定的插件決定,什么是插件?插件就是用來定義Task,并執(zhí)行這些Task的東西;
Gradle負(fù)責(zé)定義流程和規(guī)則,而具體的編譯工作則是通過插件的方式來完成,
如:編譯Java的有java插件,編譯Android lib 的有Android lib 的插件;
總之:Gradle中每一個待編譯的工程,都是一個Project,而Project的編譯的工作,是由其定義的一個一個Task來定義與執(zhí)行的;
在Android工程中,每一個Library、每一個App都是單獨(dú)的Project,同時在每一個Project的根目錄下,都有一個build.gradle文件,表示Project的編譯腳本;
settings.gradle文件
在Android工程中,我們一般都有多個Project,如上,每個project都有一個build.gradle文件與此對應(yīng);在工程的根目錄,有一個build.gradle文件,她負(fù)責(zé)配置其他子Project的,與settings.gradle文件,settings.gradle文件指出該工程包含多少個 子 Project;
有了這2個文件,在項(xiàng)目的根目錄進(jìn)行編譯時,可以把項(xiàng)目中的所有project都編譯好;
Gradle 相關(guān)命令
- gradle projects 查看工程信息;
- gradle tasks 查看任務(wù)信息;
- gradle task-name 執(zhí)行任務(wù),如:gralde clean,gradle properties
task 的依賴關(guān)系
task和task之間可能有關(guān)系,如:某task的執(zhí)行,需要其他task先執(zhí)行完成,這就是依賴關(guān)系;如:assemble task就依賴其他task先執(zhí)行,assemble 才能執(zhí)行;
可以指定 assemble 依賴于 自己定義的 task,這樣,自定義的task會優(yōu)先執(zhí)行;
gradle工作流程
- 初始化階段:對于多project build而言,就是執(zhí)行 settings.gradle;
- Configuration階段:解析每個project中的build.gradle文件,在這2個階段之間,可加入一些定制化的hook;
- 預(yù)執(zhí)行階段:現(xiàn)整個 build的 project及內(nèi)部的task關(guān)系已確定;
- 執(zhí)行任務(wù)階段;
gradle編程模型
Gradle執(zhí)行的時候,會把腳本轉(zhuǎn)化成Java對象,Gradle主要3種對象,并與三種不同的腳本文件對應(yīng):
- Gradle對象:執(zhí)行g(shù)radle xxx,gradle會從默認(rèn)配置腳本中構(gòu)造出一個Gradle對象,整個執(zhí)行過程中,只有這么一個對象,類型是Gradle;
- Project對象:由build.gralde轉(zhuǎn);
- Settings對象:settings.gradle轉(zhuǎn);
Project對象
Project包含若干個Tasks,Project對應(yīng)具體工程,需要為Project加載所需要的插件,如:為java工程加入Java插件;
- 加載插件 :調(diào)用apply方法, https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html
apply plugin: 'com.android.library'
apply plugin: 'com.android.application'
Groovy支持函數(shù)調(diào)用時,通過 參數(shù)名1:參數(shù)值1,,參數(shù)名2:參數(shù)值2來傳遞參數(shù);
// 加載自定義的插件(這里為一個工具文件)
apply from: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle" - 設(shè)置屬性
gradle可能包含不止一個build.gradle文件,考慮在多個腳本中設(shè)置屬性:
gradle提供名為 extra property的方法,表示額外屬性,在第一次定義該屬性時需通過ext前綴來標(biāo)示他是一個額外的屬性,后面的存在,就不需要ext前綴了,ext屬性支持Project和Gradle對象,意思是為Project和Gradle對象設(shè)置ext屬性;
ext {
local = 'Hello groovy'
}
task printProperties {
println local // Local extra property
if (project.hasProperty('cmd')) {
println cmd // Command line property
}
}
如果在 utils.gradle 中定義了一些函數(shù),然后想在其他 build.gradle 中調(diào)用這些函數(shù)。那該怎么做呢?
[utils.gradle]
//utils.gradle 中定義了一個獲取 AndroidManifests.xml versionName 的函數(shù)
def getVersionNameAdvanced(){
// 下面這行代碼中的 project 是誰?
def xmlFile = project.file("AndroidManifest.xml")
def rootManifest = new XmlSlurper().parse(xmlFile)
return rootManifest['@android:versionName']
}
//現(xiàn)在,想把這個 API 輸出到各個 Project。由于這個 utils.gradle 會被每一個 Project Apply,所以
//我可以把 getVersionNameAdvanced 定義成一個 closure,然后賦值到一個外部屬性
// 下面的 ext 是誰的 ext?
ext{ //此段花括號中代碼是閉包
//除了 ext.xxx=value 這種定義方法外,還可以使用 ext{}這種書寫方法。
//ext{}不是 ext(Closure)對應(yīng)的函數(shù)調(diào)用。但是 ext{}中的{}確實(shí)是閉包。
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
問題
- project是誰?
當(dāng)一個project apply 一個gradle文件時,這個gradle文件會轉(zhuǎn)化成一個script對象;
script中有一個delegate對象,這個delegate,默認(rèn)加載(即調(diào)用apply)它的project對象;
在 apply函數(shù)中,除了 from參數(shù),還有個to參數(shù),通過to參數(shù),可改變delegate對象為其他;
delegate就是當(dāng)在script中,操作一些不是script自己定義的變量,或者函數(shù)時,gradle會到script的delegate對象中去找,看有沒有定義這些變量or函數(shù);
==》這樣project就是加載utils.gradle的Project; - ext是誰的ext?
==》 project對應(yīng)的ext了;此處為 Project 添加了一些 closure。那么,在 Project 中
就可以調(diào)用 getVersionNameAdvanced 函數(shù)了
在Java和Groovy中:可能會把常用的函數(shù)放到一個輔助類中,通過import他們,并調(diào)用;
但在Gradle中,更正規(guī)的方式在 xxx.gradle中定義插件,然后通過Task的方式來完成工作;
Task介紹
task是Gradle中的一種數(shù)據(jù)類型,表示一些要執(zhí)行的工作,不同的插件可添加不同task,每一個task需要和一個project關(guān)聯(lián);
Task 的 API 文檔位于 https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
[build.gradle]
// Task 是和Project關(guān)聯(lián)的,所以,需要利用Project的task函數(shù)來創(chuàng)建一個Task
task myTask // 新建task名字
task myTask {} // 閉包
task myType << { task action } // << 符號是 doLast縮寫
task myTask(type:SomeType)
task myTask(type:SomeType) { }
上面都用到了Project的一個函數(shù),task,注意:
- 一個Task包含若干action,所以 Task有doFirst和doLast二個函數(shù),用于添加需要最先執(zhí)行的Action和最后需要執(zhí)行的Action,action是一個閉包;
2.Task創(chuàng)建的時候可指定Type,通過 type:名字表達(dá),就是告訴gradle,這個新建的Task對象會從哪個基類Task派生,如:Copy是Gradle中的一個類,當(dāng) task myTask(type:Copy)的時候,創(chuàng)建的Task是一個Copy Task;
3.當(dāng)使用task myTask {XXX}的時候,花括號是一個閉包,這會導(dǎo)致gradle在創(chuàng)建此task之后,返回給用戶之前,會先執(zhí)行了 閉包內(nèi)容;
4.當(dāng)使用task myTask << {XXX}的時候,創(chuàng)建task對象,并把closure作為一個action加到此task的action隊列中,并且告訴他“最后才執(zhí)行這個closure”;
Script Block
gradle文件中包含一些 Script Block,她的作用是讓我們來配置相關(guān)信息的,不同的SB有不同的配置;
如:
buildscript { // 這是一個Script Block
repositories {
jcenter()
}
每個SB后面都需要跟一個花括號,閉包;
https://docs.gradle.org/current/javadoc/ ,可輸入SB名字,進(jìn)行查找;
解釋幾個SB:
- subprojects:它會遍歷 工程 中的 每個子 Project,在其closure中,默認(rèn)參數(shù)是子project對應(yīng)的Project對象,由于其他SB都在subprojects中,所以相當(dāng)于對每個Project都配置了一些信息;
- buildscript::它的 closure 是在一個類型為 ScriptHandler 的對象上執(zhí)行的。主意用來所依賴的 classpa
th 等信息。通過查看 ScriptHandler API 可知,在 buildscript SB 中,你可以調(diào)用 ScriptHandler 提供
的 repositories(Closure )、dependencies(Closure)函數(shù)。這也是為什么 repositories 和 dependencies
兩個 SB 為什么要放在 buildscript 的花括號中的原因。這就是所謂的行話,得知道規(guī)矩。不知道
規(guī)矩你就亂了。記不住規(guī)矩,又不知道查 SDK,那么就徹底抓瞎,只能到網(wǎng)上到處找答案了!
Android 自己定義了好多 ScriptBlock。Android 定義的 DSL 參考文檔在
https://developer.android.com/tools/building/plugin-for-gradle.html
其他一些:##
Gradle Wrapper
參考:
http://mp.weixin.qq.com/s?__biz=MzA4NTQwNDcyMA==&mid=2650661971&idx=1&sn=3fb69537bbc5fbb14d152ba6381c3b83#rd
我們可以在項(xiàng)目的根目錄,輸入 gradlew -v 可查看 gradle 版本;
gradlew 為 gradle wapper 的縮寫
注意:如果沒有配置 全局的 gralde 環(huán)境變量,在Android studio 的 命令框中,需要輸入./ 再加 graldew來使用相關(guān)命令,如下:
常用命令
./gradlew -v 查看版本號 (win 下 輸入 gradlew -v)
./gradlew clean 清除/app目錄下的build文件夾
./gradlew build 檢查依賴并編譯打包
./gradlew build 命令把 debug、release 環(huán)境的包都打出來;
./gradlew assembleDebug 編譯并打Debug包
./gradlew assembleRelease 編譯并打Release的包
基本的Gradle
構(gòu)建Android程序,需要構(gòu)建腳本, gradle默認(rèn)提供了一些配置與默認(rèn)值,簡化了我們的構(gòu)建工作;
Project與Tasks
Gradle中有2個非常重要的對象,Project 和 Tasks;
注意: Android Studio 中的project和Gradle中的project不是同一個概念。
這里的project指的是 gradle中的project;
每個project有至少一個tasks,每個build.gradle文件代表一個project,tasks在build.gradle中定義,一個tasks包含了多個動作,然后按順序一個一個執(zhí)行,類似java中的方法;
構(gòu)建生命周期
一旦一個tasks被執(zhí)行,后面將不再執(zhí)行,不包含依賴的tasks總是優(yōu)先執(zhí)行,一個構(gòu)建會經(jīng)歷以下3個階段:
- 初始化階段:project實(shí)例在這兒創(chuàng)建,如果有多個模塊,即有多個build.gradle文件,多個project將會被創(chuàng)建;
- 配置階段:在該階段,build.gradle腳本將會執(zhí)行,為每個project創(chuàng)建和配置所有的tasks;
- 執(zhí)行階段:這一階段,gradle會決定哪一個tasks會被執(zhí)行,哪一個tasks會被執(zhí)行完全依賴開始構(gòu)建時傳入的參數(shù)和當(dāng)前所在的文件夾位置有關(guān)
build.gradle的配置文件
基于gradle構(gòu)建的項(xiàng)目,至少有一個 build.gradle文件,下面的是Android的build.gradle:
這個 就是 實(shí)際構(gòu)建開始的地方
// 定義全局的相關(guān)屬性,使用 jcenter作為倉庫
buildscript {
repositories {
jcenter()
}
// 定義構(gòu)建過程
dependencies {
classpath 'com.android.tools.build:gradle:2.1.3'
}
}
// 用來定義各個模塊的默認(rèn)屬性,在所有模塊中的可見
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
構(gòu)建腳本定義了Android構(gòu)建工具,還有Android的依賴庫
// 每個app都需要這個插件,Android plugin 提供了所有需要去構(gòu)建和測試的應(yīng)用
apply plugin: 'com.android.application'
// 表示的是一個 依賴庫
apply plugin: 'com.android.library'
基本的Tasks
android插件依賴于Java插件,java依賴于base插件,
base插件有基本的tasks生命周期和一些通用的屬性;
base插件定義了例如assemble和clean任務(wù),Java插件定義了check和build任務(wù),這兩個任務(wù)不在base插件中定義。
這些tasks的約定含義:
assemble: 集合所有的output
clean: 清除所有的output
check: 執(zhí)行所有的checks檢查,通常是unit測試和instrumentation測試
build: 執(zhí)行所有的assemble和check
Java插件同時也添加了source sets的概念。
Android Tasks
Android插件繼承了基本tasks,并實(shí)現(xiàn)了自己的行為:
- assemble 針對每個版本創(chuàng)建一個apk
- clean 刪除所有的構(gòu)建任務(wù),包含apk文件
- check 執(zhí)行Lint檢查并且能夠在Lint檢測到錯誤后停止執(zhí)行腳本
- build 執(zhí)行assemble和check
默認(rèn)情況下assemble tasks定義了assembleDebug和assembleRelease,當(dāng)然你還可以定義更多構(gòu)建版本。除了這些tasks,android 插件也提供了一些新的tasks:
- connectedCheck 在測試機(jī)上執(zhí)行所有測試任務(wù)
- deviceCheck 執(zhí)行所有的測試在遠(yuǎn)程設(shè)備上
- installDebug和installRelease 在設(shè)備上安裝一個特殊的版本
- 所有的install task對應(yīng)有uninstall 任務(wù)
Android Studio中的tasks
![A$PZHC}F`6YTR30Q~JH]{NM.png](http://upload-images.jianshu.io/upload_images/2003670-088f7832be095f82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
依賴管理
gradle自動為android程序添加了倉庫,默認(rèn)是jcenter,我們添加的某個第三方j(luò)ar,稱為一個依賴,比如:support v7包,gson 等;
一個依賴需要定義3個元素:group,name和version,添加依賴使用的是 groovy 語法,如下:
// 依賴
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
依賴的配置
有些時候,你可能需要和sdk協(xié)調(diào)工作。為了能順利編譯你的代碼,你需要添加SDK到你的編譯環(huán)境。你不需要將sdk包含在你的APK中,因?yàn)樗缫呀?jīng)存在于設(shè)備中,所以配置來啦,我們會有5個不同的配置:
- compile
- apk
- provided
- testCompile
- androidTestCompile
compile 默認(rèn),其含義是包含所有的依賴包,即在APK里,compile的依賴會存在。
apk 的意思是apk中存在,但是不會加入編譯中,這個貌似用的比較少。
provided 的意思是提供編譯支持,但是不會寫入apk。
testCompile 和androidTestCompile 會添加額外的library支持針對測試。
這些配置將會被用在測試相關(guān)的tasks中,這會對添加測試框架例如JUnit或者Espresso非常有用,因?yàn)槟阒皇窍胱屵@些框架們能夠出現(xiàn)在測試apk中,而不是生產(chǎn)apk中。
構(gòu)建版本
一個app如果有多個版本,比如 release,debug,不同渠道不同版本等,使用 gralde 可以方便管理這些;
- Build types;
- Product flavors;
- Build variants
- Signing configurations;
buildTypes
gradle 的android插件中,一個版本構(gòu)建意味著一個app或者依賴庫如何被構(gòu)建,每個構(gòu)建版本可能有一些特殊面,比如 是否 debug,application id,應(yīng)用名稱,哪些資源是否需要刪掉等,可以定義一個構(gòu)建版本 buildTypes 方法,如:
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
product flavors
product flavors 用來為一個app創(chuàng)建不同的版本,比如:app的付費(fèi)與免費(fèi); 如果 app 需要對內(nèi)對外 完全隔離,就可以使用 product flavors,
// 多渠道打包
productFlavors {
// 個性化定制
xiaomi {
applicationId "groovy.better.com.groovytest.xiaomi"
minSdkVersion 11
}
huawei {
applicationId "groovy.better.com.groovytest.huawei"
minSdkVersion 14
}
baidu {
applicationId "groovy.better.com.groovytest.baidu"
minSdkVersion 16
}
}
// apk名稱修改
applicationVariants.all { variant ->
if (variant.buildType.name.equals('release')) {
variant.outputs.each { output ->
def appName = 'demo'
def oldFile = output.outputFile
def buildName
def releaseApkName
variant.productFlavors.each { product ->
buildName = product.name
}
releaseApkName = appName + getVersionByMainfest() + '_' + buildName + '_' + getNowTime() + '.apk'
output.outputFile = new File(oldFile.parent, releaseApkName)
}
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
}
// -----> 自定義的方法
//獲取時間戳
def getNowTime() {
def date = new Date()
def now = date.format('yyyyMMddHHmm')
return now
}
//從androidManifest.xml中獲取版本號
def getVersionByMainfest() {
def parser = new com.android.builder.core.DefaultManifestParser()
return parser.getVersionName(android.sourceSets.main.manifest.srcFile)
}
BuildConfig配置
BuildConfig.java文件,無法進(jìn)行動態(tài)配置,她是通過 module 相應(yīng)的gradle文件生成的,可通過 module的 gralde文件,進(jìn)行一些全局的開關(guān)控制:
添加配置代碼:
buildTypes {
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField 'String', 'TEST_NAME', '"test_debug"' // ildConfigField
resValue "string", "test_name", "test_debug" //resValue
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
buildConfigField 'String', 'TEST_NAME', '"test_release"' // ildConfigField
resValue "string", "test_name", "test_release" //resValue
}
}
生成的 BuildConfig.java 文件:
- buildConfigField: 會根據(jù)gradle的配置,在原來默認(rèn)的BuildConfig.java基礎(chǔ)上,動態(tài)添加一個指定數(shù)據(jù)類型的value。
buildConfigField 一共有3個參數(shù),具體參考上面的參考資料; - resValue: buildConfigField主要改變了java常量,Gradle組件提供了resValue字段,用于動態(tài)生成value資源,在程序中也可以訪問到,其中生成的目標(biāo)存在generated.xml中,使用的規(guī)則與buildConfigField 類似,即類型+常量名+常量值,如上代碼 resValue 部分,生成generated.xml截圖:
** 使用占位符動態(tài)配置 清單文件中 :meta-data **
請注意:占位符須與 gradle中的 名稱一致;
清單文件代碼:
配置不同渠道上的值:
構(gòu)建的生命周期
初始化階段:gralde尋找 settings.gradle文件,如果項(xiàng)目有多個模塊,settings.gralde文件定義了模塊的位置,如果這些子目錄包含其自己的 build.gradle文件,gradle將運(yùn)行其,并將它們合并到構(gòu)建任務(wù)中;