gradle

Posted 0号凯迪

tags:

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

Gradle作为一种很方便的构建工具,可以解决:

  1. 1.     app涉及很多开发者时手动操作带来混乱的问题.

  2. 2.     Library工程我们需要编译成jar,gradle可以自动编译成jar.

理解构建:

最最简单的构建工具就是make了。make就是根据Makefile文件中写的规则,执行对应的命令,然后得到目标产物。

Gradle爆红之前,常用的构建工具是ANT,然后又进化到MavenANTMaven这两个工具其实也还算方便,现在还有很多地方在使用。但是二者都有一些缺点,所以让更懒得人觉得不是那么方便。比如,Maven编译规则是用XML来编写的。XML虽然通俗易懂,但是很难在xml中描述if{某条件成立,编译某文件}/else{编译其他文件}这样有不同条件的任务。

Gradle选择了GroovyGroovy基于Java并拓展了Java Java程序员可以无缝切换到使用Groovy开发程序。Groovy说白了就是把写Java程序变得像写脚本一样简单。写完就可以执行,Groovy内部会将其编译成Java class然后启动虚拟机来执行。当然,这些底层的渣活不需要你管。

Gradle另外一个特点就是它是一种DSL,即Domain Specific Language,领域相关语言。

一句行话可以包含很多意思,而且在这个行当里的人一听就懂,不用解释。另外,基于行话,我们甚至可以建立一个模板,使用者只要往这个模板里填必须要填的内容,Gradle就可以非常漂亮得完成工作,得到想要的东西。

Groovy介绍

Groovy是一种动态语言。这种语言比较有特点,它和Java一样,也运行于Java虚拟机中。恩??对头,简单粗暴点儿看,你可以认为Groovy扩展了Java语言。比如,Groovy对自己的定义就是:Groovy是在 java平台上的、具有像Python Ruby Smalltalk 语言特性的灵活动态语言, Groovy保证了这些特性像 Java语法一样被 Java开发者使用。

除了语言和Java相通外,Groovy有时候又像一种脚本语言。前文也提到过,当我执行Groovy脚本时,Groovy会先将其编译成Java类字节码,然后通过Jvm来执行这个Java.

 实际上,由于Groovy Code在真正执行的时候已经变成了Java字节码,所以JVM根本不知道自己运行的是Groovy代码


3.6  更多

作为一门语言,Groovy是复杂的,是需要深入学习和钻研的。一本厚书甚至都无法描述Groovy的方方面面。

Anyway,从使用角度看,尤其是又限定在Gradle这个领域内,能用到的都是Groovy中一些简单的知识。

Gradle介绍

现在正式进入GradleGradle是一个工具,同时它也是一个编程框架。前面也提到过,使用这个工具可以完成app的编译打包等工作。当然你也可以用它干其他的事情。

Gradle是什么?学习它到什么地步就可以了?

=====>看待问题的时候,所站的角度非常重要。

-->当你把Gradle当工具看的时候,我们只想着如何用好它。会写、写好配置脚本就OK

-->当你把它当做编程框架看的时候,你可能需要学习很多更深入的内容。

另外,今天我们把它当工具看,明天因为需求发生变化,我们可能又得把它当编程框架看。

Gradle开发环境部署

Gradle的官网:http://gradle.org/

文档位置:https://docs.gradle.org/current/release-notes。其中的User GuideDSL Reference很关键。User Guide就是介绍Gradle的一本书,而DSL ReferenceGradleAPI的说明。

Ubuntu为例,下载Gradlehttp://gradle.org/gradle-download/  选择Complete distributionBinary only distribution都行。然后解压到指定目录。

最后,设置~/.bashrc,把Gradle加到PATH里,如图20所示:

执行source ~/.bashrc,初始化环境

执行gradle --version,如果成功运行就OK

注意,为什么说Gradle是一个编程框架?来看它提供的API文档:

https://docs.gradle.org/current/javadoc/org/gradle/api/Project.html

其实gradle就是一个编程框架.写程序就是用gradleapi

从网上学习到的资料来看,几乎全是从脚本的角度来介绍Gradle,结果学习一通下来,只记住参数怎么配置,却不知道它们都是函数调用,都是严格对应相关API的。

掌握大体流程,然后根据SDK+API来完成的

基本组件

Gradle是一个框架,它定义一套自己的游戏规则。我们要玩转Gradle,必须要遵守它设计的规则。

Gradle中,每一个待编译的工程都叫一个Project。每一个Project在构建的时候都包含一系列的Task。比如一个android APK的编译可能包含:Java源码编译Task、资源编译TaskJNI编译Tasklint检查Task、打包生成APKTask、签名Task

一个Project到底包含多少个Task,其实是由编译脚本指定的插件决定。插件是什么呢?插件就是用来定义Task,并具体执行这些Task的东西。

Gradle是一个框架,作为框架,它负责定义流程和规则。而具体的编译工作则是通过插件的方式来完成的。

在例子中

·        CPosDeviceSdkCPosSystemSdkCPosSystemSdkxxxImplAndroid Library其中,CPosSystemSdkxxxImpl依赖CPosSystemSdk

·        CPosDeviceServerApkCPosSdkDemoAndroid APP。这些AppSDK有依赖关系。CPosDeviceServerApk依赖CPosDeviceSdk,而CPosSdkDemo依赖所有的Sdk Library

每一个Library和每一个App都是单独的Project。根据Gradle的要求,每一个Project在其根目录下都需要有一个build.gradlebuild.gradle文件就是该Project的编译脚本,类似于Makefile

posdevice虽然包含5个独立的Project,但是要独立编译他们的话,得:

1.    cd  某个Project的目录。比如 cd CPosDeviceSdk

2.    然后执行 gradle  xxxxxxx是任务的名字。对Android来说,assemble这个Task会生成最终的产物,所以gradle assemble

所谓的独立Project其实有依赖关系的。

posdevice改造成支持GradleMulti-ProjectsBuild很容易,需要:

·        posdevice下也添加一个build.gradle。这个build.gradle一般干得活是:配置其他子Project的。比如为子Project添加一些属性。这个build.gradle有没有都无所属。

·        posdevice下添加一个名为settings.gradle。这个文件很重要,名字必须是settings.gradle。它里边用来告诉Gradle,这个multiprojects包含多少个子Project

·        settings.gradle的内容,最关键的内容就是告诉Gradle这个multiprojects包含哪些子projects:

·        [settings.gradle]

·         //通过include函数,将子Project的名字(其文件夹名)包含进来  
·         include  'CPosSystemSdk','CPosDeviceSdk',  
·                'CPosSdkDemo','CPosDeviceServerApk','CPosSystemSdkWizarPosImpl'

如果你确实只有一个Project需要编译,我也建议你在目录下添加一个settings.gradle。改得方法就是添加settings.gradle,然后include对应的project名字。

settings.gradle除了可以include外,还可以设置一些函数。这些函数会在gradle构建整个工程任务的时候执行,所以,可以在settings做一些初始化的工作

//定义一个名为initMinshengGradleEnvironment的函数。该函数内部完成一些初始化操作
//比如创建特定的目录,设置特定的参数等
definitMinshengGradleEnvironment(){  
    println"initialize Minsheng Gradle Environment ....."  
    ......//干一些special的私活....  
    println"initialize Minsheng Gradle Environment completes..."  
}  
//settings.gradle加载的时候,会执行initMinshengGradleEnvironment  
initMinshengGradleEnvironment()  
//include也是一个函数:  
include 'CPosSystemSdk','CPosDeviceSdk',  
      'CPosSdkDemo','CPosDeviceServerApk','CPosSystemSdkWizarPosImpl'

 gradle命令介绍

1.  gradle projects查看工程信息

到目前为止,我们了解了Gradle什么呢?

·        每一个Project都必须设置一个build.gradle文件。至于其内容,我们留到后面再说

·        对于multi-projects build,需要在根目录下也放一个build.gradle,和一个settings.gradle

·        一个Project是由若干tasks来组成的,当gradle xxx的时候,实际上是要求gradle执行xxx任务。这个任务就能完成具体的工作

·        当然,具体的工作和不同的插件有关系。编译Java要使用Java插件,编译Android APP需要使用Android APP插件。这些我们都留待后续讨论

如果你修改settings.gradle,使得include只有一个参数,则gradle projects的子project也会变少

  gradle tasks查看任务信息

查看了Project信息,这个还比较简单,直接看settings.gradle也知道。那么Project包含哪些Task信息,怎么看呢?图23,24中最后的输出也告诉你了,想看某个Project包含哪些Task信息,只要执行:

gradle project-path:tasks  就行。注意,project-path是目录名,后面必须跟冒号。

对于Multi-project,在根目录中,需要指定你想看哪个poject的任务。不过你要是已经cd到某个Project的目录了,则不需指定Project-path

·        cd CPossystemSdk

·        gradle tasks 得到同样的结果

CPosSystemSdk是一个Android Library工程,Android Library对应的插件定义了好多Task。每种插件定义的Task都不尽相同,这就是所谓的Domain Specific,需要我们对相关领域有比较多的了解。

radle task-name执行任务

25中列出了好多任务,这时候就可以通过 gradle 任务名来执行某个任务。这和make xxx很像。比如:

·        gradle clean是执行清理任务,和make clean类似。

·        gradle properites用来查看所有属性信息。

gradle tasks会列出每个任务的描述,通过描述,我们大概能知道这些任务是干什么的.....。然后gradle task-name执行它就好。

这里要强调一点:TaskTask之间往往是有关系的,这就是所谓的依赖关系。比如,assemble task就依赖其他task先执行,assemble才能完成最终的输出

如果知道Task之间的依赖关系,那么开发者就可以添加一些定制化的Task。比如我为assemble添加一个SpecialTest任务,并指定assemble依赖于SpecialTest。当assemble执行的时候,就会先处理完它依赖的task。自然,SpecialTest就会得到执行了...

Gradle工作流程

Gradle的工作流程其实蛮简单,用一个图26来表达:

 26告诉我们,Gradle工作包含三个阶段:

·        首先是初始化阶段。对我们前面的multi-project build而言,就是执行settings.gradle

·        Initiliazation phase的下一个阶段是Configration阶段。

·        Configration阶段的目标是解析每个project中的build.gradle。比如multi-project build例子中,解析每个子目录中的build.gradle。在这两个阶段之间,我们可以加一些定制化的Hook。这当然是通过API来添加的。

·        Configuration阶段完了后,整个buildproject以及内部的Task关系就确定了。恩?前面说过,一个Project包含很多Task,每个Task之间有依赖关系。Configuration会建立一个有向图来描述Task之间的依赖关系。所以,我们可以添加一个HOOK,即当Task关系图建立好后,执行一些操作。

·        最后一个阶段就是执行任务了。当然,任务执行完后,我们还可以加Hook

 我在:

·        settings.gradle加了一个输出。

·        posdevicebuild.gradle加了图25中的beforeProject函数。

·        CPosSystemSdk加了taskGraph whenReady函数和buidFinished函数。

  • Gradle有一个初始化流程,这个时候settings.gradle会执行。

  • 在配置阶段,每个Project都会被解析,其内部的任务也会被添加到一个有向图里,用于解决执行过程中的依赖关系。

  • 然后才是执行阶段。你在gradle xxx中指定什么任务,gradle就会将这个xxx任务链上的所有任务全部按依赖顺序执行一遍!

Gradle编程模型及API实例详解

Gradle主要有三种对象,这三种对象和三种不同的脚本文件对应,在gradle执行的时候,会将脚本转换成对应的对端

·        Gradle对象:当我们执行gradle xxx或者什么的时候,gradle会从默认的配置脚本中构造出一个Gradle对象。在整个执行过程中,只有这么一个对象。Gradle对象的数据类型就是Gradle。我们一般很少去定制这个默认的配置脚本。

·        Project对象:每一个build.gradle会转换成一个Project对象。

·        Settings对象:显然,每一个settings.gradle都会转换成一个Settings对象。

对于其他gradle文件,除非定义了class,否则会转换成一个实现了Script接口的对象当我们执行gradle的时候,gradle首先是按顺序解析各个gradle文件。这里边就有所所谓的生命周期的问题,即先解析谁,后解析谁。


   ndk-build命令,用于编译ndk。关于Exec类型的Task,请自行脑补GradleAPI 
*/  
//注意此处创建task的方法,是直接{}喔,那么它后面的tasks.withType(JavaCompile)  
//设置的依赖关系,还有意义吗?Think!如果你能想明白,gradle掌握也就差不多了  
task buildNative(type: Exec, description:'CompileJNI source via NDK'){  
       if(project.gradle.ndkDir == null)//看看有没有指定ndk.dir路径  
          println "CANNOT Build NDK"  
       else{  
            commandLine "/${project.gradle.ndkDir}/ndk-build",  
               '-C',file('jni').absolutePath,  
               '-j', Runtime.runtime.availableProcessors(),  
               'all','NDK_DEBUG=0'  
        }  
  }  
 tasks.withType(JavaCompile){  
       compileTask -> compileTask.dependsOn buildNative  
  }  
  ......    
 //对于APK,除了拷贝APK文件到指定目录外,我还特意为它们加上了自动版本命名的功能  
 tasks.getByName("assemble"){  
       it.doLast{  
       println "$project.name: After assemble, jar libs are copied tolocal repository"  
       project.ext.versionName = android.defaultConfig.versionName  
       println "\t versionName = $versionName"  
       copyOutput(false)  
     }  
}  

结果展示

posdevice下执行gradle assemble命令,最终的输出文件都会拷贝到我指定的目录,结果如图38所示:

 38所示为posdevice gradle assemble的执行结果:

·        library包都编译release版的,copyxxx/javaLib目录下

·        apk编译debugrelease-unsigned版的,copyapps目录下

·        所有产出物都自动从AndroidManifest.xml中提取versionName

--我会把它改造成支持Multi-Projects Build的样子。即在工程目录下放一个settings.build

它有三个版本,分别是debugreleasedemo。这三个版本对应的代码都完全一样,但是在运行的时候需要从assets/runtime_config文件中读取参数。参数不同,则运行的时候会跳转到debugrelease或者demo的逻辑上。

在编译buildreleasedemo版本前,在build.gradle中自动设置runtime_config的内容。代码如下所示:

[build.gradle]

apply plugin:'com.android.application'  //加载APP插件  
//加载utils.gradle  
apply from:rootProject.getRootDir().getAbsolutePath()+"/utils.gradle"  
//buildscript设置android app插件的位置  
buildscript {  
   repositories {jcenter()}  
   dependencies { classpath 'com.android.tools.build:gradle:1.2.3'}  
}  
//androidScriptBlock  
android {  
   compileSdkVersion gradle.api  
   buildToolsVersion "22.0.1"  
   sourceSets{//源码设置SB  
        main{  
           manifest.srcFile 'AndroidManifest.xml'  
           jni.srcDirs =[]  
           jniLibs.srcDir 'libs'  
           aidl.srcDirs=['src']  
           java.srcDirs=['src']  
           res.srcDirs=['res']  
           assets.srcDirs =['assets']//多了一个assets目录  
        }  
    }  
   signingConfigs {//签名设置  
       debug {  //debug对应的SB。注意  
           if(project.gradle.debugKeystore != null){  
               storeFile file("file://${project.gradle.debugKeystore}")  
               storePassword "android"  
               keyAlias "androiddebugkey"  
               keyPassword "android"  
           }  
        }  
    }  
    /* 
     最关键的内容来了: buildTypesScriptBlock. 
     buildTypes和上面的signingConfigs,当我们在build.gradle中通过{}配置它的时候, 
     其背后的所代表的对象是NamedDomainObjectContainer<BuildType>  
     NamedDomainObjectContainer<SigningConfig> 
     注意,NamedDomainObjectContainer<BuildType/或者SigningConfig>是一种容器, 
     容器的元素是BuildType或者SigningConfig。我们在debug{}要填充BuildType或者 
    SigningConfig所包的元素,比如storePassword就是SigningConfig类的成员。而proguardFile 
    BuildType的成员。 
    那么,为什么要使用NamedDomainObjectContainer这种数据结构呢?因为往这种容器里 
    添加元素可以采用这样的方法:比如signingConfig为例 
    signingConfig{//这是一个NamedDomainObjectContainer<SigningConfig> 
       test1{//新建一个名为test1SigningConfig元素,然后添加到容器里 
         //在这个花括号中设置SigningConfig的成员变量的值 
       } 
      test2{//新建一个名为test2SigningConfig元素,然后添加到容器里 
         //在这个花括号中设置SigningConfig的成员变量的值 
      } 
    } 
    buildTypes中,Android默认为这几个NamedDomainObjectContainer添加了 
    debugrelease对应的对象。如果我们再添加别的名字的东西,那么gradleassemble的时候 
    也会编译这个名字的apk出来。比如,我添加一个名为testbuildTypes,那么gradle assemble 
    就会编译一个xxx-test-yy.apk。在此,test就好像debugrelease一样。 
   */  
   buildTypes{  
        debug{//修改debugsigningConfigsigningConfig.debug配置  
           signingConfig signingConfigs.debug  
        }  
        demo{//demo版需要混淆  
           proguardFile 'proguard-project.txt'  
           signingConfig signingConfigs.debug  
        }  
       //release版没有设置,所以默认没有签名,没有混淆  
    }  
      ......//其他和posdevice 类似的处理。来看如何动态生成runtime_config文件  
   def  runtime_config_file ='assets/runtime_config'  
   /* 
   我们在gradle解析完整个任务之后,找到对应的Task,然后在里边添加一个doFirst Action 
   这样能确保编译开始的时候,我们就把runtime_config文件准备好了。 
   注意,必须在afterEvaluate里边才能做,否则gradle没有建立完任务有向图,你是找不到 
   什么preDebugBuild之类的任务的 
   */  
   project.afterEvaluate{  
      //找到preDebugBuild任务,然后添加一个Action   
      tasks.getByName("preDebugBuild"){  
           it.doFirst{  
               println "generate debug configuration for ${project.name}"  
               def configFile =newFile(runtime_config_file)  
               configFile.withOutputStream{os->  
                   os << I am Debug\n'  //往配置文件里写 I am Debug  
                }  
           }  
        }  
       //找到preReleaseBuild任务  
       tasks.getByName("preReleaseBuild"){  
           it.doFirst{  
               println "generate release configuration for ${project.name}"  
               def configFile =newFile(runtime_config_file)  
               configFile.withOutputStream{os->  
                   os << I am release\n'  
               }  
           }  
        }  
       //找到preDemoBuild。这个任务明显是因为我们在buildType里添加了一个demo的元素  
      //所以Android APP插件自动为我们生成的  
       tasks.getByName("preDemoBuild"){  
           it.doFirst{  
               println "generate offlinedemo configuration for${project.name}"  
               def configFile =newFile(runtime_config_file)  
               configFile.withOutputStream{os->  
                   os << I am Demo\n'  
               }  
            }  
        }  
    }  
}  
 .....//copyOutput  

 

最终的结果如图39所示:

为什么我知道有preXXXBuild这样的任务?

答案:gradle tasks --all查看所有任务。然后,多尝试几次,直到成功

·        其是Groovy语法,虽然看了下快速教材,但总感觉一到gradle就看不懂。主要问题还是闭包,比如Groovy那一节写得文件拷贝的例子中的withOutputStream,还有gradle中的withType,都是些啥玩意啊?

·        所以后来下决心先把Groovy学会,主要是把自己暴露在闭包里边。另外,Groovy是一门语言,总得有SDK说明吧。写了几个例子,慢慢体会到Groovy的好处,也熟悉Groovy的语法了。

·        接着开始看GradleGradle有几本书,我看过Gradle in Action。说实话,看得非常痛苦。现在想起来,Gradle其实比较简单,知道它的生命周期,知道它怎么解析脚本,知道它的API,几乎很快就能干活。而Gradle In Action一上来就很细,而且没有从API角度介绍。说个很有趣的事情,书中有个类似下面的例子

taskmyTask  <<  {

   println ' I am myTask'

}

书中说,如果代码没有加<<,则这个任务在脚本initialization(也就是你无论执行什么任务,这个任务都会被执行,I am myTask都会被输出)的时候执行,如果加了<<,则在gradle myTask后才执行。

不要这么阐释问题.理解的话直接说为什么.然后就可以了.

这和我们调用task这个函数的方式有关!如果没有<<,则闭包在task函数返回前会执行,而如果加了<<,则变成调用myTask.doLast添加一个Action了,自然它会等到grdle myTask的时候才会执行

哈哈,差不多把人家所有的东西都搬过来了,写的确实好.感恩.

 


以上是关于gradle的主要内容,如果未能解决你的问题,请参考以下文章

如何使用模块化代码片段中的LeakCanary检测内存泄漏?

如何添加jcenter仓库 gradle依赖

如何防止Android studio格式化build.gradle

谷歌地图不显示在片段中

Gradle 导出属性不适用于 Spring 中的 gradle bootRun

错误:这个片段内部类应该是静态的 [ValidFragment]