Kotlin DSL Gradle 指南(下篇)

打包

Gradle 作为构建工具,最重要的作用就是帮助我们编译打包 apk 的,apk 由各种文件组成,比如代码文件和资源文件,那其实可以理解为 Gradle 本质上是在帮我们管理这些散落在各处的文件。

签名配置

打包需要先配置签名信息,但是这些 AndroidStudio 都可以自动生成,几乎不用我们手动编写,下面来操作一下。

确认之后,在 app 模块的 gradle 中就会生成相关的代码。

kotlin 复制代码
android {
    signingConfigs {
        create("release") {
            storeFile = file("/home/lbrd/Downloads/xzjKeystore.jks")
            storePassword = "123456"
            keyAlias = "key0"
            keyPassword = "123456"
        }
    }
    ...
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            signingConfig = signingConfigs.getByName("release")
        }
    }
}

实际项目开发中一般不会直接把这些敏感信息写在这里,我们可以写在 gradle.properties 文件中,这里定义的属性都是全局的。

gradle.properties 中加入

kotlin 复制代码
storeFile=/home/lbrd/Downloads/xzjKeystore.jks
storePassword=123456
keyAlias=key0
keyPassword=123456

引用其中定义的变量

kotlin 复制代码
signingConfigs {
    create("release") {
        storeFile = file(project.findProperty("storeFile") as String)
        storePassword = project.findProperty("storePassword") as String
        keyAlias = project.findProperty("keyAlias") as String
        keyPassword = project.findProperty("keyPassword") as String
    }
}

可以借助 AndroidStudio 的 Gradle 工具执行打包,如下所示:

有些人的 AndroidStudio 可能会没有 Gradle Task 工具,这时需要在设置中打开,然后同步一下即可。

build 完之后,我们就可以在如下路径找到对应的 apk 了。

这个 apk 的文件名太简单了,没有辨识度,我们来改一下,在 android {...} 中添加如下配置:

kotlin 复制代码
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

android.applicationVariants.all {
    outputs.all {
        if (this is com.android.build.gradle.internal.api.ApkVariantOutputImpl) {
            val config = project.android.defaultConfig
            val versionName = config.versionName
            val formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmm")
            val createTime = LocalDateTime.now().format(formatter)
            this.outputFileName = "${project.name}_${this.name}_${versionName}_$createTime.apk"
        }
    }
}

再次打包就会看到自定义的文件名了

多渠道打包

当我们的应用需要上架不同的应用市场,不同的渠道需要不同的定制化时,就需要多渠道打包。

先定义个 flavorDimensions

Add Product Flavor,添加具体的渠道。

不同的渠道可以配置不同的内容

这里配置了两个渠道,下面来看看 AndroidStudio 自动生成的 Gradle 代码。

kotlin 复制代码
android {
    ...
    flavorDimensions += listOf("channel")
    productFlavors {
        create("huawei") {
            dimension = "channel"
            applicationId = "com.huawei.app"
        }
        create("xiaomi") {
            dimension = "channel"
            applicationId = "com.xiaomi.app"
        }
    }
}

配置之后,当我们执行打包的时候,就会有渠道的选择了。

这里全选,就会得到两个不同渠道的安装包文件夹。

在开发调试阶段,如果想直接运行成某个特定的渠道,而不是每次都打包所有的渠道,可以选择 Build Variant,这样调试的时候就会生成特定的渠道包了。

想要做不同渠道的定制化开发,就得在代码中获取到这些渠道,从而区别对待,可以通过 buildConfigField 修改 BulidConfig,BuildConfig 是在构建时自动生成的 Java 类,里面存放一些静态常量,编译后可以直接使用类中的常量。

kotlin 复制代码
android.buildFeatures.buildConfig = true

productFlavors {
    create("huawei") {
        dimension = "channel"
        applicationId = "com.huawei.app"
        buildConfigField("String", "CHANNEL_VALUE", ""huawei"")
    }
    create("xiaomi") {
        dimension = "channel"
        applicationId = "com.xiaomi.app"
        buildConfigField("String", "CHANNEL_VALUE", ""xiaomi"")
    }
}

然后直接通过 BuildConfig 获取即可

kotlin 复制代码
private fun getChannel() = BuildConfig.CHANNEL_VALUE

打包成 jar 和 aar

jar 包是 Java 平台的标准打包格式,只包含编译后的 .class 文件和资源文件,不包含 Android 资源文件,如布局,图片等。aar 包是 Android 平台的打包格式,可以包含编译后的 .class 文件和 Android 资源文件,如布局,图片等。 先创建一个 Android Library

直接打包这个 Module

aar 在这

jar 在这

有了 aar 和 jar 之后,就可以将其放入需要用到的 Module 中,可以放到 libs 文件夹中,然后引入即可,也可以发布到远程仓库,实现远程依赖。

kotlin 复制代码
implementation(files("libs/mylibrary-release.aar"))

我们可以修改生成的文件名和路径,创建一个 Task。

kotlin 复制代码
tasks.register<Copy>("createJar") {
    // 先删除原来的
    delete("libs/tool.jar")
    // 拷贝的源文件路径
    from("build/intermediates/aar_main_jar/release/")
    // 目标路径
    into("libs/")
    include("classes.jar")
    // 重命名
    rename("classes.jar", "tool.jar")
}
tasks.getByName("createJar").dependsOn("build")

执行这个 Task 即可

有些时候,需要把应用模块打包成 aar,我们得先把应用模块变成库模块,如下所示:

去掉之后 AndroidManifest 长这样

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application>
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:label="@string/app_name"
            android:theme="@style/Theme.ComposeApp" />
    </application>

</manifest>

同步一下,就由应用模块转变为库模块了,然后按照上面的方式打包即可。

自定义插件

Gradle 插件是一种用于扩展和定制 Gradle 构建系统行为的工具,负责处理 Android 应用程序的构建,打包,签名等任务,自定义插件只需实现 Plugin 接口,实现 apply 方法即可。

kotlin 复制代码
open class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        println("apply")
    }
}

使用 apply 函数将插件应用到项目或模块中

kotlin 复制代码
apply<MyPlugin>()

我们可以发现,在实现的 apply 方法中,有个 Project 对象,而 task 是 Project 中的一个方法,所以也可以通过这个 Project 对象去创建 Task。

kotlin 复制代码
open class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.task("pluginTask") {
            doLast {
                println("running pluginTask")
            }
        }
    }
}

Plugin 接口的 apply 方法在编译阶段就会执行,所以在 sync 执行构建后就会有输出。上面的都只是输出,乍一看似乎并没有什么实际的作用,那这里就再举个实例,一个压缩文件的自定义插件。

kotlin 复制代码
apply<ZipPlugin>()

open class ZipPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        target.afterEvaluate {
            val textFile = File(project.rootDir, "myFile.txt")
            project.task("zip", Zip::class) {
                archiveFileName.set("xzj.zip") //设置压缩文件名
                destinationDirectory.set(File("${target.buildDir}/custom")) //设置输出目录
                from(textFile) //将文件添加到压缩包中
            }
        }
    }
}

执行 Task,就可以在对应的目录上看到该压缩文件。

上面所说的插件都属于二进制插件,Gradle 还有种脚本插件,下面我们来创建一个脚本插件,也可以新建一个 gradle 文件,防止 build.gradle.kts 中代码臃肿。

kotlin 复制代码
afterEvaluate {
    task("zip", Zip::class) {
        val textFile = File(project.rootDir, "myFile.txt")
        archiveFileName.set("xzj.zip")
        destinationDirectory.set(File("${project.buildDir}/custom"))
        from(textFile)
    }
}

引入脚本插件跟二进制插件的方式不太一样,该参数是外部 Gradle 脚本的路径,Project 下的 build.gradle.kts 中引入该插件,如下所示:

kotlin 复制代码
apply("plugin.gradle.kts")

如果路径不正确的话,会因为找不到而编译出错。比方说我们需要在 app 下的 build.gradle.kts 引入该插件,就应该这样:

kotlin 复制代码
apply("../plugin.gradle.kts")

扩展插件

在 android {...} 闭包里有各类配置,如 minSdk,targetSdk,versionCode 等等,我们也可以通过扩展插件来实现自定义配置。

定义一些扩展属性

kotlin 复制代码
open class Car {
    var name: String = "BYD"
    var type: String = "ocean"
}

在 plugin 中使用扩展属性

kotlin 复制代码
class MyPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        val extension = target.extensions.create("test", Car::class.java)
        target.task("MyTask") {
            doLast {
                println("name: ${extension.name}")
                println("type: ${extension.type}")
            }
        }
    }
}

然后将插件引入

kotlin 复制代码
apply<MyPlugin>()

其实这还不够,我们先来看看 android{...} 这个闭包是怎么实现的?

kotlin 复制代码
fun org.gradle.api.Project.`android`(configure: Action<com.android.build.gradle.internal.dsl.BaseAppModuleExtension>): Unit =
    (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", configure)

这里我们就仿照这个,定义一个扩展函数,用于配置自定义属性。

kotlin 复制代码
fun Project.customConfig(action: Car.() -> Unit) {
    extensions.configure("test", action)
}

这里需要注意的是,extensions.configure 的 name,需要对应上面 extensions.create 的 name,不然会报错: Extension does not exist

这样就可以像配置 android{...} 一样,去配置我们的自定义属性了。

kotlin 复制代码
customConfig {
    name = "xiaomi"
    type = "SU7"
}
相关推荐
zhangphil7 小时前
Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆形图实现,Kotlin(2)
android·kotlin
拓端研究室10 小时前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil11 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
_Shirley1 天前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
小白学大数据1 天前
高级技术文章:使用 Kotlin 和 Unirest 构建高效的 Facebook 图像爬虫
爬虫·数据分析·kotlin
guitarjoy2 天前
Kotlin - 协程结构化并发Structured Concurrency
kotlin·协程·coroutinescope·结构化同步
zhangphil2 天前
Android使用PorterDuffXfermode模式PorterDuff.Mode.SRC_OUT橡皮擦实现“刮刮乐”效果,Kotlin(2)
android·kotlin
居居飒3 天前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
刘争Stanley4 天前
如何高效调试复杂布局?Layout Inspector 的 Toggle Deep Inspect 完全解析
android·kotlin·android 15·黑屏闪屏白屏
sickworm陈浩4 天前
Java 转 Kotlin 系列:究竟该不该用 lateinit?
android·kotlin