Android—Gradle教程(九)完结篇

前言

到目前为止,Gradle基础以及Kotlin基础讲解完毕。因此,在本篇里,将会以Gradle的构建优化以及如何从Groovy迁移到KTS进行详解!

话不多说,直接开始!

1、Gradle构建优化

优化都是些配置,快速过一下就行了!重点在迁移KTS

1.1 并行编译开启

默认情况下Gradle处理多模块时,往往是挨个按顺序处理。在项目根目录下面的gradle.properties中设置开启并行编译,提升编译速度:

org.gradle.parallel=true

1.2 开启编译守护进程 (默认开启)

该进程在第一次启动后回一直存在,当你进行二次编译的时候,可以重用该进程

  • 不需要每次启动gradle进程(JVM实例),减少了初始化相关的工作。
  • Daemon可以缓存项目结构,文件,task等,尽可能复用之前的编译成果,缩短编译过程
  • 在gradle.properties设置:org.gradle.daemon=true 。(其实默认已经支持了)

1.3 加大可编译内存

Dex-in-process 允许多个DEX 进程运行在一个单独的VM 中,这使得增量构建和清理构建变得更快。需要设置至少1536MB 的堆大小内存。

在gradle.properties中设置:org.gradle.jvmargs=-Xmx4096m //这里也就是4G大小

1.4 ZipAlign优化

在应用程序上运行zipalign,使得在运行时Android与应用程序间的交互更加有效率。让应用程序和整个系统运行得更快。

在app下面的build.gradle文件中设置:

android {
...略
    buildTypes {
        release{
            zipAlignEnabled true //也可和1.6 一起 加在自定义产品风味里
        }
        debug{
            zipAlignEnabled true
        }
    }
  }

1.5 配置dexOptions

配置 dexOptions 和 开启 library pre-dexing(dex预处理):Dex-in-process:Android Studio 2.1增加了一个新的特性:Dex In Process,可以极大的加快重新编译的速度,同样也能提高Instant Run的性能。配置相应的DEX构建特性可以提高构建速度。

android {
...略
    dexOptions {
        javaMaxHeapSize "2048m"
        jumboMode true//忽略方法数限制的检查

        //是否对依赖的库进行dex预处理来是你的增量构建更快速
        //因为这个特性可能会使你的clean构建变慢
        //因此在你的持续集成服务器上你可能想要关闭这个特性
        preDexLibraries true

        //设置最大的线程数量使用,当运行dex-in-process时,默认是4
        maxProcessCount 8
    }
  }

dexOptions一些设置说明:

  • preDexLibraaies : 声明是否对依赖的库进行dex 预处理来使你的增量构建更快速,因为这个特性可能会使你的clean 构建变慢,因此在你的持续集成服务器上你可能想关闭这个特性
  • javaMaxHeapSize: 为DEX 编译器 设置最大的堆大小,相对于设置这个属性,你应该增加 Gradle的 堆大小(这个堆大小dex-in-process可用的时候对DEX 编译器有效)这个值的设置需要调整第3点优化的值。
  • maxProcessCount : 设置最大的线程数量使用当运行 dex-in-process时,默认值是4。
  • 注意:这里的参数值没有一个规定的值,需要调整数值来测试一下哪个更适合,不然会得到一个负面的影响。

1.6 构建一个变体

有许多配置是你在准备app的release 版本的时候需要,但是当你开发app的时候是不需要的,开启不必要的构建进程会使你的增量构建或者clean构建变得很慢,因此需要构建一个只保留开发时需要配置的变体。通过productFlavors构建,dev代表debug版本,prod代表release版本

    flavorDimensions 'channel'
    productFlavors {
        //本地开发版本可以优化的内容
        dev {
            dimension 'channel'
            //避免编译不必要的资源
            resConfigs "en","xxhdpi"
            resValue 'string', 'bbb', 'nnnn'
			//1.4内容ZIP优化
			zipAlignEnabled true
            //禁止每次构建app都自动压缩图片
            aaptOptions{
                cruncherEnabled false
            }

            //本地开发环境可以停止友盟统计或者三方不需要的工具,这个是自定义的
            buildConfigField  "Boolean", "UM_FLAG", "false"
        }
        //正式版本
        prod {
            dimension 'channel'
            buildConfigField  "Boolean", "UM_FLAG", "true"
        }
    }

1.7 用静态的版本依赖

当你在build.gradle文件中声明依赖的时候,你应该避免在版本号结束的地方使用+号,比如:com.android.tools.build:gradle:4.+ 因为Gradle的检查更新,用动态的版本号会导致未知的版本更新、使解决版本的差异变得困难和更慢的构建。你应该使用静态或者硬编码版本号来代替。

1.8 分多module管理

抽取代码中相对独立的功能模块,创建新的module来开发,通过这种方式模块化你的代码将允许构建系统仅仅只编译那些有改动的模块,并将其构建结果缓存下来以被后面的构建使用。同时也可以提高开发效率,发布到maven上多APP公用。(组件化、插件化)

好了,上面的都快速过一下就行了!接下来就是本篇重点了!

2、Gradle Kotlin DSL

2.1 Kotlin DSL优缺点:

  • Android Gradle插件4.0支持在Gradle构建配置中使用Kotlin脚本 (KTS),用于替代 Groovy(过去在Gradle配置文件中使用的编程语言)
  • 将来,KTS会比Groovy更适合用于编写Gradle脚本,因为采用Kotlin编写的代码可读性更高,并且Kotlin提供了更好的编译时检查和IDE支持
  • 虽然与Groovy相比,KTS当前能更好地在Android Studio的代码编辑器中集成,但采用KTS 的构建速度往往比采用Groovy慢,因此在迁移到 KTS 时应考虑构建性能。

2.2 何为KTS?

  • KTS:是指Kotlin脚本,这是Gradle在构建配置文件中使用的一种Kotlin语言形式。Kotlin脚本是可从命令行运行的Kotlin代码。
  • Kotlin DSL:主要是指Android Gradle插件Kotlin DSL,有时也指底层Gradle Kotlin DSL
  • 用Kotlin编写的Gradle build文件使用.gradle.kts文件扩展名。

好了,概念说了一大堆,现在该上手了!

2.2 开始迁移:

迁移时建议单个文件进行,由简入深依次迁移

2.2.1 迁移 settings.gradle

原settings.gradle

include ':coroutines'
include ':app'
rootProject.name = "kotlinAndroidstudy"

先复制settings.gradle,然后删除,接着创建settings.gradle.kts

新settings.gradle.kts 注意这里后缀名

include (":coroutines",":app")
rootProject.name = "kotlinAndroidstudy"

我们看到这里include可以连续传多个参数,能够猜想到肯定有vararg关键字,进入对应源码看看:

abstract class SettingsDelegate : Settings {
...略
    override fun include(vararg projectPaths: String?) =
        delegate.include(*projectPaths)
}

看了源码果然如此。迁移到Kotlin DSL后,可以随意看里面的源码,比之前的groovy轻松多了!

接着下一个!

2.2.2 迁移根build.gradle

在之前,复制、删除、创建的基础上,还需要额外将项目里所有关于用Groovy写的build.gradle全注释掉!(现在Groovy与Kotlin DSL混合情况下,会一直报错,所以先把原有的全注释掉)

原 build.gradle

buildscript {
    ext.kotlin_version = '1.5.0'
    repositories {
        google()
        //jcenter()
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.2"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
       // jcenter()
        mavenCentral()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

新build.gradle.kts

buildscript{

}

先写个这个,然后点击右上角编译/try …,编译成功后如果能看到源码那就可以继续下一步!

源码

    /**
     * Configures the build script classpath for this project.
     *
     * @see [Project.buildscript]
     */
    @Suppress("unused")
    open fun buildscript(@Suppress("unused_parameter") block: ScriptHandlerScope.() -> Unit): Unit =
        internalError()

当你能够看到对应源码时,则说明对应的Kotlin DSL已经引入进来了!

继续改造新build.gradle.kts

buildscript {
	//ext.kotlin_version = '1.5.0'
    val kotlinVersion = "1.5.0" // kotlin dsl 里面不能有单引号!
    repositories {
        google()
       // jcenter()
        mavenCentral()
    }
    dependencies {
    	//这里改成了方法的调用
        classpath ("com.android.tools.build:gradle:4.1.2")
//        classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
		//因为这个是Kotlin plugin所以可以直接 使用 kotlin("gradle-plugin","版本号")
		//而不是上面那个
        classpath(kotlin("gradle-plugin", kotlinVersion))
    }
}

allprojects {
    repositories {
        google()
       // jcenter()
        mavenCentral()
    }
}

//这个是原有方法的clean 系统自带的
//task clean(type: Delete) {
//    delete rootProject.buildDir
//}

tasks{//这里面是重写系统的
	
	//所以要获取系统自带的,必须要在tasks 表达式里!
    val clean by registering(Delete::class) {
        delete(buildDir)
    }
}

//这是自定义的
task("add") {
    println("config ")
    this.doFirst {
		println("doFirst")
    }
    doLast {
        val num1 = 10
        val num2 = 20
        println("name is ${ext["name"]} ,sum=${num1 + num2}")
    }
}


//最新的定义 ext 变量
ext {
    //name = "hqk"
    set("name","hqk")
    set("kotlinVersion","1.5.0")
}
//最新的使用 ext 变量
ext["name"]

Android—Gradle教程(九)完结篇
如图所示

编译成功,执行右边的Task任务

运行效果:

> Configure project :
config 

> Task :add
doFirst
name is hqk ,sum=30

OK,现在根部build.gradle.kts已经迁移完毕,接着下一个!

2.2.3 迁移主model对应的build.gradle

创建对应的build.gradle.kts文件,当然也可以注释掉原文件里的代码,接着重命名加后缀!

Android—Gradle教程(九)完结篇
如图所示

现在Kotlin DSL已经完全引入进来了,有提示下写代码,而且还能看源码,简直爽的不要不要的。

之前Groovy没有任何提示,还不能看源码,痛苦面具直接带上。

吐槽了一番,继续回到正题上!

新model对应的build.gradle.kts

plugins{
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-android-extensions")
}

先写好脚本插件引入,然后点击右边的Sync Now,因为后面写的内容在com.android.application脚本插件里,所以要先引入才能往后编写!

编译完成后,继续改造:

新model对应的build.gradle.kts

plugins {
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-android-extensions")
}

android {
    compileSdkVersion(31)
    defaultConfig {
        applicationId = "com.hqk.kotlinstudy"
        minSdkVersion(28)
        targetSdkVersion(31)
        versionCode = 1
        versionName = "1.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
//        release {
//            zipAlignEnabled true
//        }
//        debug {
//            zipAlignEnabled true
//        }

        getByName("release") {
            zipAlignEnabled(true)
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }

        getByName("debug") {
            zipAlignEnabled(true)
        }

    }

    dexOptions {
        javaMaxHeapSize = "2048m"
        jumboMode = true//忽略方法数限制的检查

        //是否对依赖的库进行dex预处理来是你的增量构建更快速
        //因为这个特性可能会使你的clean构建变慢
        //因此在你的持续集成服务器上你可能想要关闭这个特性
        preDexLibraries = true

        //设置最大的线程数量使用,当运行dex-in-process时,默认是4
        maxProcessCount = 8
    }

    flavorDimensions.add("channel")
    productFlavors {

        create("dev") {
            dimension = "channel"
            resConfigs("en", "xxhdpi")
            resValue("string", "bbb", "nnnn")// 'string', 'bbb', 'nnnn'
            buildConfigField("Boolean", "UM_FLAG", "false")
        }

        create("prod") {
            dimension = "channel"
            buildConfigField("Boolean", "UM_FLAG", "false")
        }

    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation("androidx.appcompat:appcompat:1.2.0")
    implementation ("com.google.android.material:material:1.1.0")
    implementation ("androidx.constraintlayout:constraintlayout:1.1.3")
    testImplementation ("junit:junit:4.+")
    androidTestImplementation ("androidx.test.ext:junit:1.1.1")
    androidTestImplementation ("androidx.test.espresso:espresso-core:3.2.0")
    implementation ("androidx.core:core-ktx:+")
    implementation ("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.0")
}


afterEvaluate {

    val task = tasks.findByName("packageDebug")
    task?.doFirst {
        println("==============inputs.files====================")
        inputs.files.forEach {
            println(it)
        }
    }

    task?.doLast {
        println("==============outputs.files====================")
        outputs.files.forEach {
            println(it)
        }
    }

}

println(rootProject.ext["name"])

这里注意buildTypesproductFlavors这里,就只有这两处改动最大,其他的要么改为=;要么改为("xx")

至于什么时候用=,什么时候用("xx"),读者可以去看对应的源码!

当然也可选择尝试=不行,那就用("xx"),两个都不行,那就看源码!反正现在能够随时随地看源码的!

OK,现在来试试Build Apk运行一下,来看看效果:

Android—Gradle教程(九)完结篇
如图所示

已经完美运行成功了!已经成功的迁移到Kotlin DSL,爽歪歪!

最后再来个大大的总结!

3、Gradle专栏总结

本专栏需要掌握哪些知识点?

  1. Groovy语法(基础语法、闭包等)及动态特性 (MOP元编程)
  2. Gradle运行机制,生命周期,钩子函数
  3. .gradle脚本的本质就是project对象的操作。(根gradle脚本的不同,buildscript)
  4. Gradle提供的属性扩展。
  5. Gradle任务及插件的实现
  6. Gradle依赖管理,解决依赖冲突问题
  7. Gradle Plugin for Android:
    • defaultConfig,自定义产品风味及变种
    • productFlavors及构建维度
    • buildTypes、aaptOptions、dexOptions、compileOptions …
    • AGP的各种内置构建任务及特殊处理等等
  8. Groovy迁移Kotlin DSL

看看这些,读者们掌握了多少呢?

哈哈哈哈,到这!本专栏教程就已经完结了!当然光学会知识点并不能发挥其最大的作用!需要结合实战来淬炼Kotlin的知识点!

比如说:Tinker热更新、字节码插桩等等!都会用到Gradle相关知识点!在以后的例子肯定还会再见的!

好了,就这样了!不感叹了!从下一篇开始将开启Jetpack专栏!:>

上一篇:jQuery 筛选器2


下一篇:论文笔记之:Asynchronous Methods for Deep Reinforcement Learning