Gradle工作原理全面了解

Posted xjz729827161

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Gradle工作原理全面了解相关的知识,希望对你有一定的参考价值。

说gradle之前先扯个笑话,有次我给我同事说,gradle在web里面管理各种jar也挺好用的,我同事跳起来“纳尼,这玩意还可以用在别的地方,不是android特有的么?” 。可能做某一项工作可能就认为都是为自己服务的 ,这个想法很奇怪的。 gradle是一个用来管理的编译构建流程的软工具而已,我是这么理解的。

一、 没有你的日子里(手动悲伤)

如果你手动的来完成一个可以执行的java程序要经历什么步骤

  1. 完成一个功能总不能都是自己写的代码吧,总会用到一些第三方的包吧, 这个时候你需要下载第三方的包放在本地
  2. java文件要 通过java自带的工具编译成 .class
  3. 生成.class可能还要顺带生成文档
  4. 最后再要用 java自带的工具将 所有的资源一起打包成 一个.jar

最简单大概就这这些步骤。如果还要加上一些编译多个差异包之类的需要,就更复杂。仔细认真的想想,这特么的也太麻烦了。如果同样的工程要换个电脑该怎么办?这些都问题。 要想办法解决这些问题,让问题变的简单起来,这就是gradle的作用了。帮助我们做了一些需要手动做的事,减轻我们的工作量,把关注点让在具体的工程代码上面。

二、 瞅一眼gradle

万一很简单,咱瞅一眼就能懂了呢?

gradle的官网是这么定义的

Gradle is an open-source build automation tool 
focused on flexibility and performance.
 Gradle build scripts are written using a Groovy or Kotlin DSL.

懵逼…字我都认识,可是 groovy是啥。DSL是啥。
再百度下groovy:

Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,
它结合了Python、Ruby和Smalltalk的许多强大的特性,
Groovy 代码能够与 Java 代码很好地结合,也能用于扩展现有代码。
由于其运行在 JVM 上的特性,Groovy 可以使用其他 Java 语言编写的库。

再瞅瞅 DSL

领域特定语言(英语:domain-specific language、DSL)
指的是专注于某个应用程序领域的计算机语言。

对于最上面那一串的翻译过来就是:有一个语言叫groovy ,咱用它制定一些规则,来完成上面所说的操蛋的构建的事。这种新的脚本语言就是gradle

因为先不回学习自定义,先跳过groovy语法的部分,先瞅瞅gradle的一套规则是怎么运行的。

三、 纯粹的gralde

gradle的是一个单独的东东,是可以脱离IDE 脱离具体的某个类型的项目(java,android)存在的。我们先瞅瞅它的原始模样。

  1. 准备工作
    gradle是一个单独的工具,可以单独执行。 为了方便我们把它配置到环境变量中。 如果之前有使用过gradle,一般就是找到C:\\Users\\xxxxxx\\.gradle\\wrapper\\dists\\gradle-4.4-all这样的目录就有了,配置其bin目录到环境变量中即可。 如果之前没有使用过,可以单独下载。

  2. 目录结构
    在某个你要使用的目录下调用gradle.bat init就可以完成初始化。
    如图1。
    这个就最基本的结构了。

    文件作用
    gradle里面配置gradle的来源和一个基本配置,默认的是一个网络连接
    .gradle自动生成的一些记录,可以不用管它
    build.gradle
    setting.gradle设置目录,决定了整个工程包含哪些工程
    gradlew.bat执行任务可以直接调用这个gradlew.bat脚本,只是对命令 做了一封装

    为了更加的丰富测试,我加了两个工程, 创建了文件夹projectA projectB里面分别创建一个空的build.gradle文件,在文件中加入了一句打印的话。然后在setting.gralde中加上include projcetA, projectB,在
    再来查看工程中的project gradlew.bat projects

    添加一个简单的任务默认来执行,我擦,居然还需要下载,好吧我本地其实已经下载了的。那我把它拷贝本地wraper中,gralde.wrapper 中的链接指向一个本地链接就好了。在 //gradle.wrapper中修改对应的url地址。
    修改后就 快了很多,这个时候就可以大大的节省时间,再也不用担心玩命的等待了。

  3. 本来想顺着官网的文档来挨着讲。可是回想起我自己琢磨gradle的时候,我特么最关心就是它是怎么运行的,以及怎么自定义。那就先说这个事。

    • 总的执行顺序 初始化->配置->执行阶段
    • 初始化阶段:扫描setting.gradle创建gradle 等对象 官网初始化的解释
    • 配置阶段: 扫描 各个 build.gradle 创建对应的project ,获取项目中有多少个task,因为task的执行有顺序,这里就为task构建一个DFA(有向无环图)
    • 执行阶段:运行所有的task
      因为是脚本语言,其实每次执行都会把脚本让进解释器读一遍,所以每次执行某个task都会把以上的三个阶段走一次。

这里有几个概念很关键,gradle, project, task。 gradle全局可见,相当于这个gradle工具本身。 project是一个模块,与android studio中的module是对应的关系,也对应了一个build.gradle的文件, task是最小的执行块。为了方便,我们来有两个project的工程来模拟, 他们的具体执行顺序进一步细分为下面的这个样子。

看到这幅图,我们就能知道这个工具的能力边界在哪里。通过一步步的配置操作,形成一个任务的DFA来继续执行,如果我们需要在某些节点插入一些操作,就可以有两种方式:

  1. 使用系统的提供的回调监听 #800a00
  2. 调整task 或者 创建新的task修改task的执行图 #802300

3.1 设置监听插入自定义操作到整个运行过程

所有gradle中能操作的动作,有对应的类文件俩定义它,我们搜索可查看Gradle.java的文件可以查看到这些监听的事件。

	
    /**
     * Adds the given listener to this build. The listener may implement any of the given listener interfaces:
     *
     * <ul>
     * <li>@link org.gradle.BuildListener
     * <li>@link org.gradle.api.execution.TaskExecutionGraphListener
     * <li>@link org.gradle.api.ProjectEvaluationListener
     * <li>@link org.gradle.api.execution.TaskExecutionListener
     * <li>@link org.gradle.api.execution.TaskActionListener
     * <li>@link org.gradle.api.logging.StandardOutputListener
     * <li>@link org.gradle.api.tasks.testing.TestListener
     * <li>@link org.gradle.api.tasks.testing.TestOutputListener
     * <li>@link org.gradle.api.artifacts.DependencyResolutionListener
     * </ul>
     *
     * @param listener The listener to add. Does nothing if this listener has already been added.
     */
    void addListener(Object listener);

一般的我们我们比较关心 projectEvaluation这个对应中扫描build.gradle文件的动作,我们可以知道扫描时候扫描开始,也可以知道什么时候扫描结束,taskGraph就对应上面的念念叨叨的任务所形成的有向无环图。

动作对应的类文件动作的能力
projectEvaluateProject.javabeforeEvaluate文件扫描前 afterEvaluate文件扫描后
taskGraphTaskExecutionGraph.javawhenReady beforeTask afterTask

在工程projectA 和 projectB的build.gradle中加入扫描的监听,将其输出来,还是向最上面一样,执行taskB

针对taskB来做监听

println( "I am Project root ")

task testA 
	doLast 
	  println( "testA doLast")
	


task testB 
	  doFirst 
        println( "testB doFirst")
    
	
	doLast 
	  println( "testB doLast")
	

testB.dependsOn(testA)


project.gradle.taskGraph.beforeTask 
    task ->
        if("testB".equals(task.name)) 
            println(" before  testB ")
        


project.gradle.taskGraph.whenReady 
    graph ->
       println("task graph ready")


project.gradle.taskGraph.afterTask 
    task ->
        if("testB".equals(task.name)) 
            println(" afterTask  testB ")
        


afterEvaluate 
    println(" afterEvaluate")


beforeEvaluate 
    println(" beforeEvaluate")


最后执行下testB 看下结果

如图所示的。看到监听在不同的阶段产生了效果

3.2 修改或者增加task来插入自定义操作到整个运行过程

  1. 修改task
    dofirt dolast可以不断往上附加,多次设置dolast,都可以执行,
    如果原来的task有一个dolast ,可以在后面追求一个dolast的闭包。两者是累积的关系,不会替换。这样的话就可以执行自己想要个的操作。
    这里以一个实际有效的例子来说明,在编译android项目的过程中经常需要打包成一个jar,又需要将操作嵌入到正常的编译链之后,build会生成一系列的文件,这个文件在assemble task执行后一定会执行,我们可以这样进行操作
    assemble.doLast 
    String cmd = "jar -cMf $buildDir/aaa.jar $buildDir/intermediates/classes/a360/debug"
    def cmdResult = cmd.execute().text.trim()
    println "cmdResult: " + cmdResult
    

这样做了之后 每次build都可以自动生成一个jar

  1. 增加task
    task的顺序有两种,一个依赖,一个定义顺序,这两个有啥区别?
    举个例子
    规定task A 依赖于 taskB 当执行taskA的时候一定会执行 taskB,再执行taskA
    //这样可以定义依赖关系
    testA.dependsOn(testB)
    
    规定taskB 在taskA的前面执行,单独执行taskA时,taskB不会执行,除非由于其他的任务导致taskB执行了,在执行的时候才会让taskB运行在taskA的前面
    testA.mustRunAfter(testB)
    
    有了上面的依赖方式,则可以增加一个task,通过依赖的方式嵌入到前面的提到的任务执行的有向无环图中,任务自然就可以执行了。

推荐官网关于task 操作,大家可以看看
因为实际的开发中用的比较多,额外又记录一些作为补充

android中的gradle(我已经不单纯了)

1.android编译过程

如果不理解android的编译过程,你说想写好自动化构建的脚步,,那就有点幽默了。这个最底层的,我们需要完全的理解之后才能各种操作变化。不想自己画图,在网上扣了个图

2.对应的工具

名称---------------功能介绍在操作系统中的路径
aaptAndroid资源打包工具$ANDROID_SDK_HOME/platform-tools/appt或E:\\Android\\sdk\\build-tools\\23.0.3\\appt
aidlAndroid接口描述语言转化为.java文件的工具$ANDROID_SDK_HOME/platform-tools/aidl或E:\\Android\\sdk\\build-tools\\23.0.3\\aidl
javacJavaCompiler
dex转化.class文件为DavikVM能识别的.dex文件
apkbuilder生成apk包$ANDROID_SDK_HOME/tools/opkbuilder
jarsigner.jar文件的签名工具$JDK_HOME/jarsigner或/usr/bin/jarsigner
zipalign字节码对齐工具$ANDROID_SDK_HOME/tools/zipalign
proguard混淆工具Android\\Sdk\\tools

3. 对应的gradle过程

创建了一个最基本的android项目 随便执行了一句gradlew.bat build 。 emmm… 58个task。我都没法截图了。
执行gradlew.bat tasks --all可以查看所有的任务。这里就跳过了,如果有特殊的需求再来找这些。同时
也可以通过 gradlew.bat sourceSets看到所有默认的资源路径配置

//部分的结果
main
----
Compile configuration: compile
build.gradle name: android.sourceSets.main
Java sources: [app\\src\\main\\java]
Manifest file: app\\src\\main\\AndroidManifest.xml
Android resources: [app\\src\\main\\res]
Assets: [app\\src\\main\\assets]
AIDL sources: [app\\src\\main\\aidl]
RenderScript sources: [app\\src\\main\\rs]
JNI sources: [app\\src\\main\\jni]
JNI libraries: [app\\src\\main\\jniLibs]
Java-style resources: [app\\src\\main\\resources]

4. android特有的一些类,查看技巧和使用

每次在写android的buid.gralde我都苦恼于 这些标签一定不能瞎写,肯定有地方规定了什么能够写,什么不能够写。那么这些规定在哪里可以看到?

最上层的android标签里面能够写那些你知道?
一般的写法如下

android 
    compileSdkVersion 27
    defaultConfig 
        applicationId "com.example.a01377123.myapplication"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
    buildTypes 
        release 
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        
    

但是还有哪些呢? 这个时候就可以查看 AppExtension.java ,它以及他的父类的里面出来的函数都可以写在
android这个括号里面的。因为这个类不好直接搜索,我们可以在android studio中的 build.gradle点击一个标签buildTypes可以跳转到BaseExtension.java截个图

/**
 * Base extension for all Android plugins.
 *
 * <p>You don't use this plugin directly. Instead, use one of the following:
 *
 * <ul>
 *   <li>@link AppExtension: outputs the @code com.android.application plugin you use to create
 *       an Android app module.
 *   <li>@link LibraryExtension: outputs the @code com.android.library plugin you use to <a
 *       href="https://developer.android.com/studio/projects/android-library.html">create an Android
 *       library module</a>.
 *   <li>@link TestExtension: outputs the @code com.android.test plugin you use to create an
 *       Android test module.
 *   <li>@link FeatureExtension: outputs the @code com.android.feature plugin you use to create
 *       a feature module for your <a href="https://d.android.com/instant-apps">Android Instant
 *       Apps</a>.
 * </ul>

最上面的注释这些话很重要。可以看到 com.android.applicaiton对应 AppExtension

其函数为,emm…还是太长了,基本上可以囊括编译过程中的各种工具的配置,比如aapt,javac,jarsign等。跳过,如果感兴趣就可以看到,知道这个标签下面哪些能够写,哪些不能够写。

收集了一些常用的做整理。可以方便使用android下的gradle

标签作用对应的类文件子标签
sourceSets制定各种源文件的目录AndroidSourceSet.java实例一
buildTypes编译的类型BuildType.java
productFlavorsProductFlavor.java

…我又犯懒了 ,后面还有很多都是类似的道理,查看源文件就知道它的功能边界在哪里

实例一

sourceSets 
        main 
            java.srcDirs('src/test1/java')  //定义java 源代码
            res.srcDirs('src/test1/res')    //定义资源目录(layout , drawable,values)
        
    

最后安利一个用来处理打包过程密码的问题
https://github.com/etiennestuder/gradle-credentials-plugin

//配置
configurations 
    baiduDebugImplementation
    baiduReleaseImplementation
    a360DebugImplementation
    a360ReleaseImplementation

baiduDebugImplementation 'com.facebook.stetho:stetho:1.3.1'

//可视化查看task依赖的过程
https://github.com/dorongold/gradle-task-tree

//通盘了解gradle
https://docs.gradle.org/current/userguide/userguide.html

//关于编写过程中各种语言语法的api的介绍
https://docs.gradle.org/current/dsl/

//旧的android pulgin用户指导
http://tools.android.com/tech-docs/new-build-system/user-guide

以上是关于Gradle工作原理全面了解的主要内容,如果未能解决你的问题,请参考以下文章

优秀的Java程序员必须了解GC的工作原理

我所了解的搜索引擎工作原理

玩转Gradle ,动不动就Build Running,这玩意到底在干嘛?

程序员都应该知道的URI,一文帮你全面了解

玩转Gradle ,动不动就Build Running,这玩意到底在干嘛?

带你全面了解 OAuth2.0