美文网首页Android程序员
学习Android构建系统(三)-Gradle基础

学习Android构建系统(三)-Gradle基础

作者: 十思叶 | 来源:发表于2018-08-27 20:11 被阅读67次

    引言


    Gradle是Android构建系统的重点,需要花费时间用心学习。学习资料主要是官方的Gradle Docs

    Gradle简介


    Gradle是一个注重灵活性和性能的开源构建自动化工具,使用Groovy或Kotlin DSL来编写构建脚本。

    虽然支持Kotlin,但还是建议学习Groovy。因为:一,目前官方示例里有许多还没有kotlin方式;二,目前大多数项目的Gradle脚本都是用Groovy编写的。

    Gradle特点如下:

    1. 高度可定制。Gradle使用了可定制和可扩展的设计思想。
    2. 快速。Gradle通过重用先前执行的输出,仅处理已更改的输入以及并行执行任务来快速完成任务。
    3. 强大。Gradle是Android的官方构建工具,并支持许多主流的编程语言和技术。

    Gradle支持IDE或命令行两种方式来构建工程。支持主流的IDE,如Android Studio, Eclipse, IntelliJ IDEA, Visual Studio 2017, 和XCode等。

    Gradle在构建时,会根据构建脚本创建对应的Java实例,如settings.gradle对应Setting接口,build.gradle对应Project接口。

    There is a one-to-one correspondence between a Settings instance and a settings.gradle settings file. Before Gradle assembles the projects for a build, it creates a Settings instance and executes the settings file against it.
    There is a one-to-one relationship between a Project and a build.gradle file.

    Gradle Docs的使用


    Gradle Docs资料中,最重要的是User Manual(用户手册)和API文档(DSL Reference和Javadoc),其余的可以不用看。

    Gradle Docs
    Gradle User Manual

    User Manual中最关键的一部分当属‘Build Configuration Scripts’。这一部分内容相当多,内容也比较杂。可以先看‘Build Lifecycle’和‘Configuring Multi-Project Build’这两节,看懂这两节,就可以对Gradle构建的流程有一个清晰的了解了,之后再看其他章节就非常容易了。


    Gradle User Manual
    API文档

    API文档包括Gradle DSL Reference(Gradle特定语言指南)Javadoc(Java文档)。Reference包含主要的接口和类,并做了描述和分类以利于学习,Javadoc包含所有的API,主要用于检索。就内容的覆盖面而言,Javadoc完全包含Reference。学习时可以看Reference,使用中具体查找Gradle的某个API文档时,用Javadoc。

    Reference Javadoc

    几个基础概念


    脚本(Script)

    利用Gradle构建项目时,与开发者直接交互的就是脚本了。Gradle脚本有以下特点

    1. 每个脚本都有一个对应的代理对象。Gradle脚本是一种配置脚本,在脚本执行时,Gradle会配置特定类型对象,称为脚本的代理对象。脚本与代理对象一一对应,并且在脚本中可以直接使用代理对象的属性和方法。各Gradle脚本对应的代理对象如下:
    Type of script Delegates to instance of
    Build script Project
    Init script Gradle
    Settings script Settings
    1. Gradle脚本实现了Script接口。此接口定义了许多属性和方法,可以在脚本中直接使用。
    2. Gradle脚本由零或多个语句和脚本块组成(statements and script blocks)。 语句包括方法调用、属性赋值和局部变量定义等。 脚本块是一种方法调用,它以闭包作为方法的参数,而闭包用来配置代理对象。
    项目(Project)

    项目和任务是Gradle中的两个最基本的概念,所有的内容都基于它们。
    每个Gradle构建都由一个或多个项目组成。 Project接口是构建文件与Gradle交互的主要API。Project实例可以访问Gradle的所有功能。

    任务(Task)

    项目由一个或多个任务组成。 每个任务执行一些基本工作,例如编译类、运行单元测试或压缩WAR文件等。
    任务由一系列Action对象组成。 执行任务时,通过调用Action.execute(T)依次执行每个Action。 可以通过调用Task.doFirst(org.gradle.api.Action)Task.doLast(org.gradle.api.Action)向任务添加Action

    以上概念详情,可查看它们的Reference

    示例-单项目构建


    官方示例 Creating New Gradle Builds
    主要练习:初始化项目、执行任务、移动文件、添加Plugin等。

    提示:示例中使用了./gradlew来执行命令,如果因为下载失败导致该命令不可用,使用gradle命令即可。

    示例-多项目构建

    官方示例 Creating Multi-project Builds

    提示:示例中需要下载依赖,国内网络直接访问可能导致下载失败,需要设置国外代理服务。
    Gradle项目代理设置如下:在电脑中设置好http协议代理后,在当前项目根目录下创建gradle.properties文件(如果有就不用创建),编辑文件如下:

    systemProp.http.proxyHost=127.0.0.1
    systemProp.http.nonProxyHosts=192.168.*, <localhost>
    systemProp.http.proxyPort=8118
    systemProp.https.proxyHost=127.0.0.1
    systemProp.https.nonProxyHosts=192.168.*, <localhost>
    systemProp.https.proxyPort=8118
    

    其中8118privoxy代理的端口号
    Gradle代理设置详细说明请参考官方文档Accessing the web via a proxy(虽然也不够详细:p)

    示例-Android项目根目录 build.gradle语法分析


    利用Android Studio创建一个项目,根目录中build.gradle文件如下

    buildscript {
        ext.kotlin_version = '1.2.50'
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.1.4'
            classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        }
    }
    
    allprojects {
        repositories {
            google()
            jcenter()
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    直接使用API文档来分析上面的代码:

    1. Project和方法。build.gradle脚本的代理对象是Project实例,buildscriptallprojectstaskProject中的三个方法(实际上,task在这里是关键字,不是方法,但Project中有方法与之对应)。
    2. buildscript。在Android Studio中(Mac平台 Command+鼠标左键)或者在文档中查看API详情,如下
      buildscript
      这个方法的作用是配置项目构建脚本的classpath,参数是一个闭包,闭包的代理对象是ScriptHandler,即如果buildscript代码块中的属性和方法,如果不能在Project中找到,就去ScriptHandler中去找。
      再来分析下buildscript代码块里的内容。示例代码中,repositoriesdependencies都是ScriptHandler的方法,可以直接在Android Studio中点击查看其API。但ext是什么,是谁的属性?ownerProject)的,还是delegateScriptHandler)的?由于不能在Studio里直接查看,我们先看看Project的API
      Project
      全局搜索extsetExtgetExt,都没有结果。再看看其继承的接口,发现ExtensionAware中可以搜索到,API文档说
    // All extension aware objects have a special “ext” extension of type >ExtraPropertiesExtension
    assert project.hasProperty("myProperty") == false
    project.ext.myProperty = "myValue"
    // Properties added to the “ext” extension are promoted to the owning >object
    assert project.myProperty == "myValue"
    

    继承了ExtensionAware的对象都有一个特殊的ext扩展类型,可以直接添加属性project.ext.myProperty = "myValue",之后使用属性myProperty时可以不用写ext,如project.myProperty
    因此示例代码中的extownerProject)的属性。

    1. allprojects。分析方法同上,略。
    2. taskProject中有四个task方法

    Task task(String name)
    Task task(String name, Closure configureClosure)
    Task task(Map<String, ?> args, String name)
    Task task(Map<String, ?> args, String name, Closure configureClosure)

    Studio中链接的是Task task(String name),但后面的clean(type: Delete) { delete rootProject.buildDir }是什么呢?开始以为是方法,实际上并不是,一是在Project中没有clean方法,二是Groovy语法中不允许嵌套的方法省略括号

    Parentheses are required for method calls without parameters or ambiguous method calls:

    println(Math.max(5, 10))
    

    不是方法,但看起来又不是参数。查看TaskAPI,有定义task的示例

    You can also use the task keyword in your build file:

    task myTask
    task myTask { configure closure }
    task myTask(type: SomeType)
    task myTask(type: SomeType) { configure closure }
    

    也就是说task可以是一个关键字!去看Gradle官方文档Defining tasks

    There are a few variations on this style, which you may need to use in certain situations. For example, the keyword style does not work in expressions.

    有很多方式定义task,有关键字格式:

    task copy(type: Copy) {
        from(file('srcDir'))
        into(buildDir)
    }
    

    我们的示例就是这种格式的。
    也有方法格式的:

    task('copy', type: Copy) {
        from(file('srcDir'))
        into(buildDir)
    }
    

    这对应Project的方法Task task(Map<String, ?> args, String name, Closure configureClosure)。Groovy方法中的参数似乎是可以改变顺序的,有人写的博客中提到了这件事,但我没有找到相关的官方文档。
    终于分析完了,初学者学习这些估计要抓狂,:p。

    最后,把示例代码中省略的括号和ownerdelegate补全,以提高可读性,如下

    buildscript({
        owner.ext.kotlin_version = '1.2.50'
        delegate.repositories({
            delegate.google()
            delegate.jcenter()
        })
        dependencies({
            delegate.add('classpath', 'com.android.tools.build:gradle:3.1.4')
            delegate.add('classpath', "org.jetbrains.kotlin:kotlin-gradle-plugin:${owner.kotlin_version}")
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
        })
    })
    
    allprojects({
        delegate.repositories({
            delegate.google()
            delegate.jcenter()
        })
    })
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    

    注意: 其中的classpath,我暂时并没有理解清楚它是如何工作的,它不是方法,只能理解它是一个可以添加的配置属性。

    Dependency add(String configurationName, Object >dependencyNotation);
    

    参考


    1. Gradle官网
    2. Groovy官网

    上篇:学习Android构建系统(二)-Groovy基础
    下篇:学习Android构建系统(四)-Gradle构建流程

    相关文章

      网友评论

      本文标题:学习Android构建系统(三)-Gradle基础

      本文链接:https://www.haomeiwen.com/subject/hajaiftx.html