数码控科技猎奇Iphone动漫星座游戏电竞lolcosplay王者荣耀攻略allcnewsBLOGNEWSBLOGASKBLOGBLOGZSK全部技术问答问答技术问答it问答代码软件新闻开发博客电脑/网络手机/数码笔记本电脑互联网操作系统软件硬件编程开发360产品资源分享电脑知识文档中心IT全部全部分类全部分类技术牛文全部分类教程最新网页制作cms教程平面设计媒体动画操作系统网站运营网络安全服务器教程数据库工具网络安全软件教学vbscript正则表达式javascript批处理更多»编程更新教程更新游戏更新allitnewsJava新闻网络医疗信息化安全创业站长电商科技访谈域名会议专栏创业动态融资创投创业学院 / 产品经理创业公司人物访谈营销开发数据库服务器系统虚拟化云计算嵌入式移动开发作业作业1常见软件all电脑网络手机数码生活游戏体育运动明星影音休闲爱好文化艺术社会民生教育科学医疗健康金融管理情感社交地区其他电脑互联网软件硬件编程开发360相关产品手机平板其他电子产品摄影器材360硬件通讯智能设备购物时尚生活常识美容塑身服装服饰出行旅游交通汽车购房置业家居装修美食烹饪单机电脑游戏网页游戏电视游戏桌游棋牌游戏手机游戏小游戏掌机游戏客户端游戏集体游戏其他游戏体育赛事篮球足球其他运动球类运动赛车健身运动运动用品影视娱乐人物音乐动漫摄影摄像收藏宠物幽默搞笑起名花鸟鱼虫茶艺彩票星座占卜书画美术舞蹈小说图书器乐声乐小品相声戏剧戏曲手工艺品历史话题时事政治就业职场军事国防节日风俗法律法规宗教礼仪礼节自然灾害360维权社会人物升学入学人文社科外语资格考试公务员留学出国家庭教育学习方法语文物理生物工程学农业数学化学健康知识心理健康孕育早教内科外科妇产科儿科皮肤科五官科男科整形中医药品传染科其他疾病医院两性肿瘤科创业投资企业管理财务税务银行股票金融理财基金债券保险贸易商务文书国民经济爱情婚姻家庭烦恼北京上海重庆天津黑龙江吉林辽宁河北内蒙古山西陕西宁夏甘肃青海新疆西藏四川贵州云南河南湖北湖南山东江苏浙江安徽江西福建广东广西海南香港澳门台湾海外地区

关于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'
  }
 }

总结

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

您可能感兴趣的文章:


  • 本文相关:
  • 为android studio编写自定义gradle插件的教程
  • gradle编译打包android apk详细介绍
  • 详解androidstudio3.0 关于gradle报错的问题(小结)
  • 详解关于android studio中安装和gradle的一些坑
  • android studio 3.0 gradle 打包脚本配置详解
  • android studio使用教程(四):gradle基础
  • android studio利用gradle打jar包并混淆的方法详解
  • android如何在gradle中更改apk文件名详解
  • android客户端程序gradle如何打包
  • androidstudio gradle第三依赖统一管理的实现方法
  • android使用mediarecorder实现录像功能
  • android通过继承binder类实现多进程通信
  • android 中volley二次封装并实现网络请求缓存
  • android解析json数据
  • kotlin基础学习之位运算
  • 浅谈android开发者2017年最值得关注的25个实用库
  • 详解android自定义view--自定义柱状图
  • android实战教程第八篇之短信备份
  • android应用第一次安装成功点击“打开”后home键切出应用后再点击
  • android开发教程之获取power_profile.xml文件的方法(android运行
  • 免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved