Gradle基础

Posted ljt2724960661

tags:

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

            Gradle是以Groovy语言为基础,面向Java应用为主。基于DSL(Domain Specific Language)语法的自动化构建工具。它集合了Ant的灵活性和强大功能,同时也集合了Maven的依赖管理和约定,从而创造了一个更有效的构建方式,它提供了一个可声明的方式,并在合理默认值的基础上描述所有类型的构建,Gradle目前已被选作许多开源项目的构建系统。

Gradle特点:1 基于jvm,故需要java环境 2 基于协议,即build.gradle中的配置声明等。3可扩展,如自定义task及action,自定义插件等。4可视化,构建的阶段及task等都可追溯,查看耗时及执行情况等。

   Android项目中Gradle文件理解

  settings文件:对于一个只包含一个android应用的新项目来说,settings.gradle应该是这样的:

include ':app'
rootProject.name = "My Application"

 settings文件在初始化阶段被执行,并且定义了哪些模块应该包含在构建内。在这背后,Gradle会为每个settings文件创建一个Settings对象,并调用该对象的相关方法,如下:

项目下build.gradle文件

buildscript 
repositories 
jcenter() 
dependencies 
classpath "com.android.tools.build:gradle:3.5.3"



allprojects 
repositories 
jcenter()
	
	

buildscript ... ,配置了编译时的代码驱动. 实际构建配置在buildscript代码块内,repositories代码块将JCenter配置成一个仓库,在这种情况下,一个仓库意味着一系列的依赖包,或者说在我们应用和依赖项目中可使用的一系列可下载的函数库。JCenter是一个很有名的Maven库。dependencies代码块用于配置构建过程中的依赖包。这也意味着你不能将你的应用或依赖项目所需要的依赖包包含在顶层构建文件中。默认情况下,唯一被定义的依赖包是Gradle的Android插件。每个Android模块都需要有Android插件,因为该插件可使其执行Android相关的任务。插件用于扩展Gradle构建脚本的能力。在一个项目中应用一个插件,该项目就可以使用该插件预定义的一些属性和任务。allprojects代码块可用来声明那些需要被用于所有模块的属性。你甚至可以在allprojects中创建任务,这些任务最终会被运用到所有模块。

模块级构建文件

模块级 build.gradle 文件位于每个 project/module/ 目录下,用于为其所在的特定模块配置构建设置。您可以通过配置这些构建设置提供自定义打包选项(如额外的构建类型和产品变种),以及替换 main/ 应用清单或顶层 build.gradle 文件中的设置。

三种依赖类型

dependencies 
    // Dependency on a local library module
    implementation project(":mylibrary")

    // Dependency on local binaries
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // Dependency on a remote binary
    implementation 'com.example.android:app-magic:12.3'

gradle.properties

您可以在其中配置项目全局 Gradle 设置,如 Gradle 守护程序的最大堆大小。

local.properties

为构建系统配置本地环境属性,其中包括:

1 ndk.dir - NDK 的路径。需要NDK环境时配置; 2 sdk.dir - SDK 的路径。

Gradle Wrapper

Gradle是一个不断发展的工具,新版本可能会打破向后兼容性,而使用Gradle Wrapper可以避免这个问题,并能确保构建是可重复的。Gradle Wrapper为微软的Windows 操作系统提供了一个batch 文件,为其他操作系统提供了一个shell脚本。当你运行这段脚本时,需要的Gradle版本会被自动下载(如果它还不存在)和使用。其原理是,每个需要构建应用的开发者或自构建系统可以仅仅运行Wrapper,然后由Wrapper搞定剩余部分。简单将,它是用来下载Gradle SDK的工具。

Gradle语法

        Gradle有约定优于配置的原则,即为设置和属性提供默认值。Groovy是一种基于Java虚拟机的动态语言。

项目和任务

         在Gradle中,最重要的两个概念是项目和任务。每一次构建都包括至少一个项目,每一个项目又包括一个或多个任务。每个build.gradle文件都代表着一个项目,任务定义在构建脚本里。当初始化构建过程时,Gradle会基于build文件组装项目和任务对象。一个任务对象包含一系列动作对象,这些动作对象之后会按顺序执行。一个单独的动作对象就是一个待执行的代码块,它和Java中的方法类似。

          Project 代表一个构建组件,在「构建-初始化阶段」,Gradle 引擎会为每个 build.gradle 脚本文件实例化一个 Project 实例。

Task 任务:一个 Project 由一个或多个 Task 组成,Task 代表一个可执行的任务。

 注: 动作(action)就是 Task 中的构建逻辑,这些构建逻辑在「构建-执行阶段」运行。Task 接口提供了两个方法来声明 Task 任务:

doFirst(Closure):表示 Task 的第一个动作;doLast(Closure):表示 Task 的最后一个动作;

 Closure:闭包,类似Java里的匿名函数;它最终会转化成一个对象:

构建生命周期

     执行一个Gradle构建的最简单的形式是,只执行任务中的动作,而这些任务又依赖于其他任务。为了简化构建过程,构建工具会新建一个动态的模型流,叫作Directed Acyclic Graph(DAG)。简单讲就是”有向无环图”,这意味着所有的任务都会被一个接一个地执行,循环是不可能的。一旦一个任务被执行,其就不会被再次执行。那些没有依赖的任务通常会被优先执行。在构建的配置阶段会生成依赖关系图。一个Gradle的构建通常有如下三个阶段。

初始化:项目实例会在该阶段被创建。如果一个项目有多个模块,并且每一个模块都有其对应的build.gradle文件,那么就会创建多个项目实例。

 配置:配置阶段的任务是执行各项目下的build.gradle脚本,完成Project的配置,并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task。每个build.gralde脚本文件对应一个Project对象,在初始化阶段创建。配置阶段执行的代码包括build.gralde中的各种语句、闭包以及Task中的配置段语句。在配置阶段结束后,Gradle会根据任务Task的依赖关系创建一个有向无环图。  

 执行:在该阶段,Gradle将决定哪个任务会被执行。通过Gradle对象的getTaskGraph方法访问,对应的类为TaskExecutionGraph,然后通过调用gradle <任务名>执行对应任务。

Setting.gradle

 输出:

settings.gradle parse over
init  end
> Configure project :app
The current default is 'false'.
config hello task
config over
> Task :app:clean
> Task :app:preBuild UP-TO-DATE
> Task :app:preDebugBuild UP-TO-DATE
> Task :app:compileDebugAidl NO-SOURCE
> Task :app:mergeExtDexDebug
> Task :app:mergeProjectDexDebug
> Task :app:packageDebug
> Task :app:assembleDebug
build over

语法实战

---->> 1 gradlew -v

Welcome to Gradle 6.5!

Here are the highlights of this release:
 - Experimental file-system watching
 - Improved version ordering
 - New samples
For more details see https://docs.gradle.org/6.5/release-notes.html
------------------------------------------------------------
Gradle 6.5
------------------------------------------------------------

Build time:   2020-06-02 20:46:21 UTC
Revision:     a27f41e4ae5e8a41ab9b19f8dd6d86d7b384dad4

Kotlin:       1.3.72
Groovy:       2.5.11
Ant:          Apache Ant(TM) version 1.10.7 compiled on September 1 2019
JVM:          1.8.0_291 (Oracle Corporation 25.291-b10)
OS:           Windows 10 10.0 amd64

---->> 2 Task

def ADemoTask = project.task("ADemoTask",group: "android")
ADemoTask.doFirst( println("2") )
ADemoTask.doFirst( println("1") )
ADemoTask.doLast( println("3") )
ADemoTask.doLast( println("4") )

> Configure project :app
WARNING: The option setting 'android.nonTransitiveRClass=true' is experimental.
The current default is 'false'.
> Task :app:ADemoTask
1
2
3
4
---->> 3
task playgradle 
    doLast 
        println 'hello world!'
    


gradlew -q playgradle
hello world!

---->> 4

4.1 任务依赖

task taskA 
    doLast 
        println "taskA run"
    


task taskB 
    doLast 
        println "taskB run"
    


task taskC(dependsOn: [taskA, taskB]) 
    doLast 
        println "taskC run"
    



taskA run
taskB run
taskC run

4.2 task与lib之间的依赖

task lib1 
    println 'lib1'

task lib2 
    println 'lib2'

task lib3 
    println 'lib3'


 task taskA 
    dependsOn this.tasks.findAll  task ->
        return task.name.startsWith('lib')
    
    doLast 
        println  "taskA run"
    

---->> 5 获取所有的project

this.gradle.buildFinished 
    println "--->>> end init"
    getProjects()


//获取所有的Project
def getProjects()
    this.getAllprojects().eachWithIndex  Project project ,int index ->
        if(index == 0)
            println "Root Project :$project.name"
        else
            println "+--- Project :$project.name"
        
    


输出:
--->>> end init
Root Project :TestGroovy
+--- Project :app

获取指定名字的Project
project('app') 
    println " 77777 --- Project :$project.name"

--->> 6  配置全局属性

6.1

项目下的gradle

  原: targetSdk 32
  改:
    项目下的gradle :
	
    ext 
        targetSdkVersion = 32
    

   app下的gradle:
     defaultConfig 
	   targetSdk  rootProject.ext.targetSdkVersion
	 

6.2 在gradle.properties中定义全局扩展属性

原:
  versionName "1.0"
  
  改:
  versionName  version_custom
  
  gradle.properties  
  version_custom = "1.0"

7 文件相关的操作

7.1
  
  项目下的gradle: 
  this.gradle.buildFinished 
    println "--->>> end init"
    getFilePath()
  
  
  def getFilePath() 
    println "  --- Project"+getRootDir().absolutePath
    println " --- Project"+getBuildDir().absolutePath
    println " --- Project"+getProjectDir().absolutePath


  --->>> end init
 --- ProjectD:\\AndroidWorkSpace\\TestGroovy
 --- ProjectD:\\AndroidWorkSpace\\TestGroovy\\build
 --- ProjectD:\\AndroidWorkSpace\\TestGroovy
7.2 获取项目下gradle内容
 
  项目下的gradle: 
  
  this.gradle.buildFinished 
   getContent('build.gradle')
 
   
   def  getContent(String path) 
    try 
        def file = file(path)
        println "--->>> end path $file.path"
        println "--->>> end 222 path \\n $file.text"
        return file.text
     catch(GradleException e) 
        println 'file not found'
    
    return null


  注  \\n 换行
  7.2.2 获取App下gradle内容
    getContent('/app/build.gradle')
8 多渠道打包:
	 this.afterEvaluate 
        this.android.applicationVariants.all  variant ->
            //获取Variants相关属性
            def name = variant.name
            def baseName = variant.baseName
            def buildType = variant.buildType
            def signingConfig = variant. signingConfig
            def versionName = variant.versionName
            def versionCode = variant.versionCode
            println "name: $name , baseName: $baseName"

            //修改variant的apk名字
            def output = variant.outputs.first()
            def apkName = "app-$variant.baseName-$variant.versionName.apk"
            println "output:  , apkName: $apkName"

            //修改variant的task
            def checkTask = variant.checkManifest
            checkTask.doFirst 
            
        
    
---输出-------
	
	> Configure project :app
name: debug , baseName: debug
output:  , apkName: app-debug-"1.0".apk
name: release , baseName: release
output:  , apkName: app-release-"1.0".apk
说明: Variant是构造类型和不同的定制类型(product flavor)的组合。构造类型在Android studio中默认有debug和release;而定制类型(product flavor)在国内一般用于不同的分发渠道;

Gradle原理

 1.5.1 Configuration

Every dependency declared for a Gradle project applies to a specific scope. For example some dependencies should be used for compiling source code whereas others only need to be available at runtime. Gradle represents the scope of a dependency with the help of a Configuration. Every configuration can be identified by a unique name.

Configuration定义了依赖在编译和运行时候的不同范围, 每个Configuration都有name来区分。比如android常见的两种依赖方式implementation和api。

 依赖的识别

 gradle中使用MethodMixIn这个接口来实现methodmissing的能力。

// MethodMixIn
public interface MethodMixIn 
    MethodAccess getAdditionalMethods();


public interface MethodAccess 
    /**
     * Returns true when this object is known to have a method with the given name that accepts the given arguments.
     *
     * <p>Note that not every method is known. Some methods may require an attempt invoke it in order for them to be discovered.</p>
     */
    boolean hasMethod(String name, Object... arguments);

    /**
     * Invokes the method with the given name and arguments.
     */
    DynamicInvokeResult tryInvokeMethod(String name, Object... arguments);

可以看到这里的methodmissing主要是在找不到这个方法的时候去返回一个MethodAccess,MethodAccess中去判断是否存在以及动态执行这个method。我们看DependencyHandler的实现类DefaultDependencyHandler。这个类实现了MethodMixIn接口,返回的是一个DynamicAddDependencyMethods对象

// DefaultDependencyHandler.java
public DefaultDependencyHandler(...) 
    ...
    this.dynamicMethods = new DynamicAddDependencyMethods(configurationContainer, new DefaultDependencyHandler.DirectDependencyAdder());


public MethodAccess getAdditionalMethods() 
    return this.dynamicMethods;

返回了一个DynamicAddDependencyMethods去加以判断。

 DynamicAddDependencyMethods(ConfigurationContainer configurationContainer, DynamicAddDependencyMethods.DependencyAdder dependencyAdder) 
        this.configurationContainer = configurationContainer;
        this.dependencyAdder = dependencyAdder;
    

    public boolean hasMethod(String name, Object... arguments) 
        return arguments.length != 0 && this.configurationContainer.findByName(name) != null;
    

    public DynamicInvokeResult tryInvokeMethod(String name, Object... arguments) 
        if (arguments.length == 0) 
            return DynamicInvokeResult.notFound();
         else 
            Configuration configuration = (Configuration)this.configurationContainer.findByName(name);
            if (configuration == null) 
                return DynamicInvokeResult.notFound();
             else 
                List<?> normalizedArgs = CollectionUtils.flattenCollections(arguments);
                if (normalizedArgs.size() == 2 && normalizedArgs.get(1) instanceof Closure) 
                    return DynamicInvokeResult.found(this.dependencyAdder.add(configuration,normalizedArgs.get(0), (Closure)normalizedArgs.get(1)));
                 else if (normalizedArgs.size() == 1) 
                    return DynamicInvokeResult.found(this.dependencyAdder.add(configuration,normalizedArgs.get(0), (Closure)null));
                 else 
                    Iterator var5 = normalizedArgs.iterator();

                    while(var5.hasNext()) 
                        Object arg = var5.next();
                        this.dependencyAdder.add(configuration, arg, (Closure)null);
                    

                    return DynamicInvokeResult.found();
                
            
        
    

可以看到这个类的两个要点:

1.判断Configuration有无:通过外部传入的ConfigurationContainer来判断是否存在这个方法。这样我们可以联想到,这个ConfigurationContainer肯定是每个平台Plugin自己传入的,必须是已定义的才能使用。比如android就添加了implementation, api等。

2.执行方法:真正的执行方法会根据参数来判断,比如我们常见的一个参数的引用形式,还有一个参数+一个闭包的形式,比如:

implementation 'com.google.android.material:material:1.4.0'

当在ConfigurationContainer中找到了这个引用方式(以下都称Configuration)时,就会返回一个DynamicInvokeResult。它做了一个 add的操作,

this.dependencyAdder.add(configuration, arg, (Closure)null);
// DefaultDependencyHandler.java
    private class DirectDependencyAdder implements DependencyAdder<Dependency> 
        private DirectDependencyAdder() 
        

        public Dependency add(Configuration configuration, Object dependencyNotation, @Nullable Closure configureAction) 
            return DefaultDependencyHandler.this.doAdd(configuration, dependencyNotation, configureAction);
        
    

    private Dependency doAdd(Configuration configuration, Object dependencyNotation, Closure configureClosure) 
        if (dependencyNotation instanceof Configuration) 
            Configuration other = (Configuration)dependencyNotation;
            if (!this.configurationContainer.contains(other)) 
                throw new UnsupportedOperationException("Currently you can only declare dependencies on configurations from the same project.");
             else 
                configuration.extendsFrom(new Configuration[]other);
                return null;
            
         else 
            Dependency dependency = this.create(dependencyNotation, configureClosure);
            configuration.getDependencies().add(dependency);
            return dependency;
        
    

小结一下: 这个过程就是借助gradle的MethodMixIn接口,将所有未定义的引用方法转到getAdditionalMethods方法上来,在这个方法里面判断Configuration是否存在,如果存在的话就生成Dependency。

依赖的创建

DefaultDependencyHandler.java

public Dependency create(Object dependencyNotation, Closure configureClosure) 
        Dependency dependency = this.dependencyFactory.createDependency(dependencyNotation);
        return (Dependency)ConfigureUtil.configure(configureClosure, dependency);
    

 // DefaultDependencyFactory.java
    public Dependency createDependency(Object dependencyNotation) 
        Dependency dependency = (Dependency)this.dependencyNotationParser.parseNotation(dependencyNotation);
        this.injectServices(dependency);
        return dependency;
    

DependencyNotationParser

public class DependencyNotationParser 
    public static NotationParser<Object, Dependency> parser(Instantiator instantiator, DefaultProjectDependencyFactory dependencyFactory, ClassPathRegistry classPathRegistry, FileLookup fileLookup, RuntimeShadedJarFactory runtimeShadedJarFactory, CurrentGradleInstallation currentGradleInstallation, Interner<String> stringInterner) 
        return NotationParserBuilder.toType(Dependency.class)
        .fromCharSequence(new DependencyStringNotationConverter(instantiator, DefaultExternalModuleDependency.class, stringInterner))
        .converter(new DependencyMapNotationConverter(instantiator, DefaultExternalModuleDependency.class))
        .fromType(FileCollection.class, new DependencyFilesNotationConverter(instantiator))
        .fromType(Project.class, new DependencyProjectNotationConverter(dependencyFactory))
        .fromType(ClassPathNotation.class, new DependencyClassPathNotationConverter(instantiator, classPathRegistry, fileLookup.getFileResolver(), runtimeShadedJarFactory, currentGradleInstallation))
        .invalidNotationMessage("Comprehensive documentation on dependency notations is available in DSL reference for DependencyHandler type.").toComposite();
    

从里面我们看到了FileCollection,Project,ClassPathNotation三个类,和我们的三种三方库资源形式很对应?其实这三种资源形式的解析就是用这三个类进行的。  DependencyNotationParser就是整合了这些转换器,成为一个综合的转换器。其中DependencyFilesNotationConverter将FileCollection解析为SelfResolvingDependency,也就是implementation fileTree(include: ['*.jar'], dir: 'libs')这种形式。DependencyProjectNotationConverter将Project解析为ProjectDependency。也就是implementation project(‘:projectA’),DependencyClassPathNotationConverter将ClassPathNotation转成SelfResolvingDependency,也就是implementation ‘xxx’这种。

// DefaultDependencyHandler.java
    public Dependency project(Map<String, ?> notation) 
        return this.dependencyFactory.createProjectDependencyFromMap(this.projectFinder, notation);
    

    // DefaultDependencyFactory.java
    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder, Map<? extends String, ? extends Object> map) 
        return this.projectDependencyFactory.createFromMap(projectFinder, map);
    

    // ProjectDependencyFactory.java
    public ProjectDependency createFromMap(ProjectFinder projectFinder, Map<? extends String, ?> map) 
        return (ProjectDependency)NotationParserBuilder.toType(ProjectDependency.class).converter(new ProjectDependencyFactory.ProjectDependencyMapNotationConverter(projectFinder, this.factory)).toComposite().parseNotation(map);
    

    // ProjectDependencyMapNotationConverter.java
    protected ProjectDependency parseMap(@MapKey("path") String path, @Optional @MapKey("configuration") String configuration) 
            return this.factory.create(this.projectFinder.getProject(path), configuration);
    

    // DefaultProjectDependencyFactory.java
    public ProjectDependency create(ProjectInternal project, String configuration) 
        DefaultProjectDependency projectDependency = (DefaultProjectDependency)this.instantiator.newInstance(DefaultProjectDependency.class, new Object[]project, configuration, this.projectAccessListener, this.buildProjectDependencies);
        projectDependency.setAttributesFactory(this.attributesFactory);
        projectDependency.setCapabilityNotationParser(this.capabilityNotationParser);
        return projectDependency;
    

分析:传入的project最终传递给了ProjectDependencyMapNotationConverter。先去查找这个project,然后通过factory去create ProjectDependency对象,最终是产生了一个DefaultProjectDependency。这就是ProjectDependency的产生过程。

依赖是如何实现的:

// DefaultProjectDependency.java
    public TaskDependencyInternal getBuildDependencies() 
        return new DefaultProjectDependency.TaskDependencyImpl();
    

    private class TaskDependencyImpl extends AbstractTaskDependency 
        private TaskDependencyImpl() 
        

        public void visitDependencies(TaskDependencyResolveContext context) 
            if (DefaultProjectDependency.this.buildProjectDependencies) 
                DefaultProjectDependency.this.projectAccessListener.beforeResolvingProjectDependency(DefaultProjectDependency.this.dependencyProject);
                Configuration configuration = DefaultProjectDependency.this.findProjectConfiguration();
                context.add(configuration);
                context.add(configuration.getAllArtifacts());
            
        
    

    public Configuration findProjectConfiguration() 
        ConfigurationContainer dependencyConfigurations = this.getDependencyProject().getConfigurations();
        String declaredConfiguration = this.getTargetConfiguration();
        Configuration selectedConfiguration = dependencyConfigurations.getByName((String)GUtil.elvis(declaredConfiguration, "default"));
        if (!selectedConfiguration.isCanBeConsumed()) 
            throw new ConfigurationNotConsumableException(this.dependencyProject.getDisplayName(), selectedConfiguration.getName());
         else 
            return selectedConfiguration;
        
    

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

Gradle基础知识点总结

Gradle命令行操作

Gradle compileDebugJavaWithJavac lambda 编译错误问题

Gradle compileDebugJavaWithJavac lambda 编译错误问题

Android Gradle 技巧之一: Build Variant 相关

每个新项目都会在 Android Studio 中下载大量 gradle 版本和 jcenter.bintray 文件