zoukankan      html  css  js  c++  java
  • Android studio:Groovy 与 Gradle 基础【三】

    转载:http://www.apkbus.com/forum.php?mod=viewthread&tid=255064&extra=page%3D2%26filter%3Dauthor%26orderby%3Ddateline&_dsign=276e9e2e

     
    相信看过前一篇 《Android Studio 与 Gradle 深入》的同学,有一部分就会遇到我初识 Gradle 时的困惑:代码我也依稀看得懂,但就是不知道还能这样写,为什么这样写。
     

    问题与解决方案

    回想我在 Gradle 的学习过程中遇到的问题与及其解决方案,总结出下面三点:
     
    • 原理不懂:学习 Groovy 与 Gradle 的基础原理
    • Gradle 实践不懂:学会找示例,学习开源例子
    • 方法和属性不懂:学会查文档
    下面的我将以解决三个问题为线索,介绍 Groovy 和 Gradle。
     

    学习 GroovyGroovy 概述

    Gradle 采用了 Groovy 语言作为主要的脚本语言。一个 build.gradle 文件,其实是一个 Groovy 类。
    Groovy 是一个基于 JVM 的语言,代码最终编译成字节码(bytecode)在 JVM 上运行。它具有类似于 Java 的语法风格,但是语法又比 Java 要灵活和方便,同时具有动态语言(如 ruby 和 Python)的一些特性。
    Groovy 的诸多特定,很适合用来定义 DSL(Domain Specific Language)。
    简单的来讲 DSL 是一个面向特定小领域的语言,如常见的 HTML、CSS 都是 DSL,它通常是以配置的方式进行编程,与之相对的是通用语言(General Purpose Language),如 Java 等。
    既然是一门语言,就肯定有自己的特性。我们要从下面几个步骤来介绍 Groovy:
     
    • 环境安装
    • 语言基础
    • 语言特性及其本质


    环境安装

    Groovy 官方安装文档提供多种方式进行安装,确保你不会在跪在环境配置的路上 ^-^ :
    • Windows 下推荐 binary 包配置环境变量
    • Mac 下推荐使用 sdkman 或者 Brew 进行安装
    • Linux 下推荐 sdkman
    • 嵌入在程序中,则推荐使用 Maven 远程依赖
    初学者也没有必要使用IDE,平添障碍,后期用 Intellij IDEA Community 版本足矣。
     
    下面只介绍 Mac 下使用 sdkman 的安装方式。
     
    • 下载安装 sdkman,执行下面命令,按照提示安装即可
    1 $ curl -s http://get.sdkman.io | bash
    • 使环境变量生效
    1 $ source "$HOME/.sdkman/bin/sdkman-init.sh"
    • 安装 Groovy
    1 $ sdk install groovy
    • 查看当前版本,如果能否运行且输出对应版本,就是成功了
    1
    2
    $ groovy -version
    Groovy Version: 2.4.4 JVM: 1.8.0_25 Vendor: Oracle Corporation OS: Mac OS X


    初探

    安装好环境之后,先来一个 hello, world!
     
    • 新建文件
    1 $ vim test.groovy
    • 在其中写上内容
    1 println "hello, world!"
    • 保存退出,执行
    1
    2
    $ groovy test.groovy
    hello, world!
    Wow, So easy!
     

    语言基础

    下面将会用一些实际的例子,介绍一些最重要的点,
     
    例子都已经传到 github 的 demo 项目中。

    第一次使用 demo 项目的时候,需要等待自动下载几个远程包。
    笔者一个 Java 程序员,可以你能够看到很多 Java 的习性还是留在代码中。
     

    文件与类,变量与函数

    Groovy 代码文件,支持不显式声明类:
     
    ScriptClass.groovy
     
    1 println 'hello,world'
    这样一个 Groovy 脚本,经过编译之后,会产生一个继承自 groovy.lang.Script 类的子类:
     
    是不是能看出点什么?
     
    groovy/build/classes/main/io/kvh/as/groovy/ScriptClass.class
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class ScriptClass extends Script {
        public ScriptClass() {
            CallSite[] var1 = $getCallSiteArray();
        }

        public ScriptClass(Binding context) {
            CallSite[] var2 = $getCallSiteArray();
            super(context);
        }

        public static void main(String... args) {
            CallSite[] var1 = $getCallSiteArray();
            var1[0].call(InvokerHelper.class, ScriptClass.class, args);
        }

        public Object run() {//关键方法
            CallSite[] var1 = $getCallSiteArray();
            return var1[1].callCurrent(this, "hello,world");// got it?
        }
    }
    Groovy 支持如下的方式来定义变量和函数:
     
    VarAndMethod.groovy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def varAndMethod() {
        def a = 1//不显式声明变量类型
        a = "abc"//运行时改变类型

        println a//无需;结束一行代码
        a = 4//最后一行作为返回值
    }
    def ret = varAndMethod()//文件内运行方法

    println ret//输出4


    字符串

    Groovy 支持单引号,双引号,三单引号声明一个字符串;
     
    Quoted.groovy
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def quoted() {
            def singleQ = 'hello, single quot'// 声明为java.lang.String
            def doubleQ = "hello, double quot ${singleQ}"// 如果有${},则为groovy.lang.GString,支持变量替换;否则为java.lang.String
            def tripleQ = '''hello,
    triple quot'''// 允许多行,而不需要+号

            println singleQ
            println doubleQ
            println tripleQ
    }
    Groovy 还支持以:
     
    1
    2
    3
    """..."""
    /.../
    $/.../$
    来声明字符串,详情参见参考文档。
     

    List,Array 和 Map

    Groovy 默认使用 java.util.ArrayList 来提供 List 服务,但提供了更加灵活易用的操作方式:
     
    Collections.groovy
     
    1
    2
    3
    4
    5
    6
    7
    def playList() {
        def lst = ["a",2,true]//支持不同类型元素

        println(lst)
    }

    playList()
    要使用 Array,需要显式声明:
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def playArray() {
        def intArr = [1, 2, 3] as int[]//显示声明
        String[] strArr = ["a", "b"]//另外一种方式

        println(intArr)
        println(strArr)
    }

    playArray()
    使用 key:value 的方式定义 Map,注意 key 的正确使用方式:
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def playMap() {
        def map = [a: "a", b: "b"]
        
        println(map)
        
        def key = "name"
        def map2 = [key: 'a']//未使用
        def map3 = [(key): 'a']//使用

        println(map2)
        println(map3)
    }

    playMap()


    import

    Groovy 提供了更强大的 import
     
    • 默认 import,这些类都是被默认 import 到代码中的,可以直接使用
    1
    2
    3
    4
    5
    6
    7
    8
    import java.lang.*
    import java.util.*
    import java.io.*
    import java.net.*
    import groovy.lang.*
    import groovy.util.*
    import java.math.BigInteger
    import java.math.BigDecimal
    • import alias
    引入一个类,通过 as 关键字赋予一个别名,有点 JavaScript 的意思么?
     
    Import.groovy
     
    1
    2
    3
    import java.lang.String as KString

    println(new KString("aaa"))


    语言特性及其本质Closure(闭包)

    闭包的概念不再赘述,大概就是可以将函数作为参数传递和使用,详情参见 wikipedia
     
    1 { [closureParameters -> ] statements }
    可以省略方括号内的内容,也就是说,可以没有参数列表。
     
    Closure.groovy
     
    当闭包不声明参数列表,默认参数是 it;闭包被定义之后,是一个 Closure 对象,可以对其调用 call 方法使其执行。
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def defaultIt() {
        3.times {
            println it //默认参数 it
        }
    }

    defaultIt()
    def closureObj() {
        def obj = { a ->
            ++a
        }
        println obj.call(1)
    }

    closureObj()


    面向对象特性

    • 定义和实例化一个类
    GroovyCLass.groovy
     
    1
    2
    3
    4
    5
    6
    7
    class People{
        String name
        int age
    }

    People p1 = new People();
    People p2 = new People(name:"Luis",age: 29)//通过类似 map 的方式赋值参数
    • 方法的默认参数
    1
    2
    3
    4
    5
    6
    def foo(String p1, int p2 = 1) {
        println(p1)
        println(p2)
    }

    foo("hello")
    • Field 和 Property
    Field 是以各种修饰符修饰的变量。Property是私有变量和自带的 gettters/setters,
    下面的类具有私有变量 name、age,并自带这两个变量的 getter 和 setter:
     
    1
    2
    3
    4
    class People{
        String name
        int age
    }
    当变量声明为 final 的时候,默认就没有 setter
     
    • Trait
    Groovy 提供了一个叫做 Trait 特性实现了多继承,还有很多强大的功能,读者可以自己探索。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    trait Fly {
        void fly() {
            println("fly")
        }
    }

    trait Walk {
        void walk() {
            println("walk")
        }
    }

    class Duck implements Fly, Walk {

    }

    Duck duck = new Duck()
    duck.fly()
    duck.walk()


    Groovy 基础小结

    至此,我们已经熟悉了 Groovy 的基本语法和特性,相信你也能够使用 Groovy 写一些基础程序了。Groovy 还有很多深入的内容,请用到的时候,参考这本这个 pdf: 《Programming Groovy 2》
    下面开始介绍使用 Groovy 写 Gradle 程序,主要的内容来自 《Gradle Sser Guide》
     

    学习 GradleGradle 安装

    三种方式安装 Gradle:
    1 brew install gradle
    • 推荐:使用 IntelliJ IDEA(Android Studio)自带的 wrapper 结构来下载 Gradle
    Wrapper 是为了让不同版本的插件能够使用其对应版本的 Gradle 的一个机制
    Gradle Wrapper 会把不同的版本 Gradle 安装在:
     
    1 $USER_HOME/.gradle/wrapper/dists

    Gradle Build 的生命周期

    回忆一下《Android Studio 与 Gradle 深入》中的 Android Studio 项目文件结构:
     
     
    1
    2
    3
    4
    5
    6
    7
    8
    .
    ├── app                                                //app module
    │   ├── build.gradle                //app module 的 build.gradle
    ├── build.gradle                        //项目 build.gradle,通常配置项目全局配置,如 repositories 和 dependencies
    ├── gradle.properties                //项目属性文件,通常可以放置一些常量
    ├── lib                                                //lib module
    │   ├── build.gradle                //lib module 的 build.gradle
    └── settings.gradle                        //项目总体设置,通常是配置项目中所有的 module


    Gradle 构建三个阶段:

    • 初始化:Gradle 支持单 module 构建和多 module 构建(Android Studio 创建的项目默认是多 module)。初始化阶段,Gradle 会为每一个 module 中的 build.gradle 文件创建一个 Project 实例。
    • 配置:项目根目录的 build.gradle 会首先被执行
    • 执行:执行所选取的 task

    Settings.gradle

    多 module 构建要求在项目根目录下有一个 settings.gradle,用来指定哪些 module 参与构建,如:
    settings.gradle
     
    1
    2
    3
    include ':app', ':groovy'

    println 'print in settings.gradle'
    在 settings.gradle 文件中,添加一行打印语句,在控制台中,切换到当前项目根目录下执行:
     
    1 ./gradlew -p groovy build
    可以在看出 settings.gradle 的代码每次都会率先执行。
     

    Task

    接下来,我们开始学习 Gradle 的核心 Task。
    groovy/build.gradle
    定义一个 Task:
     
    1
    2
    3
    4
    5
    task hello {
        doLast {
            println 'Hello,'
        }
    }
    执行命令,查看输出:
     
    1
    2
    $ ./gradlew hello
    Hello,
    Task 也可以这样定义:
     
    1
    2
    3
    task World << {
        println 'World!'
    }
    注意,如果定义成这样:
     
    1
    2
    3
    task hi {
        println 'description hi'
    }
    在进行初始化和配置的时候,下面语句就会运行。
     
    1 println 'hi'
    这种语法通常是用来定义 task 的描述信息。
    Task 可设置 dependsOn 和 finalizedBy:
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    task hello {
        doLast {
            println 'Hello,'
        }
    }

    task intro(dependsOn: hello) << {
        println 'intro'
    }

    World.finalizedBy hello
    执行 intro 之前,会先执行 hello;执行 World 之后,会自动执行 hello


    Plugin

    Gradle 的核心代码,只提供了一个框架,具体的功能(如构建 Android 工程)是通过插件机制来实现的。
    Gradle 提供了大量官方的插件,如 Maven、Groovy、Java、Publishing、Signing等,也有大量第三方的插件(Android),甚至每个人都可以自己实现一个插件(如 笔者开发的 Bugtags 插件,这个将在最后一篇讲述)。
    这些 plugin 定义了一系列的 task、DSL 和约定,在build.gradle 文件使用这些 plugin:
     
    1 apply plugin: java
    当你写了一个独立的 file_uri.gradle 文件,你可以通过:
     
    1 apply from: 'file_uri.gradle'
    来引入你的 gradle 文件,这个文件甚至可以在某个服务器上。
     

    Gradle 实践参考

    学习了基础理论之后,如果你还是不知道如何开始写,那就先来实现一个自定义 apk 名称的功能吧!
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    android.applicationVariants.all { variant ->//获取 variant 参数,就是 productFlavor x buildType
        variant.outputs.each { output ->//获取输出文件
            def file = output.outputFile//修改实例
            output.outputFile = new File(
                    (String) file.parent,
                    (String) file.name.replace(
                            file.name,
                            // alter this string to change output file name
                            "Your_Apk_Name_" + variant.name + "_" + variant.versionName + ".apk"
                    )
            )
        }
    }
    你问我怎么知道 android 下有个 applicationVariants?其实我也不知道的,也得找文档。
     
    因为使用的是 Android 的插件,那就得在谷歌搜 “android gradle plugin dsl”,果然有个 Android Plugin DSL Reference
     
    点进去找找,里面有关于 build variant 的文档: applicationVariants,既然是一个 Set,那就可以调用 all 方法。
     
    写代码调试,再配合文档,你就晓得该怎么写了。
     

    总结

    从 Gradle 入门到现在略懂,经历了大量懵懂的时光。最后狠下心去系统学习了 Groovy 和 Gradle 的基础之后,最终茅塞顿开。希望读者遇到类似的情况,一定要沉下心,多学多练。
    在接下来的两篇,我将分别介绍将发布远程库和编写 Gradle 插件。
  • 相关阅读:
    codeforces 336D Vasily the Bear and Beautiful Strings(组合数学)
    13年山东省赛 The number of steps(概率dp水题)
    13年山东省赛 Mountain Subsequences(dp)
    13年山东省赛 Boring Counting(离线树状数组or主席树+二分or划分树+二分)
    codeforces 337C Quiz(贪心)
    codeforces 336C Vasily the Bear and Sequence(贪心)
    codeforces 335A Banana(贪心)
    codeforces 339C Xenia and Bit Operations(线段树水题)
    codeforces 339C Xenia and Weights(dp或暴搜)
    codeforces 340E Iahub and Permutations(错排or容斥)
  • 原文地址:https://www.cnblogs.com/benchao/p/5417345.html
Copyright © 2011-2022 走看看