关于gradle你应该知道的一些小事
前言
gradle的定义(来自维基百科)
Gradle是一个基于ApacheAnt和ApacheMaven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。当前其支持的语言限于Java、Groovy和Scala,计划未来将支持更多的语言。
通俗的理解:gradle是一种构建工具,我们可以用他来对多工程进行各种管理(依赖,打包,部署,发布,各种渠道的差异管理);
有些时候,我们会有一些个性化的构建需求,比如我们引入了第三方库,或者我们想要在通用构建过程中做一些其他的事情,这时我们就要自己在系统默认构建规则上做一些修改。这时候我们就要自己向Gradle”下命令“了,这时候我们就需要用Gradle能听懂的话了,也就是Groovy。
我们在开头处提到“Gradle是一种构建工具”。实际上,当我们想要更灵活的构建过程时,Gradle就成为了一个编程框架——我们可以通过编程让构建过程按我们的意愿进行。也就是说,当我们把Gradle作为构建工具使用时,我们只需要掌握它的配置脚本的基本写法就OK了;而当我们需要对构建流程进行高度定制时,就务必要掌握Groovy等相关知识了。
遭遇的问题
我们在实时多项目构建的时候经常遇到以下这些问题:
1、同时依赖了不同版本的某个库,编译时出现duplicateclass错误;
2、gradle不同版本api报错;
3、不会写gradle配置,看不懂gradle语法,不知道从何学起;
4、对编译过程中gradle的报错无从下手;
等等…
我们接下来将从实际项目出发一步一步来学习gradle的这些事,本文主旨在于学习gradle的思路,深度细节将会忽略;
揭开Gradle的面纱
一、理解打包命令gradlecleanassembleDebug/assembleRelease
以上这条命令可以分解为三个部分,gradle,clean,assembleDebug;实际上就和我们执行脚本一样,gradle是执行器,而clean和assembleDebug是入参,在这里它们两个代表不同的task,就类似gradletask1task2这样。
二、什么是task?
在build.gradle写上
tasktask1{
println"===>task1"
}
tasktask2{
println"===>task2"
}
这样就定义了两个task;当我们执行gradletask1task2-q的时候(-q是设置日志级别),理论上会看到日志输出:
===>task1
===>task2
task的关系有dependsOn,mustRunAfter等等,由于项目中用的比较少这里先跳过这部分;
这里我们简单讲一下闭包的概念:
闭包在groovy中是一个处于代码上下文中的开放的,匿名代码块。它可以访问到其外部的变量或方法,
更详细的请自行google
然而,当我们在项目里执行gradletask1task2-q的时候,我们发现输出是这样的:
SeeyouClientgit:(SeeyouClient-dev)✗gradletask1task2-q
doPackagevalue:False
Configuration'compile'inproject':app'isdeprecated.Use'implementation'instead.
==============annaapplystart==================
configurationdo:
include
** onClick
** onItemClick
** onCheckedChanged
** onItemSelected
** onSwitchButtonCheck
** onItemLongClick
** onLongClick
** onPullRefresh
** OnRefresh
configurationdo:
exclude
org/conscrypt/
configurationdo:
exceptions
java/lang/Exception
java/lang/NullPointerException
configurationdo:
switch
custom
needinject=false
==============annaapplyend==================
Configuration'provided'inproject':app'isdeprecated.Use'compileOnly'instead.
Configuration'debugCompile'inproject':app'isdeprecated.Use'debugImplementation'instead.
===>task1
===>task2
DexKnife:ProcessingVariant
DexKnife:processSplitDextrue
DexKnife:processingTask
----------------------tinkerbuildwarning------------------------------------
tinkerautooperation:
excludingannotationprocessorandsourcetemplatefromapppackaging.EnabledxjumboModetoreducepackagesize.
enabledxjumboModetoreducepackagesize.
disablepreDexLibrariestopreventClassDefNotFoundExceptionwhenyourappisbooting.
disablearchivedexmodesofarforkeepingdexapply.
tinkerwillchangeyourbuildconfigs:
wewilladdTINKER_ID=117inyourbuildoutputmanifestfilebuild/intermediates/manifests/full/*
ifminifyEnabledistrue
youwillfindthegenproguardrulefileatbuild/intermediates/tinker_intermediates/tinker_proguard.pro
andwewillhelpyoutoputitintheproguardFiles.
ifmultiDexEnabledistrue
youwillfindthegenmultiDexKeepProguardfileatbuild/intermediates/tinker_intermediates/tinker_multidexkeep.pro
andwewillhelpyoutoputitintheMultiDexKeepProguardFile.
ifapplyResourceMappingfileisexist
wewillbuildappapkwithresourceR.txtfile
ifresources.arschaschanged,youshoulduseapplyResourcemodetobuildthenewapk!
-----------------------------------------------------------------
Taskspendtime:
这是为什么呢?原因是gradle具有自己的生命周期:
初始化阶段:负责判断有多少个Projects参与构建:
先执行settings.gradle
配置阶段:负责对初始化阶段创建的Projects完成配置:
比如添加Task,修改Task的行为,闭包的内容会被执行,执行build.gradle的内容;
执行阶段:根据配置阶段的配置执行任务:
执行task对应的内容,如doLast,doFirst之类的
因此gradletask1task2-q的输出日志就可以理解了,其实按照task1和task2的写法,执行gradletask1task2-q和gradle-q实际上效果是一样的。
三、gradlecleanassmebleDebug到底做了什么?(源码追踪和依赖分析出编译流程)
1、打开gradle-4.5.1/bin/gradle文件可以看到执行了代码:
evalset--$DEFAULT_JVM_OPTS$JAVA_OPTS$GRADLE_OPTS"\"-Dorg.gradle.appname=$APP_BASE_NAME\""-classpath"\"$CLASSPATH\""org.gradle.launcher.GradleMain"$APP_ARGS"
最终调用exec"$JAVACMD""$@"来执行;所以入口就是:org.gradle.launcher.GradleMain
具体细节可以参照:https://blog.csdn.net/yanbober/article/details/60584621
2,最终会调用DefaultGradleLauncher里,我们可以很明确的看到它的生命周期:
这边最需要注意的时候,当我们只执行gradle-q这样的时候,实际上每一次都会执行到TaskGraph的阶段;也就是所有的tasks都已经梳理完成;
publicclassDefaultGradleLauncherimplementsGradleLauncher{
//(这里是4.5.1的版本,生命周期更细致化)
privateenumStage{
Load,LoadBuild,Configure,TaskGraph,Build,Finished
}
//2.14.1的版本则是:
privateenumStage{
Load,Configure,Build
}
//核心方法
privatevoiddoBuildStages(StageupTo){
try{
loadSettings();
if(upTo==Stage.Load){
return;
}
configureBuild();
if(upTo==Stage.Configure){
return;
}
constructTaskGraph();
if(upTo==Stage.TaskGraph){
return;
}
runTasks();
finishBuild();
}catch(Throwablet){
Throwablefailure=exceptionAnalyser.transform(t);
finishBuild(newBuildResult(upTo.name(),gradle,failure));
thrownewReportedException(failure);
}
}
//调用时机
@Override
publicSettingsInternalgetLoadedSettings(){
doBuildStages(Stage.Load);
returnsettings;
}
@Override
publicGradleInternalgetConfiguredBuild(){
doBuildStages(Stage.Configure);
returngradle;
}
publicGradleInternalexecuteTasks(){
doBuildStages(Stage.Build);
returngradle;
}
四、知道编译流程后有什么用呢?
1、我们经常在app/build.gradle看到这样的代码:
project.afterEvaluate{...}
android.applicationVariants.all{...}
gradle.addListener(newTaskListener())
applyfrom'../mvn.gradle'
...
这里我们介绍几个概念:
project
对应gradle源码的Project.java(按住control点击project会自动跳转),里边提供一些对外的方法,如afterEvalute,beforeEvalue;在理解编译流程后,才能灵活的使用这些api;
android
对应gradle插件的AppExtension.java文件,提供了一些对外的参数和方法,我们可以使用android.xxx来访问app/build.gradle里的任意参数和方法;
gradle
对应的gradle源码里的Gradle.java对象,也是提供了一系列的方法给外部使用;
那么接下来假设我们有这样一个需求:找到一个叫cleanBuildCache的task,找到之后添加一个action,打印一行字;要实现这个需求,首先我们如何遍历这个app的所有task:
有很多种写法:
gradle.getTaskGraph().whenReady{
project.tasks.each{
task->
println"taskName:"+task.getName()
}
}
project.afterEvaluate{
project.tasks.each{
task->
println"taskName:"+task.getName()
}
}
执行gradle-q感受一下。
接下看看如何添加action
project.afterEvaluate{
project.tasks.each{
task->
//println"taskName:"+task.getName()
if(task.getName().equals("cleanBuildCache")){
println"findcleanBuildCache!!!!!!"
List>list=newArrayList<>()
list.add(newAction(){
@Override
voidexecute(Tasktask1){
println'excutecleanBuildCacheaction!!!!!!'
}
})
task.setActions(list)
}
}
}
执行gradlecleanBuildCache感受一下,
你会看到‘excutecleanBuildCacheaction!!!!!!'的打印字样;
那为什么一定要放在afterEvaluate之后呢,因为这样tasksGrap完成才有那么多task让你遍历,这就是理解生命周期所带来的好处。
2、现在回顾我们之前主app写的代码:
processProductDebugManifest;
project.tasks.each{
task->
if(task.getName().equals("processZroTestDebugManifest")){
println'!!!!!findprocessZroTestDebugManifesttask:'
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'!!!!!excuteprocessZroTestDebugManifesttask'
defdated=newDate().format("MMddHH:mm")
defmanifestFile="${buildDir}/intermediates/manifests/full/zroTest/debug/AndroidManifest.xml"
defupdatedContent=newFile(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}")
newFile(manifestFile).write(updatedContent,'UTF-8')
}
}
}
实际上也可以写成这样(但是这样因为变种不确定,写死成zroTest/debug,所以还是用上面的方法比较好,直接替换所有的变种):
project.tasks.each{
task->
if(task.getName().equals("processZroTestDebugManifest")){
println'!!!!!findprocessZroTestDebugManifesttask:'+task.outputs.files.each{
file->
file.getAbsolutePath();
}
task.doLast{
println'!!!!!excuteprocessZroTestDebugManifesttask'
defdated=newDate().format("MMddHH:mm")
defmanifestFile="${buildDir}/intermediates/manifests/full/zroTest/debug/AndroidManifest.xml"
defupdatedContent=newFile(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}")
newFile(manifestFile).write(updatedContent,'UTF-8')
}
}
}
3、如何知道某个task干了什么呢,比如processZroTestDebugManifest或者Clean:
这些是com.android.tools.build到源码里寻找或者直接compile‘com.android.tools.build:gradle:3.0.1'直接从依赖库里看源码;或者直接下载源码(大概30G左右):
$mkdirgradle_2.3.0 $cdgradle_2.3.0 $repoinit-uhttps://android.googlesource.com/platform/manifest-bgradle_2.3.0 $reposync
大部分tasks都在com.android.build.gradle.tasks文件夹下,比如:ManifestProcessorTask和CleanBuildCache
具体可以:https://fucknmb.com/2017/06/01/Android-Gradle-Plugin源码阅读与编译/
4、如何查找某个task的依赖呢,比如我想知道assmebleZroTestDebug执行后最终执行了哪些task;
1、编译后打印;
gradle.addListener(newTaskListener())
classTaskListenerimplementsBuildListener,TaskExecutionListener{
privateListtasks=newArrayList<>();
@Override
voidbuildStarted(Gradlegradle){
}
@Override
voidsettingsEvaluated(Settingssettings){
}
@Override
voidprojectsLoaded(Gradlegradle){
}
@Override
voidprojectsEvaluated(Gradlegradle){
}
@Override
voidbuildFinished(BuildResultresult){
StringBuilderstringBuilder=newStringBuilder();
for(StringtaskName:tasks){
stringBuilder.append(taskName).append("\n")
}
println("任务列表:\n"+stringBuilder.toString())
}
@Override
voidbeforeExecute(Tasktask){
}
@Override
voidafterExecute(Tasktask,TaskStatestate){
//println("===>Task:"+task.getName())
tasks.add(task.getName())
}
}
2、不用编译直接打印;
voidprintTaskDependency(Tasktask,Stringdivider){
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分析整个app的aar依赖
可以用于排查依赖库异常的问题;
请注意!:对工程依赖无效;
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添加这个依赖
}
3、排除依赖和强制使用某个版本和强制排除某个库:
configurations.all{
resolutionStrategy{
//force'org.javassist:javassist:3.18.2-GA'
//don'tcachechangingmodulesatall
cacheChangingModulesFor0,'seconds'
////强制模块使用指定版本号(防止其他模块使用、跟主工程不匹配的版本:
forcedModules=[
"com.meiyou:peroid.base:${PERIOD_BASE_VERSION}",
'org.javassist:javassist:3.18.2-GA'//"org.javassist:javassist:3.20.0-GA"//
,'com.google.guava:guava:18.0'//'com.google.guava:guava:19.0-rc2'//
]
excludegroup:'com.squareup.okhttp3'
excludegroup:'com.google.code.findbugs',module:'annotations'
}
}
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。