关于gradle你应该知道的一些小事

前言

gradle的定义(来自维基百科)

Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化建构工具。它使用一种基于Groovy的特定领域语言来声明项目设置,而不是传统的XML。当前其支持的语言限于Java、Groovy和Scala,计划未来将支持更多的语言。

通俗的理解:gradle是一种构建工具,我们可以用他来对多工程进行各种管理(依赖,打包,部署,发布,各种渠道的差异管理);

有些时候,我们会有一些个性化的构建需求,比如我们引入了第三方库,或者我们想要在通用构建过程中做一些其他的事情,这时我们就要自己在系统默认构建规则上做一些修改。这时候我们就要自己向Gradle”下命令“了,这时候我们就需要用Gradle能听懂的话了,也就是Groovy。

我们在开头处提到“Gradle是一种构建工具”。实际上,当我们想要更灵活的构建过程时,Gradle就成为了一个编程框架——我们可以通过编程让构建过程按我们的意愿进行。也就是说,当我们把Gradle作为构建工具使用时,我们只需要掌握它的配置脚本的基本写法就OK了;而当我们需要对构建流程进行高度定制时,就务必要掌握Groovy等相关知识了。

遭遇的问题

我们在实时多项目构建的时候经常遇到以下这些问题:

1、同时依赖了不同版本的某个库,编译时出现duplicate class错误;

2、gradle 不同版本api报错;

3、不会写gradle配置,看不懂gradle语法,不知道从何学起;

4、对编译过程中gradle的报错无从下手;

等等…

我们接下来将从实际项目出发一步一步来学习gradle的这些事,本文主旨在于学习gradle的思路,深度细节将会忽略;

揭开Gradle的面纱

一、理解打包命令 gradle clean assembleDebug/assembleRelease

以上这条命令可以分解为三个部分,gradle,clean, assembleDebug;实际上就和我们执行脚本一样,gradle是执行器,而clean 和 assembleDebug是入参, 在这里它们两个代表不同的task,就类似gradle task1 task2 这样。

二、什么是task?

在build.gradle写上

task task1 {
 println "===>task 1"
}
task task2 {
 println "===>task 2"
}

这样就定义了两个task;当我们执行gradle task1 task2 -q的时候(-q是设置日志级别),理论上会看到日志输出:

===>task 1
===>task 2

task的关系有dependsOn,mustRunAfter等等,由于项目中用的比较少这里先跳过这部分;

这里我们简单讲一下闭包的概念:

闭包在groovy中是一个处于代码上下文中的开放的,匿名代码块。它可以访问到其外部的变量或方法,
更详细的请自行google

然而,当我们在项目里执行gradle task1 task2 -q的时候,我们发现输出是这样的:

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具有自己的生命周期:

初始化阶段:负责判断有多少个Projects参与构建:
 先执行settings.gradle
配置阶段:负责对初始化阶段创建的Projects完成配置:
 比如添加Task,修改Task的行为,闭包的内容会被执行,执行build.gradle的内容;
执行阶段:根据配置阶段的配置执行任务:
 执行task对应的内容,如doLast,doFirst之类的

因此gradle task1 task2 -q的输出日志就可以理解了,其实按照task1和task2的写法,执行gradle task1 task2 -q和gradle -q实际上效果是一样的。

三、gradle clean assmebleDebug到底做了什么?(源码追踪和依赖分析出编译流程)

1、打开gradle-4.5.1/bin/gradle文件可以看到执行了代码:

 eval set -- $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都已经梳理完成;

public class DefaultGradleLauncher implements GradleLauncher {
 //(这里是4.5.1的版本,生命周期更细致化)
 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);
  }
 }

 //调用时机
  @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、我们经常在app/build.gradle看到这样的代码:

project.afterEvaluate {...}
android.applicationVariants.all {...}
gradle.addListener(new TaskListener())
apply from '../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 "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)
   }
 }
}

执行gradle cleanBuildCache感受一下,

你会看到‘excute cleanBuildCache action !!!!!!'的打印字样;

那为什么一定要放在afterEvaluate之后呢,因为这样tasksGrap完成才有那么多task让你遍历,这就是理解生命周期所带来的好处。

2、现在回顾我们之前主app写的代码:

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')
    }
   }
 }

实际上也可以写成这样(但是这样因为变种不确定,写死成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、如何知道某个task干了什么呢,比如processZroTestDebugManifest或者Clean:

这些是com.android.tools.build到源码里寻找或者直接compile ‘com.android.tools.build:gradle:3.0.1'直接从依赖库里看源码; 或者直接下载源码(大概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、如何查找某个task的依赖呢,比如我想知道assmebleZroTestDebug执行后最终执行了哪些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("任务列表:\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 分析整个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't cache changing modules at all
   cacheChangingModulesFor 0, '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'//
   ]
   exclude group: 'com.squareup.okhttp3'
   exclude group: 'com.google.code.findbugs', module: 'annotations'
  }
 }

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Spring Boot使用Thymeleaf + Gradle构建war到Tomcat

    Spring Boot 以Jar的方式部署启动,这个不用介绍了, 之前也介绍了关于 Spring Boot + thymeleaf 的简单使用 ,但是今天遇到一个问题, 我先描述下问题的场景: 由于运维部门的需求,项目需要以war的形式放到tomcat运行 ,而不是原定的jar的方式运行 配置了一下午,也查了一下午的资料,以war的方式在Tomcat能运行,并且能访问Controller,但是在返回html视图时,找不到视图模板.最终发现问题在Thymeleaf的配置,话不多说,具体看操作步骤:

  • Android Studio Gradle插件版本与Gradle版本之间的对应关系

    1.gradle插件版本配置位置: project对应的build.gradle文件中 buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:1.2.3' } } 2.gradle版本配置位置: gradle-wrapper.properties 中 distributionUrl=https\://services.gradle.org/distribu

  • spring boot使用sonarqube来检查技术债务

    作为代码质量检查的流行工具,比如Sonarqube能够检查代码的"七宗罪",跟代码结合起来能够更好地提高代码的质量,让我们来看一下,刚刚写的Springboot2的HelloWorld的代码有什么"罪". Sonarqube Sonarqube可以使用docker版本快速搭建,可以参看一下Easypack整理的镜像,具体使用可以参看如下链接,这里不再赘述: https://hub.docker.com/r/liumiaocn/sonarqube/ 环境假定 本文使用

  • 给Android初学者的Gradle知识普及

    Gradle build android 历史 Android Tools 主页 ,大概是2016年2月份发布 adt21.1 的时候,忽然在主页发现了New Build System 原来是可以用gradle 来构建android项目,至于gradle是什么(既然点击进来看了应该都知道了吧.).然后,又看了一下RoadMap 那时候,还并不支持Proguard 打包,于是就没看了. android studio 发布,终于gradle 0.4 也跟着出来了,于是,先把gradle 学了一遍,然

  • Spring Boot配置Thymeleaf(gradle)的简单使用

    最近项目用到了Spring Boot ,但是在控制器返回html视图并渲染参数的时候,存在了疑问.后面考虑用Thymeleaf ,感觉真的不错,下面分享给大家 总共四步: jar 引入 控制器参数传递 html标签引入 Thymeleaf 缓存设置 一.相关Jar的引用 1.maven的引用方式: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-

  • Gradle进阶使用结合Sonarqube进行代码审查的方法

    作为代码质量检查的流行工具,比如Sonarqube能够检查代码的"七宗罪",跟代码结合起来能够更好地提高代码的质量,这篇文章将会介绍如何结合gradle和sonarqube对代码质量与测试覆盖率进行分析. Sonarqube Sonarqube可以使用docker版本快速搭建,可以参看一下Easypack整理的镜像,具体使用可以参看如下链接,这里不再赘述: https://hub.docker.com/r/liumiaocn/sonarqube/ 环境假定 本文使用到的sonarqub

  • 使用Sonarqube扫描Javascript代码的示例

    使用sonarqube对javascript代码进行扫描,分析代码质量,最简单的方式莫过于使用缺省的sonar-way中的javascript的规则,使用sonar-scanner进行扫描,这篇文章通过最简单的例子,来进行说明. 事前准备 Sonarqube Sonarqube可以使用docker版本快速搭建,可以参看一下Easypack整理的镜像,具体使用可以参看如下链接,这里不再赘述: https://hub.docker.com/r/liumiaocn/sonarqube/ 环境假定 本文

  • 优化Gradle提升Gradle编译速度

    一. 我们先说一下提升Gralde编译速度. 针对这个问题, 先讲一下配置相关设置提升编译速度.  我发现在build.gradle文件中如果设置了很多渠道, 在编译时会影响编译速度, 尤其是windows系统下运行. 对gradle中渠道进行优化, 可以提升编译速度 1. 在user/.gradle文件下创建gradle.properties文件, 并在中写上org.gradle.daemon=true , 保存. 2. 设置studio, 打开setting >> gradle>&g

  • Android gradle插件打印时间戳的方法详解

    Android中时间戳的详细解释: (1).定义: 时间戳就是根据当前系统时间生成的一组随机数字. (2).作用: 作为对数据唯一性的一种判断依据.避免了重复修改数据所带来的错误! (3).应用: (1).在银行account表中建立时间戳字段timestamp,设定为文本类型varchar. (2).当银行A读取account表中的存款字段时,同时也读取时间戳字段,比如123456. (3).当银行A修改完存款数值后,进行存盘操作时,将先前读取的时间戳123456与当时表中的时间戳进行一次对比

  • Android中的build.gradle文件深入讲解

    一.什么是Gradle gradle是一个项目构建工具,java开发中有两个大名鼎鼎的项目构建Maven,Ant,其中maven简单易控深受开发者喜爱. 项目构建工具是什么?能干吗? 开发某些项目时,需要很多jar或者库的支持,还没开始开发就下载了n多个库,浪费了时间不说,这些库之间兼容性又无法保证.删了?白下载了.不删?又不兼容.此时就需要使用项目构建工具,简单的说就是一个可以根据简单的配置文件自动去下载相应包/库的软件.自动的还很简单,不用岂不亏了.多个项目构建工具各有特点,但是核心思想一样

随机推荐