再谈Gradle,配置文件拆解

383 阅读5分钟

闭包

对于gradle来讲,buildscript相当于一个方法,里面的内容相当于方法传递的参数,这实际上就是一个方法调用

如下,点进buildscript的源码,我们定位到了Project.java,发现buildscript其实就是一个参数为闭包的一个方法,

void buildscript(Closure configureClosure);

project/build.gradle

buildscript {
    ext.kotlin_version = '1.4.10'

    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.4'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

闭包和方法调用

如下,闭包和方法调用时等价的

//闭包写法
allprojects {
    repositories {
        mavenCentral()
        maven { url 'https://maven.google.com' }
        jcenter()
        google()
        maven { url "https://jitpack.io" }//使用数据库升级辅助GreenDaoUpgradeHelper时添加
    }
}

//方法调用写法
allprojects(new Action<Project>() {
    @Override
    void execute(Project project) {
        repositories {
            mavenCentral()
            maven { url 'https://maven.google.com' }
            jcenter()
            google()
            maven { url "https://jitpack.io" }//使用数据库升级辅助GreenDaoUpgradeHelper时添加
        }
    }
})

方法讲解

buildTypes

android{
    buildTypes{
        test{
            minifyEnabled false
            ...
        }
        release{
            minifyEnabled true
            ...
        }
    }
}

假设我们想要在我们的某个页面做一个标记,开发人员能看到,测试人员看不到,我们可以这样做

  • src下新建main同级别目录debug和release
  • 在release和debug下新建同包文件BuildTypes.kt
  • 在main下业务Activity中调用BuildTypes.kt中的方法,
  • 这样随着在编译时构建类型的不同,运行时的表现也就不一样了
  • 假设我们构建的是debug,实际执行的代码就是main+debug的代码

代码如下:

productFlavors

渠道纬度配置,假设我们为App配置中文版和英文版,可以添加以下代码,那么根据上面的配置,我们就有了cnDebug、cnRelease、enDebug、enRelease 4个构建类型了

//Gradle3.0版本开始需要配置渠道维度,不配置则报错
    flavorDimensions 'language'
    productFlavors{
        cn{}
        en{}
    }

为了保证名称的App名称的本地化显示,我们可以在src下新建一个目录en,为我们的app配置不同的资源文件
src/en/res/values/strings

<resources>
    <string name="app_name">Dsh_Android</string>
</resources>

而在main我们默认的目录下

<resources>
    <string name="app_name">Dsh_安卓</string>
</resources>

多维度渠道配置

除了语言环境维度的不同,还可以扩展其他维度,比如渠道信息,那么我们可以这样写,现在我们有4x2 = 8个构建类型了,[cnXiaomiDebug,enXiaomiDebug,...]

    //Gradle3.0版本开始需要配置渠道维度,不配置则报错
    flavorDimensions 'language','market'
    productFlavors{
        cn{
            dimension 'language'
        }
        en{
            dimension 'language'
        }
        yingyongbao{
            dimension 'market'
        }
        xiaomi{
            dimension 'market'
        }
    }

dependencies

compile, implementation 和 api

  • implementation:不会传递依赖
  • compile / api:会传递依赖;api 是 compile 的替代品,效果完全等同
  • 当依赖被传递时,二级依赖的改动会导致 0 级项目重新编译;当依赖不传递 时,二级依赖的改动不会导致 0 级项目重新编译
  1. 假设我们app主工程【0级】implementation依赖了lib1【1级】,lib1通过implementation依赖了lib2【2级】
    • 由于lib1不传递依赖,所以app下是无法直接使用lib2中的类的
  2. 假设lib1通过api依赖了lib2【2级】
    • 由于lib1传递了依赖,所以app下是直接可以使用lib2中的类的

这样做的原因是,lib1使用api依赖了lib2,那么app就间接依赖了lib2,这样app主工程每次编译的时候都需要重新编译lib2,这增加了打包构建的时间,而通过implementation不依赖lib2,那么打包时间会缩短。
这样做既有好处也有缺点,具体情况要具体分析

Gradle Wrapper

对应项目中的gradlew文件

  • 通过「只同步版本,不同步文件」的方式来减小协作项目的大小 每个人电脑上的
  • Gradle 存放在固定位置,然后使用 Gradle Wrapper 的配置来 取用对应的版本就行了

gradle-wrapper.properties
gradle-wrapper配置文件,真正的Gradle Wrapper是gradlew

settings.gradle
项目结构,包括module等

task

例:比如clean:清除项目的build目录

task clean(type: Delete) {
    delete rootProject.buildDir
}
  • 使用方法: ./gradlew taskName
  • task 的结构:
task taskName {
    初始化代码
    doFirst { 
        task 代码
    } 
    doLast {
        task 代码 
    }
}
  • doFirst() doLast() 和普通代码段的区别:
    • 普通代码段:在 task 创建过程中就会被执行,发生在 configuration 阶段
    • doFirst() 和 doLast():在 task 执行过程中被执行,发生在 execution 阶 段。如果用户没有直接或间接执行 task,那么它的 doLast() doFirst() 代码 不会被执行
    • doFirst() 和 doLast() 都是 task 代码,其中 doFirst() 是往队列的前面插入代 码,doLast() 是往队列的后面插入代码
    • 如下代码,最后输出为 -> 4213
    task clean(type: Delete) {
        delete rootProject.buildDir
        
        doLast {
            println 1
        }
    
        doFirst {
            println 2
        }
        
        doLast {
            println 3
        }
    
        doFirst {
            println 4
        }
        
        //4213
    }
    
  • task 的依赖:可以使用 task taskA(dependsOn: b) 的形式来指定依赖。 指定依赖后,task 会在自己执行前先执行自己依赖的 task。
    • 下面我们完成一个功能,每次更新版本前,都给当前的版本号+1,那么我们像下面这样写,这样我们每次打包前执行 ./gradlew bumpVersionAndNotify任务就可以了
    task bumpVersion() {
        doLast {
            def versionPropsFile = file('version.properties')
            def versionProps = new Properties()
            versionProps.load(new FileInputStream(versionPropsFile))
            def codeBumped = versionProps['VERSION_CODE'].toInteger() + 1
            versionProps['VERSION_CODE'] = codeBumped.toString()
            versionProps.store(versionPropsFile.newWriter(), null)
        }
    }
    
    task bumpVersionAndNotify(dependsOn: bumpVersion) {
        doLast {
            println "升级完成"
        }
    }
    

gradle 执行的生命周期

三个阶段:

  • 初始化阶段:执行 settings.gradle,确定主 project 和子 project
  • 定义阶段:执行每个 project 的 bulid.gradle,确定出所有 task 所组成的有向无 环图
  • 执行阶段:按照上一阶段所确定出的有向无环图来执行指定的 task

在阶段之间插入代码:

  • 一二阶段之间:settings.gradle 的最后
  • 二三阶段之间:project/build.gradle的最后
afterEvaluate {
    插入代码
}