關(guān)于gradle你應(yīng)該知道的一些小事
前言
gradle的定義(來(lái)自維基百科)
Gradle是一個(gè)基于Apache Ant和Apache Maven概念的項(xiàng)目自動(dòng)化建構(gòu)工具。它使用一種基于Groovy的特定領(lǐng)域語(yǔ)言來(lái)聲明項(xiàng)目設(shè)置,而不是傳統(tǒng)的XML。當(dāng)前其支持的語(yǔ)言限于Java、Groovy和Scala,計(jì)劃未來(lái)將支持更多的語(yǔ)言。
通俗的理解:gradle是一種構(gòu)建工具,我們可以用他來(lái)對(duì)多工程進(jìn)行各種管理(依賴,打包,部署,發(fā)布,各種渠道的差異管理);
有些時(shí)候,我們會(huì)有一些個(gè)性化的構(gòu)建需求,比如我們引入了第三方庫(kù),或者我們想要在通用構(gòu)建過(guò)程中做一些其他的事情,這時(shí)我們就要自己在系統(tǒng)默認(rèn)構(gòu)建規(guī)則上做一些修改。這時(shí)候我們就要自己向Gradle”下命令“了,這時(shí)候我們就需要用Gradle能聽(tīng)懂的話了,也就是Groovy。
我們?cè)陂_(kāi)頭處提到“Gradle是一種構(gòu)建工具”。實(shí)際上,當(dāng)我們想要更靈活的構(gòu)建過(guò)程時(shí),Gradle就成為了一個(gè)編程框架——我們可以通過(guò)編程讓構(gòu)建過(guò)程按我們的意愿進(jìn)行。也就是說(shuō),當(dāng)我們把Gradle作為構(gòu)建工具使用時(shí),我們只需要掌握它的配置腳本的基本寫(xiě)法就OK了;而當(dāng)我們需要對(duì)構(gòu)建流程進(jìn)行高度定制時(shí),就務(wù)必要掌握Groovy等相關(guān)知識(shí)了。
遭遇的問(wèn)題
我們?cè)趯?shí)時(shí)多項(xiàng)目構(gòu)建的時(shí)候經(jīng)常遇到以下這些問(wèn)題:
1、同時(shí)依賴了不同版本的某個(gè)庫(kù),編譯時(shí)出現(xiàn)duplicate class錯(cuò)誤;
2、gradle 不同版本api報(bào)錯(cuò);
3、不會(huì)寫(xiě)gradle配置,看不懂gradle語(yǔ)法,不知道從何學(xué)起;
4、對(duì)編譯過(guò)程中g(shù)radle的報(bào)錯(cuò)無(wú)從下手;
等等…
我們接下來(lái)將從實(shí)際項(xiàng)目出發(fā)一步一步來(lái)學(xué)習(xí)gradle的這些事,本文主旨在于學(xué)習(xí)gradle的思路,深度細(xì)節(jié)將會(huì)忽略;
揭開(kāi)Gradle的面紗
一、理解打包命令 gradle clean assembleDebug/assembleRelease
以上這條命令可以分解為三個(gè)部分,gradle,clean, assembleDebug;實(shí)際上就和我們執(zhí)行腳本一樣,gradle是執(zhí)行器,而clean 和 assembleDebug是入?yún)ⅲ?在這里它們兩個(gè)代表不同的task,就類似gradle task1 task2 這樣。
二、什么是task?
在build.gradle寫(xiě)上
task task1 {
println "===>task 1"
}
task task2 {
println "===>task 2"
}
這樣就定義了兩個(gè)task;當(dāng)我們執(zhí)行g(shù)radle task1 task2 -q的時(shí)候(-q是設(shè)置日志級(jí)別),理論上會(huì)看到日志輸出:
===>task 1
===>task 2
task的關(guān)系有dependsOn,mustRunAfter等等,由于項(xiàng)目中用的比較少這里先跳過(guò)這部分;
這里我們簡(jiǎn)單講一下閉包的概念:
閉包在groovy中是一個(gè)處于代碼上下文中的開(kāi)放的,匿名代碼塊。它可以訪問(wèn)到其外部的變量或方法,
更詳細(xì)的請(qǐng)自行g(shù)oogle
然而,當(dāng)我們?cè)陧?xiàng)目里執(zhí)行g(shù)radle task1 task2 -q的時(shí)候,我們發(fā)現(xiàn)輸出是這樣的:
SeeyouClient git:(SeeyouClient-dev) ✗ gradle task1 task2 -q
doPackage value:False
Configuration 'compile' in project ':app' is deprecated. Use 'implementation' instead.
==============anna apply start==================
configuration do:
include
** onClick
** onItemClick
** onCheckedChanged
** onItemSelected
** onSwitchButtonCheck
** onItemLongClick
** onLongClick
** onPullRefresh
** OnRefresh
configuration do:
exclude
org/conscrypt/
configuration do:
exceptions
java/lang/Exception
java/lang/NullPointerException
configuration do:
switch
custom
need inject=false
==============anna apply end==================
Configuration 'provided' in project ':app' is deprecated. Use 'compileOnly' instead.
Configuration 'debugCompile' in project ':app' is deprecated. Use 'debugImplementation' instead.
===>task 1
===>task 2
DexKnife: Processing Variant
DexKnife: processSplitDex true
DexKnife: processing Task
----------------------tinker build warning ------------------------------------
tinker auto operation:
excluding annotation processor and source template from app packaging. Enable dx jumboMode to reduce package size.
enable dx jumboMode to reduce package size.
disable preDexLibraries to prevent ClassDefNotFoundException when your app is booting.
disable archive dex mode so far for keeping dex apply.
tinker will change your build configs:
we will add TINKER_ID=117 in your build output manifest file build/intermediates/manifests/full/*
if minifyEnabled is true
you will find the gen proguard rule file at build/intermediates/tinker_intermediates/tinker_proguard.pro
and we will help you to put it in the proguardFiles.
if multiDexEnabled is true
you will find the gen multiDexKeepProguard file at build/intermediates/tinker_intermediates/tinker_multidexkeep.pro
and we will help you to put it in the MultiDexKeepProguardFile.
if applyResourceMapping file is exist
we will build app apk with resource R.txt file
if resources.arsc has changed, you should use applyResource mode to build the new apk!
-----------------------------------------------------------------
Task spend time:
這是為什么呢?原因是gradle具有自己的生命周期:
初始化階段:負(fù)責(zé)判斷有多少個(gè)Projects參與構(gòu)建:
先執(zhí)行settings.gradle
配置階段:負(fù)責(zé)對(duì)初始化階段創(chuàng)建的Projects完成配置:
比如添加Task,修改Task的行為,閉包的內(nèi)容會(huì)被執(zhí)行,執(zhí)行build.gradle的內(nèi)容;
執(zhí)行階段:根據(jù)配置階段的配置執(zhí)行任務(wù):
執(zhí)行task對(duì)應(yīng)的內(nèi)容,如doLast,doFirst之類的
因此gradle task1 task2 -q的輸出日志就可以理解了,其實(shí)按照task1和task2的寫(xiě)法,執(zhí)行g(shù)radle task1 task2 -q和gradle -q實(shí)際上效果是一樣的。
三、gradle clean assmebleDebug到底做了什么?(源碼追蹤和依賴分析出編譯流程)
1、打開(kāi)gradle-4.5.1/bin/gradle文件可以看到執(zhí)行了代碼:
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.launcher.GradleMain "$APP_ARGS"
最終調(diào)用exec "$JAVACMD" "$@"來(lái)執(zhí)行;所以入口就是:org.gradle.launcher.GradleMain
具體細(xì)節(jié)可以參照:https://blog.csdn.net/yanbober/article/details/60584621
2,最終會(huì)調(diào)用DefaultGradleLauncher里,我們可以很明確的看到它的生命周期:
這邊最需要注意的時(shí)候,當(dāng)我們只執(zhí)行g(shù)radle -q這樣的時(shí)候,實(shí)際上每一次都會(huì)執(zhí)行到TaskGraph的階段;也就是所有的tasks都已經(jīng)梳理完成;
public class DefaultGradleLauncher implements GradleLauncher {
//(這里是4.5.1的版本,生命周期更細(xì)致化)
private enum Stage {
Load, LoadBuild, Configure, TaskGraph, Build, Finished
}
//2.14.1的版本則是:
private enum Stage {
Load, Configure, Build
}
//核心方法
private void doBuildStages(Stage upTo) {
try {
loadSettings();
if (upTo == Stage.Load) {
return;
}
configureBuild();
if (upTo == Stage.Configure) {
return;
}
constructTaskGraph();
if (upTo == Stage.TaskGraph) {
return;
}
runTasks();
finishBuild();
} catch (Throwable t) {
Throwable failure = exceptionAnalyser.transform(t);
finishBuild(new BuildResult(upTo.name(), gradle, failure));
throw new ReportedException(failure);
}
}
//調(diào)用時(shí)機(jī)
@Override
public SettingsInternal getLoadedSettings() {
doBuildStages(Stage.Load);
return settings;
}
@Override
public GradleInternal getConfiguredBuild() {
doBuildStages(Stage.Configure);
return gradle;
}
public GradleInternal executeTasks() {
doBuildStages(Stage.Build);
return gradle;
}
四、知道編譯流程后有什么用呢?
1、我們經(jīng)常在app/build.gradle看到這樣的代碼:
project.afterEvaluate {...}
android.applicationVariants.all {...}
gradle.addListener(new TaskListener())
apply from '../mvn.gradle'
...
這里我們介紹幾個(gè)概念:
project
對(duì)應(yīng)gradle源碼的Project.java(按住control點(diǎn)擊project會(huì)自動(dòng)跳轉(zhuǎn)),里邊提供一些對(duì)外的方法,如afterEvalute,beforeEvalue; 在理解編譯流程后,才能靈活的使用這些api;
android
對(duì)應(yīng)gradle插件的AppExtension.java文件,提供了一些對(duì)外的參數(shù)和方法,我們可以使用android.xxx來(lái)訪問(wèn)app/build.gradle里的任意參數(shù)和方法;
gradle
對(duì)應(yīng)的gradle源碼里的Gradle.java對(duì)象,也是提供了一系列的方法給外部使用;
那么接下來(lái)假設(shè)我們有這樣一個(gè)需求:找到一個(gè)叫cleanBuildCache的task,找到之后添加一個(gè)action,打印一行字; 要實(shí)現(xiàn)這個(gè)需求,首先我們?nèi)绾伪闅v這個(gè)app的所有task:
有很多種寫(xiě)法:
gradle.getTaskGraph().whenReady {
project.tasks.each {
task->
println "taskName:"+task.getName()
}
}
project.afterEvaluate {
project.tasks.each {
task->
println "taskName:"+task.getName()
}
}
執(zhí)行gradle -q 感受一下。
接下看看如何添加action
project.afterEvaluate {
project.tasks.each {
task->
// println "taskName:"+task.getName()
if(task.getName().equals("cleanBuildCache")){
println "find cleanBuildCache!!!!!!"
List<Action<? super Task>> list = new ArrayList<>()
list.add(new Action<Task>() {
@Override
void execute(Task task1) {
println 'excute cleanBuildCache action !!!!!!'
}
})
task.setActions(list)
}
}
}
執(zhí)行gradle cleanBuildCache感受一下,
你會(huì)看到‘excute cleanBuildCache action !!!!!!'的打印字樣;
那為什么一定要放在afterEvaluate之后呢,因?yàn)檫@樣tasksGrap完成才有那么多task讓你遍歷,這就是理解生命周期所帶來(lái)的好處。
2、現(xiàn)在回顧我們之前主app寫(xiě)的代碼:
processProductDebugManifest;
project.tasks.each {
task->
if(task.getName().equals("processZroTestDebugManifest")){
println '!!!!!find processZroTestDebugManifest task:'
task.outputs.files.each {
file ->
println 'file.getAbsolutePath():'+ file.getAbsolutePath()
//file.getAbsolutePath():/Users/mu/MeiyouCode/PeriodProject/SeeyouClient/app/build/intermediates/manifests/instant-run/zroTest/debug
//file.getAbsolutePath():/Users/mu/MeiyouCode/PeriodProject/SeeyouClient/app/build/intermediates/manifests/full/zroTest/debug
//file.getAbsolutePath():/Users/mu/MeiyouCode/PeriodProject/SeeyouClient/app/build/outputs/logs/manifest-merger-zroTest-debug-report.txt
}
task.doLast {
println '!!!!!excute processZroTestDebugManifest task'
def dated = new Date().format("MMdd HH:mm")
def manifestFile = "${buildDir}/intermediates/manifests/full/zroTest/debug/AndroidManifest.xml"
def updatedContent = new File(manifestFile).getText('UTF-8')
.replaceAll("MY_APP_PKGNAME", "${MY_APP_PKGNAME}")
.replaceAll("MY_JPUSH_APPKEY", "${JPUSH_APPKEY}")
.replaceAll("MY_HUAWEI_APPKEY", "${HUAWEI_APPKEY}")
.replaceAll("MY_MEIZU_APPKEY", "${MEIZU_APPKEY}")
.replaceAll("MY_MEIZU_APPID", "${MEIZU_APPID}")
// .replaceAll("MY_XIAOMI_APPKEY", "${XIAOMI_APPKEY}")
// .replaceAll("MY_XIAOMI_APPID", "${XIAOMI_APPID}")
//.replaceAll("cn.jpush.android.service.PluginXiaomiPlatformsReceiver","com.meiyou.message.mipush.XiaomiReceiver")
.replaceAll("MY_APK_VERSION", "${archivesBaseName}-${dated}")
new File(manifestFile).write(updatedContent, 'UTF-8')
}
}
}
實(shí)際上也可以寫(xiě)成這樣(但是這樣因?yàn)樽兎N不確定,寫(xiě)死成zroTest/debug,所以還是用上面的方法比較好,直接替換所有的變種):
project.tasks.each {
task->
if(task.getName().equals("processZroTestDebugManifest")){
println '!!!!!find processZroTestDebugManifest task:'+task.outputs.files.each {
file ->
file.getAbsolutePath();
}
task.doLast {
println '!!!!!excute processZroTestDebugManifest task'
def dated = new Date().format("MMdd HH:mm")
def manifestFile = "${buildDir}/intermediates/manifests/full/zroTest/debug/AndroidManifest.xml"
def updatedContent = new File(manifestFile).getText('UTF-8')
.replaceAll("MY_APP_PKGNAME", "${MY_APP_PKGNAME}")
.replaceAll("MY_JPUSH_APPKEY", "${JPUSH_APPKEY}")
.replaceAll("MY_HUAWEI_APPKEY", "${HUAWEI_APPKEY}")
.replaceAll("MY_MEIZU_APPKEY", "${MEIZU_APPKEY}")
.replaceAll("MY_MEIZU_APPID", "${MEIZU_APPID}")
// .replaceAll("MY_XIAOMI_APPKEY", "${XIAOMI_APPKEY}")
// .replaceAll("MY_XIAOMI_APPID", "${XIAOMI_APPID}")
//.replaceAll("cn.jpush.android.service.PluginXiaomiPlatformsReceiver","com.meiyou.message.mipush.XiaomiReceiver")
.replaceAll("MY_APK_VERSION", "${archivesBaseName}-${dated}")
new File(manifestFile).write(updatedContent, 'UTF-8')
}
}
}
3、如何知道某個(gè)task干了什么呢,比如processZroTestDebugManifest或者Clean:
這些是com.android.tools.build到源碼里尋找或者直接compile ‘com.android.tools.build:gradle:3.0.1'直接從依賴庫(kù)里看源碼; 或者直接下載源碼(大概30G左右):
$ mkdir gradle_2.3.0 $ cd gradle_2.3.0 $ repo init -u https://android.googlesource.com/platform/manifest -b gradle_2.3.0 $ repo sync
大部分tasks都在com.android.build.gradle.tasks文件夾下,比如:ManifestProcessorTask和CleanBuildCache
具體可以:https://fucknmb.com/2017/06/01/Android-Gradle-Plugin源碼閱讀與編譯/
4、如何查找某個(gè)task的依賴呢,比如我想知道assmebleZroTestDebug執(zhí)行后最終執(zhí)行了哪些task;
1、編譯后打??;
gradle.addListener(new TaskListener())
class TaskListener implements BuildListener,TaskExecutionListener {
private List<String> tasks = new ArrayList<>();
@Override
void buildStarted(Gradle gradle) {
}
@Override
void settingsEvaluated(Settings settings) {
}
@Override
void projectsLoaded(Gradle gradle) {
}
@Override
void projectsEvaluated(Gradle gradle) {
}
@Override
void buildFinished(BuildResult result) {
StringBuilder stringBuilder = new StringBuilder();
for(String taskName:tasks){
stringBuilder.append(taskName).append("\n")
}
println("任務(wù)列表:\n"+stringBuilder.toString())
}
@Override
void beforeExecute(Task task) {
}
@Override
void afterExecute(Task task, TaskState state) {
//println("===>Task:"+task.getName())
tasks.add(task.getName())
}
}
2、不用編譯直接打??;
void printTaskDependency(Task task, String divider) {
divider += "-------"
task.getTaskDependencies().getDependencies(task).any() {
println(divider+ it.getPath())
if (it.getPath().contains(":app")) {
printTaskDependency(it,divider)
}
}
}
gradle.getTaskGraph().whenReady {
project.tasks.all {
//println("!!!!!!!!!! it:"+it.getName()+"==>it.getPath:"+it.getPath())
if (it.getPath().equals(":app:assembleZroTestDebug")) {
//println(it.getPath())
printTaskDependency(it,"")
}
}
}
5、常用技能
1、gradle :app:dependencies > 1.txt 分析整個(gè)app的aar依賴
可以用于排查依賴庫(kù)異常的問(wèn)題;
請(qǐng)注意?。簩?duì)工程依賴無(wú)效;
2、productFlavors和buildType概念,組合成變種 如:
productFlavors {
branchOne {
applicationId "com.example.branchOne"
buildConfigField "String", "CONFIG_ENDPOINT", "http://branchOne.com/android"
}
branchTwo {
applicationId "com.example.branchTwo"
buildConfigField "String", "CONFIG_ENDPOINT", "http://branchTwo.org"
}
}
dependencies {
compile 'com.android.support:support-v4:22.2.0'
branchOneCompile 'com.android.support:appcompat-v7:22.2.0'//只為branchOne添加這個(gè)依賴
}
3、排除依賴和強(qiáng)制使用某個(gè)版本和強(qiáng)制排除某個(gè)庫(kù):
configurations.all {
resolutionStrategy {
// force 'org.javassist:javassist:3.18.2-GA'
// don't cache changing modules at all
cacheChangingModulesFor 0, 'seconds'
// //強(qiáng)制模塊使用指定版本號(hào)(防止其他模塊使用、跟主工程不匹配的版本:
forcedModules = [
"com.meiyou:peroid.base:${PERIOD_BASE_VERSION}",
'org.javassist:javassist:3.18.2-GA'//"org.javassist:javassist:3.20.0-GA"http://
, 'com.google.guava:guava:18.0'//'com.google.guava:guava:19.0-rc2'//
]
exclude group: 'com.squareup.okhttp3'
exclude group: 'com.google.code.findbugs', module: 'annotations'
}
}
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- Android Gradle依賴管理、去除重復(fù)依賴、忽略的方式
- android studio更新gradle錯(cuò)誤構(gòu)建項(xiàng)目失敗的解決方法
- Android Studio和Gradle使用不同位置JDK的問(wèn)題解決
- gradle+shell實(shí)現(xiàn)自動(dòng)系統(tǒng)簽名
- Jenkins使用Gradle編譯Android項(xiàng)目詳解
- Gradle環(huán)境下導(dǎo)出Swagger為PDF的步驟詳解
- Android Studio Gradle依賴沖突解決方法
- 基于Java Gradle復(fù)制項(xiàng)目模塊過(guò)程圖解
相關(guān)文章
Android開(kāi)發(fā)新手常見(jiàn)的10個(gè)誤區(qū)
這篇文章主要介紹了Android開(kāi)發(fā)新手常見(jiàn)的10個(gè)誤區(qū),我們?nèi)匀豢吹搅四男┬碌腁ndr​​oid開(kāi)發(fā)人員不斷重復(fù)的錯(cuò)誤,這里有10個(gè)最常見(jiàn)的誤區(qū),需要的朋友可以參考下2015-03-03
android閱讀器長(zhǎng)按選擇文字功能實(shí)現(xiàn)代碼
本篇文章主要介紹了android閱讀器長(zhǎng)按選擇文字功能實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,有興趣的可以了解一下2017-07-07
PC版與Android手機(jī)版帶斷點(diǎn)續(xù)傳的多線程下載
這篇文章主要介紹了PC版與Android手機(jī)版帶斷點(diǎn)續(xù)傳的多線程下載的相關(guān)資料,需要的朋友可以參考下2015-10-10
Android Studio 3.0后出現(xiàn)AAPT2與“android.enableAapt2”問(wèn)題的解決方法
這篇文章主要給大家介紹了關(guān)于Android Studio 3.0后出現(xiàn)AAPT2與“android.enableAapt2”問(wèn)題的解決方法,文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
Android提高之模擬信號(hào)示波器的實(shí)現(xiàn)
這篇文章主要介紹了Android模擬信號(hào)示波器的實(shí)現(xiàn)方法,在Android項(xiàng)目開(kāi)發(fā)中有一定的實(shí)用價(jià)值,需要的朋友可以參考下2014-08-08
Android開(kāi)發(fā)筆記SQLite優(yōu)化記住密碼功能
這篇文章主要為大家詳細(xì)介紹了Android開(kāi)發(fā)筆記SQLite優(yōu)化記住密碼功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
Android4.X讀取SIM卡短信和聯(lián)系人相關(guān)類實(shí)例分析
這篇文章主要介紹了Android 4.X讀取SIM卡短信和聯(lián)系人相關(guān)類,以實(shí)例形式分析了Android 4.X讀取SIM卡短信和聯(lián)系人的兩個(gè)相關(guān)類的功能、用法與注意事項(xiàng),具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10
微信小程序電商常用倒計(jì)時(shí)實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了微信小程序電商常用倒計(jì)時(shí)實(shí)現(xiàn)實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06

