Gradle 相关

一、Groovy脚本

Groovy是一种动态语言,Groovy脚本基于Java且拓展了Java,都在Java虚拟机中运行。当运行Groovy脚本时它会先被编译成Java类字节码,然后通过JVM虚拟机执行这个Java字节码类。

1、如何安装Groovy?

安装Groovy在各种Bash下(bash 是一个为GNU计划编写的Unix shell)都是通用的,具体如下命令就可搞定:

$ curl -s get.sdkman.io | bash
$ source "$HOME/.sdkman/bin/sdkman-init.sh"
$ sdk install groovy
$ groovy -version
//至此就可以享用了!

我们在写Groovy代码时可以直接使用自己喜欢的文本编辑器编辑就可以(studio有内置的 Tools-->IDE Scripting Console-->Groovy)

然后以.groovy后缀保存,然后在终端执行如下命令即可运行:

$ groovy ./HaiJiaTest.groovy
二、Gradle

通俗的说:gradle是打包用的,Gradle核心是基于Groovy的领域特定语言(DSL)。Ant和Maven都是基于XML的构建工具,Gradle是用Groovy编写的构建工具。Gradle通过编写一个名为build.gradle的脚本文件对项目进行设置,再根据这个脚本对项目进行构建(复杂的项目也有其他文件)。当前其支持的语言限于Java、Groovy、Kotlin和Scala。

Gradle里有两个基本概念:项目(projects)和任务(tasks)。

项目由多个任务组成,一个项目可以理解为提供给不同设备的构建版本,如桌面版、网页版、安卓版、iOS版等等,也可以理解为一种行为,例如部署应用到生产环境。任务相当于Ant的target,可以理解成一个构建中原子性的工作,例如编译、打包、执行等。需要注意的是,Ant中他自己的命令例如javac、copy等也叫做task,但Ant的task远没有Gradle的task那么自由。

为什么要用Gradle

ant可以自动化打包逻辑。
maven也可以自动化打包,相比于ant,它多做的事是帮你下载jar包。
但是maven的打包逻辑太死板,定制起来太麻烦,不如ant好用。gradle就是又能自动下jar包,又能自己写脚本,并且脚本写起来还比ant好用的这么个东西。

Gradle DSL基础

Gradle的实质是配置脚本,执行一种类型的配置脚本时就会创建一个关联的对象,譬如执行Build script脚本就会创建一个Project对象,这个对象其实就是Gradle的代理对象。下面给出来各种类型Gradle对应的对象类型:

脚本类型 关联对象类型
Build script Project :每个build.gradle会转换成一个Project对象。
Init script Gradle :构建初始化时创建,整个构建执行过程中只有这么一个对象,一般很少去修改这个默认配置脚本。
Settings script Settings : 每个settings.gradle会转换成一个Settings对象。

可以看见,当我们编写指定类型Gradle脚本时我们可以直接使用关联对象的属性和方法;当然了,每个脚本也都实现了Script接口,也就是说我们也可以直接使用Script接口的属性与方法。

1、构建脚本Build script(Project)

在Gradle中每个待编译的工程都是一个Project(每个工程的build.gradle对应一个Project对象),每个Project在构建的时候都包含一系列Task,这些Task中很多又是Gradle的插件默认支持的。

PS:所谓的我们编写Gradle脚本,实质大多数时候都是在编写构建脚本Build script,所以说Project和Script对象的属性和方法等API非常重要。

每一个Project对象和build.gradle一一对应,一个项目在构建时都具备如下流程:

  • 为当前项目创建一个Settings类型的实例。

  • 如果当前项目存在settings.gradle文件,则通过该文件配置刚才创建的Settings实例。

  • 通过Settings实例的配置创建项目层级结构的Project对象实例。

  • 最后通过上面创建的项目层级结构Project对象实例去执行每个Project对应的build.gradle脚本。

2、初始化脚本Init script(Gradle)

初始化脚本Init script(Gradle)类似于Gradle的其他类型脚本,这种脚本在构建开始之前运行,主要的用途是为接下来的Build script做一些准备工作。

初始化脚本的Gradle对象代表了Gradle的调运,我们可以通过调用Project对象的getGradle()方法获得Gradle实例对象。

3、设置脚本Settings script(Settings)

在对工程进行配置(譬如多项目树构建)时Settings实例与settings.gradle文件一一对应,它用来进行一些项目设置的配置。这个文件一般放置在工程的根目录。譬如:

4、Build生命周期

Gradle的构建脚本生命周期具备三大步,如下:

5、Gradle多项目构建:

多项目构建总是需要指定一个树根,树中的每一个节点代表一个项目,每一个Project对象都指定有一个表示在树中位置的路径;在设置文件中我们还可以使用一套方法来自定义构建项目树。

//分层布局的多项目构建settings.gradle文件
include 'project1', 'project2:child', 'project3:child1'

上面例子中把project的路径作为了include方法的参数,譬如上面的’project3:child1’参数就指定了物理路径的project3/child1(project3/child1是相对于多项目根路径的相对路径),这也同时意味着会创建’project3’和’project3:child1’两个project。

//平面布局的多项目构建settings.gradle文件
includeFlat 'project3', 'project4'

上面例子中includeFlat方法接受目录名作为参数,但是特别注意,这些项目目录必须是根目录的兄弟目录。

当然了,设置文件中创建的多项目树其实是由项目描述符来描述的,我们可以在设置文件中随时修改这些描述符。如下:

//settings.gradle
rootProject.name = 'main'
project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
project(':projectA').buildFileName = 'projectA.gradle'

可以看见,如上例子通过描述符更改名称和项目目录,并且建立了一个项目的文件。

Gradle构建初始化Initialization:

在初始化阶段如果我们在根路径下直接指明settings.gradle文件和相关配置则构建初始化就会直接按照我们的设置去构建项目,如果我们没指明settings.gradle文件则Gradle会以一定的规则去寻找settings.gradle文件,然后依据寻找结果的不同去决定如何构建项目。

三、Gradle在android中的应用
apply plugin: 'com.android.application'

android {

    compileSdkVersion 26
    buildToolsVersion '26.0.2'

    defaultConfig {
        applicationId "com.project.haijia"
        // 配置生成的 BuildConfig 文件中的常量
        buildConfigField "String", "CHANNEL", '"PLAY_STORE"'
        minSdkVersion 16
        targetSdkVersion 25
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

     buildTypes {

        debug {
            buildConfigField "String", "API_URL", '"http://dev.goodev.org/"'
            buildConfigField "boolean"�, "SHOW_LOG"�, "true"
         }

   
        release {
            minifyEnabled false
            // 混淆文件的位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
      }

    // 移除lint检查的error
    lintOptions {
      abortOnError false
    }
}

//dependencies中配置的是这个项目中要用的类库
dependencies {
    //表示包括之前在libs文件夹下的所有jar包
    compile fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'

     //表示测试代码的时候依赖junit这个库
    testCompile 'junit:junit:4.12'

    // 编译extras目录下的JiaAndroid模块
    compile project(':extras:JiaAndroid')

    //表示编译期依赖gson这个库
    compile 'com.google.code.gson:gson:2.8.0'
   
    androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:3.0.1'
}



productFlavors {  
    youmi {
      applicationId = "org.goodev.material.youmi"
                buildConfigField "String", "CHANNEL", '"YOUMI"'
                resValue "string", "app_name", "Material free"
    }
    pro {
      applicationId = "org.goodev.material.pro"
                buildConfigField "String", "CHANNEL", '"OTHER"'
                resValue "string", "app_name", "Material pro"
    }
  }
    
signingConfigs {
    release {
        storeFile     "${System.env.PRIVATE_KEY}"
        keyAlias      "${System.env.ALIAS}"
        storePassword "${System.env.STORE_PW}"
        keyPassword   "${System.env.APP_PW}"
    }
}

两个配置项 android 和dependencies

通过 System.env 来访问系统环境变量中的值,这样你就可以把一些私有的内容排除在代码外,这样当你提交代码的时候,就不会泄露这些内容。还可以把私有数据放到local.properties 文件中:

 signingConfigs {
        release {
            def Properties localProps = new Properties()
            localProps.load(new FileInputStream(file('local.properties')))
            def Properties keyProps = new Properties()
            if (localProps['keystore.props.file'] != null) {
                keyProps.load(new FileInputStream(file(localProps['keystore.props.file'])))
            }
            storeFile keyProps["store"] != null ? file(keyProps["store"]) : null
            keyAlias keyProps["alias"] ?: ""
            storePassword keyProps["storePass"] ?: ""
            keyPassword keyProps["pass"] ?: ""
        }
    }

动态生成 Android Manifest 中的内容

为了让所有的 productFlavors 都可以安装到同一个手机上, 安卓系统要求不同的应用具有不同的 provider 等内容,如果冲突则其他应用无法安装,可以通过如下方式来解决该问题

<permission android:name="${applicationId}.permission.XX"  
        android:protectionLevel="signature" />
<uses-permission android:name="${applicationId}.permission.XX" />
 
<provider   android:name=".MyProvider"  
            android:authorities="${applicationId}.provider"
            … />
不同的 productFlavors 具有不同的 permission

四、Gradle常用命令

  • ./gradlew -v 版本号

  • ./gradlew clean 清除9GAG/app目录下的build文件夹

  • ./gradlew installRelease Release模式打包并安装

  • ./gradlew uninstallRelease 卸载Release模式包

  • ./gradlew build 检查依赖并编译打包
    这里注意的是 ./gradlew build 命令把debug、release环境的包都打出来,如果正式发布只需要打Release的包,该怎么办呢,下面介绍一个很有用的命令 assemble, 如

  • ./gradlew assembleDebug 编译并打Debug包

  • ./gradlew assembleRelease 编译并打Release的包
    除此之外 assemble 还能和 Product Flavor 结合创建新的任务,其实 assemble 是和 Build Variants 一起结合使用的,而 Build Variants = Build Type + Product Flavor

  • ./gradlew assembleWandoujiaRelease 打包wandoujia渠道的release版本
    如果我们,则:

  • ./gradlew assembleWandoujia 只打wandoujia渠道版本,此命令会生成wandoujia渠道的Release和Debug版本

  • ./gradlew assembleRelease 这条命令会把Product Flavor下的所有渠道的Release版本都打出来。
assemble 命令创建task有如下用法:
  • assemble: 允许直接构建一个Variant版本,例如assembleFlavor1Debug。
  • assemble: 允许构建指定Build Type的所有APK,例如assembleDebug将会构建Flavor1Debug和Flavor2Debug两个Variant版本。
  • assemble: 允许构建指定flavor的所有APK,例如assembleFlavor1将会构建Flavor1Debug和Flavor1Release两个Variant版本。
五、多渠道打包

这里举例友盟为主
AndroidManifest.xml

<meta-data
    android:name="UMENG_CHANNEL"
    android:value="${UMENG_CHANNEL_VALUE}" />

在build.gradle设置productFlavors

android {  
    productFlavors {
        xiaomi {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
        }
        _360 {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "_360"]
        }
        baidu {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
        }
        wandoujia {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
        }
    }  
}

或者批量修改

android {  
    productFlavors {
        xiaomi {}
        _360 {}
        baidu {}
        wandoujia {}
    }  

    productFlavors.all { 
        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name] 
    }
}

一个完整的多渠道例子

apply plugin: 'com.android.application'

def releaseTime() {
    return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

android {
    compileSdkVersion 26
    buildToolsVersion '26.0.2'

    defaultConfig {
        applicationId "com.haijia.*"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        
        // dex突破65535的限制
        multiDexEnabled true
        // 默认是umeng的渠道
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "umeng"]
    }

    lintOptions {
        abortOnError false
    }

    signingConfigs {
        debug {
            // No debug config
        }

        release {
            storeFile file("../yourapp.keystore")
            storePassword "your password"
            keyAlias "your alias"
            keyPassword "your password"
        }
    }

    buildTypes {
        debug {
            // 显示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"

            versionNameSuffix "-debug"
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.debug
        }

        release {
            // 不显示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"

            minifyEnabled true
            zipAlignEnabled true
            // 移除无用的resource文件
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release

            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith('.apk')) {
                        // 输出apk名称为boohee_v1.0_2015-01-15_wandoujia.apk
                        def fileName = "boohee_v${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
                        output.outputFile = new File(outputFile.parent, fileName)
                    }
                }
            }
        }
    }

    // 友盟多渠道打包
    productFlavors {
        wandoujia {}
        _360 {}
        baidu {}
        xiaomi {}
        tencent {}
        taobao {}
        ...
    }

    productFlavors.all { flavor ->
        flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    ...
}

参考

Gradle教程
Gradle学习系列

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 参考资料:http://gold.xitu.io/post/580c85768ac247005b5472f9htt...
    zhaoyubetter阅读 10,996评论 0 6
  • 这篇文章讲给大家带来gradle打包系列中的高级用法-自己动手编写gradle插件。我们平常在做安卓开发时,都会在...
    呆萌狗和求疵喵阅读 15,985评论 22 80
  • Gradle简介 Gradle是一个构建工具,同时它也是一个编程框架。 当你把Gradle当构建工具看的时候,我们...
    Rangethan阅读 2,320评论 1 8
  • 1. Wrapper 资料1,资料2,资料3 。 看完直接说结论: 结论一:Wrapper与Gralde间是解耦的...
    aicaprio阅读 633评论 0 0
  • 故事发生在虚构的幻雪帝国,冰国王子卡索和自己的弟弟樱空释幼年时流落人间,回到神界后,弟弟为了让哥哥能够自由自在的生...
    啦啦啦呵阅读 371评论 0 1