Gradle
Gradle是什么
- gradle是一個工具 --> 會寫,會配置腳本
- gradle是一個編程框架 --> 更多內(nèi)容
Gradle的基本組件
gradle中,每一個待編譯的工程都是一個project,每一個project構(gòu)建時都包括一系列Task,比如一個android apk的編譯包括java源碼編譯Task,資源編譯Task,Jni編譯Task,Lint檢查Task,打包Apk的Task,簽名Task等,這些Task的定義和執(zhí)行是有插件決定的。Gradle是一個框架,負(fù)責(zé)定義流程和規(guī)則,具體的編譯工作是由Gradle插件完成的,比如編譯Java有Java插件,編譯Groovy有Groovy插件,編譯Android APP有Android APP插件,編譯Android Library有Android Library插件。
舉個栗子
這個gradle目錄中包含5個項目,其中3個Lib項目和2個Apk項目,其中每一個項目的根目錄下都有一個Build.gradle,build.gradle是該項目的編譯腳本。要同時編譯這些gradle的話,需要Multi-Projects Build。
這要需要以下步驟:
- 在root目錄下新建build.gradle。它用來配置其他子project,比如為子project添加一些屬性。這個build.gradle可以沒有。
- 在root目錄下新家settings.gradle。它來定義這個multiprojects包含哪些子project。這個settings.gradle是必須的,很重要!settings.gradle除了include外,還可以設(shè)置一些方法,用于這些函數(shù)會在gradle構(gòu)建整個工程時執(zhí)行。
gradle命令介紹
gradle的工作流程
- 初始化階段。 對于剛才的例子來說就是執(zhí)行settings.gradle
- 配置階段。 解析根目錄和每個項目的build.gradle。確定內(nèi)部的Task關(guān)系和流程。
- 執(zhí)行階段。
最后,關(guān)于gradle的工作流程,只需要記?。?/p>
- Gradle有一個初始化流程,這個時候settings.gradle會執(zhí)行。
- 在配置階段,每個Project都會被解析,其內(nèi)部的任務(wù)也會被添加到一個有向圖里,用于解決執(zhí)行過程中的依賴關(guān)系。
- 然后才是執(zhí)行階段。你在gradle xxx中指定什么任務(wù),gradle就會將這個xxx任務(wù)鏈上的所有任務(wù)全部按依賴順序執(zhí)行一遍!
Gradle的編程模型和API實例
先看官方文檔
Gradle對象
Project對象
在project中,我們要:
- 加載插件
- 如果插件不同,要對插件進(jìn)行不同的配置
- 設(shè)置屬性
1. 加載插件
apply plugin:xxx
2. 設(shè)置屬性
3. Task介紹
4. 實例
- settings.gradle是必不可少的
- 根目錄下的build.gradle。這個我們沒講過,因為有的根目錄本身不包含代碼,而是包含其他5個子project。
- 每個project目錄下包含對于的build.gradle
- 另外,我把常用的函數(shù)封裝到一個名為utils.gradle的腳本里了。
4.1 utils.gradle
utils.gradle是自定義的,主要是添加一些常用的函數(shù)
[utils.gradle]
import groovy.util.XmlSlurper //解析XML時候要引入這個groovy的package
def copyFile(String srcFile,dstFile){
......//拷貝文件函數(shù),用于將最后的生成物拷貝到指定的目錄
}
def rmFile(String targetFile){
.....//刪除指定目錄中的文件
}
def cleanOutput(boolean bJar = true){
....//clean的時候清理
}
def copyOutput(boolean bJar = true){
....//copyOutput內(nèi)部會調(diào)用copyFile完成一次build的產(chǎn)出物拷貝
}
def getVersionNameAdvanced(){//老朋友
defxmlFile = project.file("AndroidManifest.xml")
defrootManifest = new XmlSlurper().parse(xmlFile)
returnrootManifest['@android:versionName']
}
//對于android library編譯,我會disable所有的debug編譯任務(wù)
def disableDebugBuild(){
//project.tasks包含了所有的tasks,下面的findAll是尋找那些名字中帶debug的Task。
//返回值保存到targetTasks容器中
def targetTasks = project.tasks.findAll{task ->
task.name.contains("Debug")
}
//對滿足條件的task,設(shè)置它為disable。如此這般,這個Task就不會被執(zhí)行
targetTasks.each{
println"disable debug task :${it.name}"
it.setEnabled false
}
}
//將函數(shù)設(shè)置為extra屬性中去,這樣,加載utils.gradle的Project就能調(diào)用此文件中定義的函數(shù)了
ext{
copyFile= this.©File
rmFile =this.&rmFile
cleanOutput = this.&cleanOutput
copyOutput = this.©Output
getVersionNameAdvanced = this.&getVersionNameAdvanced
disableDebugBuild = this.&disableDebugBuild
}
4.2 settings.gradle
內(nèi)容為include的項目和一些初始化操作
[settings.gradle]
/*我們團(tuán)隊內(nèi)部建立的編譯環(huán)境初始化函數(shù)
這個函數(shù)的目的是
1 解析一個名為local.properties的文件,讀取AndroidSDK和NDK的路徑
2 獲取最終產(chǎn)出物目錄的路徑。這樣,編譯完的apk或者jar包將拷貝到這個最終產(chǎn)出物目錄中
3 獲取Android SDK指定編譯的版本
*/
def initMinshengGradleEnvironment(){
println"initialize Minsheng Gradle Environment ....."
Properties properties = new Properties()
//local.properites也放在posdevice目錄下
FilepropertyFile = new File(rootDir.getAbsolutePath()+ "/local.properties")
properties.load(propertyFile.newDataInputStream())
/*
根據(jù)Project、Gradle生命周期的介紹,settings對象的創(chuàng)建位于具體Project創(chuàng)建之前
而Gradle底對象已經(jīng)創(chuàng)建好了。所以,我們把local.properties的信息讀出來后,通過
extra屬性的方式設(shè)置到gradle對象中
而具體Project在執(zhí)行的時候,就可以直接從gradle對象中得到這些屬性了!
*/
gradle.ext.api =properties.getProperty('sdk.api')
gradle.ext.sdkDir =properties.getProperty('sdk.dir')
gradle.ext.ndkDir =properties.getProperty('ndk.dir')
gradle.ext.localDir =properties.getProperty('local.dir')
//指定debugkeystore文件的位置,debug版apk簽名的時候會用到
gradle.ext.debugKeystore= properties.getProperty('debug.keystore')
......
println"initialize Minsheng Gradle Environment completes..."
}
//初始化
initMinshengGradleEnvironment()
//添加子Project信息
include 'CPosSystemSdk' , 'CPosDeviceSdk' ,'CPosSdkDemo','CPosDeviceServerApk', 'CPosSystemSdkWizarPosImpl'
4.3 build.gradle
全局配置
[build.gradle]
//下面這個subprojects{}就是一個Script Block
subprojects {
println"Configure for $project.name" //遍歷子Project,project變量對應(yīng)每個子Project
buildscript { //這也是一個SB
repositories {//repositories是一個SB
///jcenter是一個函數(shù),表示編譯過程中依賴的庫,所需的插件可以在jcenter倉庫中
//下載。
jcenter()
}
dependencies { //SB
//dependencies表示我們編譯的時候,依賴android開發(fā)的gradle插件。插件對應(yīng)的
//class path是com.android.tools.build。版本是1.2.3
classpath'com.android.tools.build:gradle:1.2.3'
}
//為每個子Project加載utils.gradle 。當(dāng)然,這句話可以放到buildscript花括號之后
applyfrom: rootProject.getRootDir().getAbsolutePath() + "/utils.gradle"
}//buildscript結(jié)束
}
4.4 重要的Script Block
某些Script Block的解釋
- subprojects:它會遍歷posdevice中的每個子Project。在它的Closure中,默認(rèn)參數(shù)是子Project對應(yīng)的Project對象。由于其他SB都在subprojects花括號中,所以相當(dāng)于對每個Project都配置了一些信息。
- buildscript:它的closure是在一個類型為ScriptHandler的對象上執(zhí)行的。主意用來所依賴的classpath等信息。通過查看ScriptHandler API可知,在buildscript SB中,你可以調(diào)用ScriptHandler提供的repositories(Closure )、dependencies(Closure)函數(shù)。這也是為什么repositories和dependencies兩個SB為什么要放在buildscript的花括號中的原因。明白了?這就是所謂的行話,得知道規(guī)矩。不知道規(guī)矩你就亂了。記不住規(guī)矩,又不知道查SDK,那么就徹底抓瞎,只能到網(wǎng)上到處找答案了!
- 關(guān)于repositories和dependencies,大家直接看API吧。后面碰到了具體代碼我們再來介紹
4.5 依賴項目的build.gradle
如果該項目是一個android library。android studio默認(rèn)的build方式編譯得到的是一個.aar文件,如果需求是生成.jar格式的文件,就要按下邊的配置來。
[build.gradle]
//Library工程必須加載此插件。注意,加載了Android插件就不要加載Java插件了。因為Android
//插件本身就是拓展了Java插件
apply plugin: 'com.android.library'
//android的編譯,增加了一種新類型的ScriptBlock-->android
android {
//你看,我在local.properties中設(shè)置的API版本號,就可以一次設(shè)置,多個Project使用了
//借助我特意設(shè)計的gradle.ext.api屬性
compileSdkVersion =gradle.api //這兩個紅色的參數(shù)必須設(shè)置
buildToolsVersion = "22.0.1"
sourceSets{ //配置源碼路徑。這個sourceSets是Java插件引入的
main{ //main:Android也用了
manifest.srcFile 'AndroidManifest.xml' //這是一個函數(shù),設(shè)置manifest.srcFile
aidl.srcDirs=['src'] //設(shè)置aidl文件的目錄
java.srcDirs=['src'] //設(shè)置java文件的目錄
}
}
dependencies { //配置依賴關(guān)系
//compile表示編譯和運行時候需要的jar包,fileTree是一個函數(shù),
//dir:'libs',表示搜索目錄的名稱是libs。include:['*.jar'],表示搜索目錄下滿足*.jar名字的jar
//包都作為依賴jar文件
compile fileTree(dir: 'libs', include: ['*.jar'])
}
} //android SB配置完了
//clean是一個Task的名字,這個Task好像是Java插件(這里是Android插件)引入的。
//dependsOn是一個函數(shù),下面這句話的意思是 clean任務(wù)依賴cposCleanTask任務(wù)。所以
//當(dāng)你gradle clean以執(zhí)行clean Task的時候,cposCleanTask也會執(zhí)行
clean.dependsOn 'cposCleanTask'
//創(chuàng)建一個Task,
task cposCleanTask() <<{
cleanOutput(true) //cleanOutput是utils.gradle中通過extra屬性設(shè)置的Closure
}
//前面說了,我要把jar包拷貝到指定的目錄。對于Android編譯,我一般指定gradle assemble
//它默認(rèn)編譯debug和release兩種輸出。所以,下面這個段代碼表示:
//tasks代表一個Projects中的所有Task,是一個容器。getByName表示找到指定名稱的任務(wù)。
//我這里要找的assemble任務(wù),然后我通過doLast添加了一個Action。這個Action就是copy
//產(chǎn)出物到我設(shè)置的目標(biāo)目錄中去
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
copyOutput(true)
}
}
/*
因為我的項目只提供最終的release編譯出來的Jar包給其他人,所以不需要編譯debug版的東西
當(dāng)Project創(chuàng)建完所有任務(wù)的有向圖后,我通過afterEvaluate函數(shù)設(shè)置一個回調(diào)Closure。在這個回調(diào)
Closure里,我disable了所有Debug的Task
*/
project.afterEvaluate{
disableDebugBuild()
}
android定義的script
其中buildToolsVersion和compileSdkVersion是必須配置的
4.6 APK項目的build.gradle
一個apk的build,包括ndk的編譯,項目簽名,混淆,配置依賴等。
[build.gradle]
apply plugin: 'com.android.application' //APK編譯必須加載這個插件
android {
compileSdkVersion gradle.api
buildToolsVersion "22.0.1"
sourceSets{ //差不多的設(shè)置
main{
manifest.srcFile 'AndroidManifest.xml'
//通過設(shè)置jni目錄為空,我們可不使用apk插件的jni編譯功能。為什么?因為據(jù)說
//APK插件的jni功能好像不是很好使....暈菜
jni.srcDirs = []
jniLibs.srcDir 'libs'
aidl.srcDirs=['src']
java.srcDirs=['src']
res.srcDirs=['res']
}
}//main結(jié)束
signingConfigs { //設(shè)置簽名信息配置
debug { //如果我們在local.properties設(shè)置使用特殊的keystore,則使用它
//下面這些設(shè)置,無非是函數(shù)調(diào)用....請務(wù)必閱讀API文檔
if(project.gradle.debugKeystore != null){
storeFile file("file://${project.gradle.debugKeystore}")
storePassword "android"
keyAlias "androiddebugkey"
keyPassword "android"
}
}
}//signingConfigs結(jié)束
buildTypes {
debug {
signingConfig signingConfigs.debug
jniDebuggable false
}
}//buildTypes結(jié)束
dependencies {
//compile:project函數(shù)可指定依賴multi-project中的某個子project
compile project(':CPosDeviceSdk')
compile fileTree(dir: 'libs', include: ['*.jar'])
} //dependices結(jié)束
repositories{
flatDir {//flatDir:告訴gradle,編譯中依賴的jar包存儲在dirs指定的目錄
name "minsheng-gradle-local-repository"
dirsgradle.LOCAL_JAR_OUT //LOCAL_JAR_OUT是我存放編譯出來的jar包的位置
}
}//repositories結(jié)束
}//android結(jié)束
/*
創(chuàng)建一個Task,類型是Exec,這表明它會執(zhí)行一個命令。我這里讓他執(zhí)行ndk的
ndk-build命令,用于編譯ndk。關(guān)于Exec類型的Task,請自行腦補Gradle的API
*/
//注意此處創(chuàng)建task的方法,是直接{}喔,那么它后面的tasks.withType(JavaCompile)
//設(shè)置的依賴關(guān)系,還有意義嗎?Think!如果你能想明白,gradle掌握也就差不多了
task buildNative(type: Exec, description: 'CompileJNI source via NDK') {
if(project.gradle.ndkDir == null) //看看有沒有指定ndk.dir路徑
println "CANNOT Build NDK"
else{
commandLine "/${project.gradle.ndkDir}/ndk-build",
'-C', file('jni').absolutePath,
'-j', Runtime.runtime.availableProcessors(),
'all', 'NDK_DEBUG=0'
}
}
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn buildNative
}
......
//對于APK,除了拷貝APK文件到指定目錄外,我還特意為它們加上了自動版本命名的功能
tasks.getByName("assemble"){
it.doLast{
println "$project.name: After assemble, jar libs are copied tolocal repository"
project.ext.versionName = android.defaultConfig.versionName
println "\t versionName = $versionName"
copyOutput(false)
}
}