參考資料:
http://gold.xitu.io/post/580c85768ac247005b5472f9
http://www.reibang.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 準備的基礎
構建工具
Gradle是一種構建工具肄梨,Android Studio默認采用Gradle來構建灸拍,Eclipse中使用的ant來構建Android;
構建工具指的是對項目進行編譯、運行鼻吮、簽名像街、打包、依賴管理等一系列功能的合集抖僵,另外一個非常重要的功能是管理依賴(第三方庫管理)鲤看;
Google 開發(fā)了Andriod Studio 插件 Android Gradle Plugin 用來構建 Android App;
新增內(nèi)容(基礎知識)##
Gradle是一個框架耍群,定義了自己一些規(guī)則义桂,我們需要遵循她的設計規(guī)則:
- 在Gradle中,每一個待編譯的工程都叫做一個Project世吨,如:Android Project目錄下的各種lib引入庫澡刹,都是一個Project。
- 在每個Project在構建時耘婚,又包含了一系列Task罢浇,比如:Android APK的編譯包含:java源代碼編譯Task、Android資源編譯Task沐祷、簽名Task等等嚷闭;
- 一個Project有多少個task,由其編譯腳本指定的插件決定赖临,什么是插件胞锰?插件就是用來定義Task,并執(zhí)行這些Task的東西兢榨;
Gradle負責定義流程和規(guī)則嗅榕,而具體的編譯工作則是通過插件的方式來完成,
如:編譯Java的有java插件吵聪,編譯Android lib 的有Android lib 的插件凌那;
總之:Gradle中每一個待編譯的工程,都是一個Project吟逝,而Project的編譯的工作帽蝶,是由其定義的一個一個Task來定義與執(zhí)行的;
在Android工程中块攒,每一個Library励稳、每一個App都是單獨的Project佃乘,同時在每一個Project的根目錄下,都有一個build.gradle文件驹尼,表示Project的編譯腳本趣避;
settings.gradle文件
在Android工程中,我們一般都有多個Project扶欣,如上鹅巍,每個project都有一個build.gradle文件與此對應;在工程的根目錄料祠,有一個build.gradle文件骆捧,她負責配置其他子Project的,與settings.gradle文件髓绽,settings.gradle文件指出該工程包含多少個 子 Project敛苇;
有了這2個文件,在項目的根目錄進行編譯時顺呕,可以把項目中的所有project都編譯好枫攀;
Gradle 相關命令
- gradle projects 查看工程信息;
- gradle tasks 查看任務信息株茶;
- gradle task-name 執(zhí)行任務来涨,如:gralde clean,gradle properties
task 的依賴關系
task和task之間可能有關系启盛,如:某task的執(zhí)行蹦掐,需要其他task先執(zhí)行完成,這就是依賴關系僵闯;如:assemble task就依賴其他task先執(zhí)行卧抗,assemble 才能執(zhí)行;
可以指定 assemble 依賴于 自己定義的 task鳖粟,這樣社裆,自定義的task會優(yōu)先執(zhí)行;
gradle工作流程
- 初始化階段:對于多project build而言向图,就是執(zhí)行 settings.gradle;
- Configuration階段:解析每個project中的build.gradle文件泳秀,在這2個階段之間,可加入一些定制化的hook榄攀;
- 預執(zhí)行階段:現(xiàn)整個 build的 project及內(nèi)部的task關系已確定晶默;
- 執(zhí)行任務階段;
gradle編程模型
Gradle執(zhí)行的時候航攒,會把腳本轉化成Java對象,Gradle主要3種對象趴梢,并與三種不同的腳本文件對應:
- Gradle對象:執(zhí)行gradle xxx漠畜,gradle會從默認配置腳本中構造出一個Gradle對象币他,整個執(zhí)行過程中,只有這么一個對象憔狞,類型是Gradle蝴悉;
- Project對象:由build.gralde轉;
- Settings對象:settings.gradle轉瘾敢;
Project對象
Project包含若干個Tasks拍冠,Project對應具體工程,需要為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" - 設置屬性
gradle可能包含不止一個build.gradle文件典蜕,考慮在多個腳本中設置屬性:
gradle提供名為 extra property的方法断盛,表示額外屬性,在第一次定義該屬性時需通過ext前綴來標示他是一個額外的屬性愉舔,后面的存在钢猛,就不需要ext前綴了,ext屬性支持Project和Gradle對象轩缤,意思是為Project和Gradle對象設置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)對應的函數(shù)調(diào)用剪芥。但是 ext{}中的{}確實是閉包。
getVersionNameAdvanced = this.&getVersionNameAdvanced
}
問題
- project是誰琴许?
當一個project apply 一個gradle文件時税肪,這個gradle文件會轉化成一個script對象;
script中有一個delegate對象,這個delegate益兄,默認加載(即調(diào)用apply)它的project對象锻梳;
在 apply函數(shù)中,除了 from參數(shù)净捅,還有個to參數(shù)疑枯,通過to參數(shù),可改變delegate對象為其他蛔六;
delegate就是當在script中荆永,操作一些不是script自己定義的變量,或者函數(shù)時国章,gradle會到script的delegate對象中去找具钥,看有沒有定義這些變量or函數(shù);
==》這樣project就是加載utils.gradle的Project捉腥; - ext是誰的ext氓拼?
==》 project對應的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關聯(lián);
Task 的 API 文檔位于 https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
[build.gradle]
// Task 是和Project關聯(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:名字表達跛溉,就是告訴gradle焊切,這個新建的Task對象會從哪個基類Task派生扮授,如:Copy是Gradle中的一個類,當 task myTask(type:Copy)的時候蛛蒙,創(chuàng)建的Task是一個Copy Task糙箍;
3.當使用task myTask {XXX}的時候,花括號是一個閉包牵祟,這會導致gradle在創(chuàng)建此task之后,返回給用戶之前抖格,會先執(zhí)行了 閉包內(nèi)容诺苹;
4.當使用task myTask << {XXX}的時候,創(chuàng)建task對象雹拄,并把closure作為一個action加到此task的action隊列中收奔,并且告訴他“最后才執(zhí)行這個closure”;
Script Block
gradle文件中包含一些 Script Block滓玖,她的作用是讓我們來配置相關信息的坪哄,不同的SB有不同的配置;
如:
buildscript { // 這是一個Script Block
repositories {
jcenter()
}
每個SB后面都需要跟一個花括號势篡,閉包翩肌;
https://docs.gradle.org/current/javadoc/ ,可輸入SB名字禁悠,進行查找念祭;
解釋幾個SB:
- subprojects:它會遍歷 工程 中的 每個子 Project,在其closure中碍侦,默認參數(shù)是子project對應的Project對象粱坤,由于其他SB都在subprojects中,所以相當于對每個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
我們可以在項目的根目錄贷岸,輸入 gradlew -v 可查看 gradle 版本壹士;
gradlew 為 gradle wapper 的縮寫
注意:如果沒有配置 全局的 gralde 環(huán)境變量,在Android studio 的 命令框中偿警,需要輸入./ 再加 graldew來使用相關命令躏救,如下:
常用命令
./gradlew -v 查看版本號 (win 下 輸入 gradlew -v)
./gradlew clean 清除/app目錄下的build文件夾
./gradlew build 檢查依賴并編譯打包
./gradlew build 命令把 debug、release 環(huán)境的包都打出來螟蒸;
./gradlew assembleDebug 編譯并打Debug包
./gradlew assembleRelease 編譯并打Release的包
基本的Gradle
構建Android程序盒使,需要構建腳本, gradle默認提供了一些配置與默認值七嫌,簡化了我們的構建工作少办;
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中的方法;
構建生命周期
一旦一個tasks被執(zhí)行褪测,后面將不再執(zhí)行猴誊,不包含依賴的tasks總是優(yōu)先執(zhí)行,一個構建會經(jīng)歷以下3個階段:
- 初始化階段:project實例在這兒創(chuàng)建侮措,如果有多個模塊懈叹,即有多個build.gradle文件,多個project將會被創(chuàng)建分扎;
- 配置階段:在該階段澄成,build.gradle腳本將會執(zhí)行,為每個project創(chuàng)建和配置所有的tasks畏吓;
- 執(zhí)行階段:這一階段墨状,gradle會決定哪一個tasks會被執(zhí)行,哪一個tasks會被執(zhí)行完全依賴開始構建時傳入的參數(shù)和當前所在的文件夾位置有關
build.gradle的配置文件
基于gradle構建的項目菲饼,至少有一個 build.gradle文件肾砂,下面的是Android的build.gradle:
這個 就是 實際構建開始的地方
// 定義全局的相關屬性,使用 jcenter作為倉庫
buildscript {
repositories {
jcenter()
}
// 定義構建過程
dependencies {
classpath 'com.android.tools.build:gradle:2.1.3'
}
}
// 用來定義各個模塊的默認屬性宏悦,在所有模塊中的可見
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
構建腳本定義了Android構建工具镐确,還有Android的依賴庫
// 每個app都需要這個插件包吝,Android plugin 提供了所有需要去構建和測試的應用
apply plugin: 'com.android.application'
// 表示的是一個 依賴庫
apply plugin: 'com.android.library'
基本的Tasks
android插件依賴于Java插件,java依賴于base插件源葫,
base插件有基本的tasks生命周期和一些通用的屬性诗越;
base插件定義了例如assemble和clean任務,Java插件定義了check和build任務息堂,這兩個任務不在base插件中定義嚷狞。
這些tasks的約定含義:
assemble: 集合所有的output
clean: 清除所有的output
check: 執(zhí)行所有的checks檢查,通常是unit測試和instrumentation測試
build: 執(zhí)行所有的assemble和check
Java插件同時也添加了source sets的概念储矩。
Android Tasks
Android插件繼承了基本tasks感耙,并實現(xiàn)了自己的行為:
- assemble 針對每個版本創(chuàng)建一個apk
- clean 刪除所有的構建任務,包含apk文件
- check 執(zhí)行Lint檢查并且能夠在Lint檢測到錯誤后停止執(zhí)行腳本
- build 執(zhí)行assemble和check
默認情況下assemble tasks定義了assembleDebug和assembleRelease持隧,當然你還可以定義更多構建版本。除了這些tasks,android 插件也提供了一些新的tasks:
- connectedCheck 在測試機上執(zhí)行所有測試任務
- deviceCheck 執(zhí)行所有的測試在遠程設備上
- installDebug和installRelease 在設備上安裝一個特殊的版本
- 所有的install task對應有uninstall 任務
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程序添加了倉庫逃片,默認是jcenter屡拨,我們添加的某個第三方jar,稱為一個依賴褥实,比如: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中窟勃,因為它早已經(jīng)存在于設備中祖乳,所以配置來啦,我們會有5個不同的配置:
- compile
- apk
- provided
- testCompile
- androidTestCompile
compile 默認秉氧,其含義是包含所有的依賴包眷昆,即在APK里,compile的依賴會存在汁咏。
apk 的意思是apk中存在亚斋,但是不會加入編譯中,這個貌似用的比較少攘滩。
provided 的意思是提供編譯支持帅刊,但是不會寫入apk。
testCompile 和androidTestCompile 會添加額外的library支持針對測試轰驳。
這些配置將會被用在測試相關的tasks中厚掷,這會對添加測試框架例如JUnit或者Espresso非常有用弟灼,因為你只是想讓這些框架們能夠出現(xiàn)在測試apk中,而不是生產(chǎn)apk中冒黑。
構建版本
一個app如果有多個版本田绑,比如 release,debug抡爹,不同渠道不同版本等掩驱,使用 gralde 可以方便管理這些;
- Build types冬竟;
- Product flavors欧穴;
- Build variants
- Signing configurations;
buildTypes
gradle 的android插件中,一個版本構建意味著一個app或者依賴庫如何被構建泵殴,每個構建版本可能有一些特殊面涮帘,比如 是否 debug,application id笑诅,應用名稱调缨,哪些資源是否需要刪掉等,可以定義一個構建版本 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的付費與免費; 如果 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文件,無法進行動態(tài)配置者祖,她是通過 module 相應的gradle文件生成的立莉,可通過 module的 gralde文件,進行一些全局的開關控制:
添加配置代碼:
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的配置咸包,在原來默認的BuildConfig.java基礎上桃序,動態(tài)添加一個指定數(shù)據(jù)類型的value。
buildConfigField 一共有3個參數(shù)烂瘫,具體參考上面的參考資料媒熊; - resValue: buildConfigField主要改變了java常量,Gradle組件提供了resValue字段坟比,用于動態(tài)生成value資源芦鳍,在程序中也可以訪問到,其中生成的目標存在generated.xml中葛账,使用的規(guī)則與buildConfigField 類似柠衅,即類型+常量名+常量值,如上代碼 resValue 部分籍琳,生成generated.xml截圖:
** 使用占位符動態(tài)配置 清單文件中 :meta-data **
請注意:占位符須與 gradle中的 名稱一致;
清單文件代碼:
配置不同渠道上的值:
構建的生命周期
初始化階段:gralde尋找 settings.gradle文件,如果項目有多個模塊狈醉,settings.gralde文件定義了模塊的位置,如果這些子目錄包含其自己的 build.gradle文件势誊,gradle將運行其,并將它們合并到構建任務中谣蠢;