Kotlin DSL Gradle 指南

本文是关于 Kotlin DSL Gradle 的指南(上篇),介绍了 Gradle 作为 Android 开发构建工具的作用及优势,包括初始配置、生命周期、依赖管理、Task 相关内容。如 Task 的创建、自定义、各种方法和属性,以及文件操作等,还提到了解决依赖冲突的方式。

Gradle 是用于 Android 应用程序开发的构建工具,可以帮助开发者管理项目的编译,打包,签名等任务,同时支持模块化开发,定制构建流程和多种签名配置,简化了项目管理和提高了开发效率。现在 Android 官方已经默认推荐使用 Kolin DSL 来构建 Gradle 了,那就一起来瞧瞧吧!

image.png

初始配置

新建一个项目,看看初始配置。

image.png

我们从上到下来看一看,相关配置的说明都在注释里。

Module 下的 build.gradle.kts

plugins {

id("com.android.application")

id("org.jetbrains.kotlin.android")

}

android {

//应用程序的命名空间,主要用于访问应用程序资源。

namespace = "com.xzj.myapp"

//编译所依赖的 Android SDK 版本。

compileSdk = 33

defaultConfig { //APP 的唯一标识

applicationId = "com.xzj.myapp"

//支持的最低 API Level,如指定23,表示低于 Android 6.0 的机型不能使用这个 APP。

minSdk = 23

//基于哪个 Android 版本开发的,如指定33,代表适配到 Android 13。

targetSdk = 33

//版本号

versionCode = 1

//版本名称

versionName = "1.0"

//指定运行测试时要使用的 InstrumentationRunner,AndroidJUnitRunner 是 Android 测试框架中的一个组件,用于执行针对 Android 应用程序的测试。

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

//表示在项目中使用支持库来处理矢量图像

vectorDrawables { useSupportLibrary = true }

}

//构建类型,主要用于打包。

buildTypes {

release {

isMinifyEnabled = false

proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" )

}

}

//指定 Java 环境版本

compileOptions {

sourceCompatibility = JavaVersion.VERSION_1_8

targetCompatibility = JavaVersion.VERSION_1_8

}

//指定 JVM 环境

kotlinOptions {

jvmTarget = "1.8"

}

//是否启用 compose

buildFeatures { compose = true }

//指定了 Compose 所使用的 Kotlin 编译器扩展的版本

composeOptions { kotlinCompilerExtensionVersion = "1.4.3" }

//表示在打包过程中排除项目中位于 "/META-INF/AL2.0" 和 "/META-INF/LGPL2.1" 路径下的资源文件

packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } }

//依赖

dependencies {

implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation("androidx.activity:activity-compose:1.7.0") implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest")

}

gradle-wrapper.properties

#Thu Mar 21 18:07:31 CST 2024 #Gradle 压缩包解压后的主目录 distributionBase=GRADLE_USER_HOME 
#Gradle 压缩包解压后的具体路径 distributionPath=wrapper/dists 
#Gradle 的下载地址 distributionUrl=https://services.gradle.org/distributions/gradle-8.0-bin.zip 
#存放 Gradle zip 压缩包的主目录。 zipStoreBase=GRADLE_USER_HOME 
#存放 Gradle zip 压缩包的具体路径 zipStorePath=wrapper/dists 

Project 下的 build.gradle.kts

//使用 Kotlin DSL 配置 Gradle 插件 
plugins { 
id("com.android.application") version "8.1.1" apply false 
id("org.jetbrains.kotlin.android") version "1.8.10" apply false 
} 

gradle.properties

#设置 JVM 的最大堆内存为 2048MB,指定了文件编码为 UTF-8。 
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 
#指示项目使用 AndroidX 库 
android.useAndroidX=true 
#指定 Kotlin 代码风格的规范,official 表示采用官方推荐的代码风格。 
kotlin.code.style=official 
#用于控制R类是否会传递依赖,当为 true 时,表示类不会被传递到依赖模块中,每个模块会有自己独立的 R 类。 
android.nonTransitiveRClass=true 

local.properties

## This file is automatically generated by Android Studio. 
# Do not modify this file -- YOUR CHANGES WILL BE ERASED! 
# # This file should *NOT* be checked into Version Control Systems, 
# as it contains information specific to your local configuration. 
# # Location of the SDK. This is only used by Gradle. 
# For customization when using a Version Control System, please read the 
# header note. sdk.dir=/opt/android/sdk 

settings.gradle.kts

//插件管理,指定插件下载的仓库。 
pluginManagement { 
repositories { 
google() 
mavenCentral() 
gradlePluginPortal() 
} 
} 
//依赖管理,指定依赖库下载的仓库,此仓库是有顺序的,顺序决定先从哪个仓库下载。 
dependencyResolutionManagement { 
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 
repositories { 
google() 
mavenCentral() 
} 
} 
//项目名称 rootProject.name = "MyApp" 
//指定参与构建的模块,一个根工程可以有很多的 module,只有在这设置了,gradle 才会去识别,才会在构建的时候被包含进去。 
include(":app") 

生命周期

Task 是 Gradle 构建的核心,生命周期的意义就是在各个阶段把 Task 组合起来,按照我们的意图去构建项目。 Gradle 的生命周期可以分为三个阶段:

  1. 初始化阶段:Gradle 支持单项目和多项目构建,在初始化阶段,Gradle 确定哪些项目将参与构建,并为每个项目创建 Project 实例,比如解析 settings.gradle 文件,以此来决定是单项目构建还是多项目构建。
  2. 配置阶段:解析每个工程的 build.gradle 文件,创建要执行的任务子集和确定各种任务之间的关系,并对任务做一些初始化配置。
  3. 运行阶段:Gradle 根据配置阶段创建和配置的要执行的任务子集,执行任务。

Gradle 也可以监听各个阶段的回调

gradle.addProjectEvaluationListener(object : ProjectEvaluationListener {

//项目配置被评估之前触发,在这个阶段,Gradle 将会加载项目的构建脚本并准备执行项目的配置。

override fun beforeEvaluate(project: Project) {

println("beforeEvaluate")

}

//项目配置被评估之后触发,在这个阶段,所有的项目配置已经被加载和评估完成。

override fun afterEvaluate(project: Project, state: ProjectState) { println("afterEvaluate")

}

})

当项目配置被评估完成后,Gradle 就可以准确地知道项目的结构和需要执行的任务,从而能够开始真正的构建过程。因此,在项目配置被评估之前和之后,你可以通过监听相应的事件,在项目配置生命周期的不同阶段执行自定义的逻辑。

gradle.addBuildListener(object : BuildListener {

override fun settingsEvaluated(settings: Settings) {

println("settingsEvaluated")

}

override fun projectsLoaded(gradle: Gradle) {

println("projectsLoaded")

}

override fun projectsEvaluated(gradle: Gradle) {

println("projectsEvaluated")

}

override fun buildFinished(result: BuildResult) {

println("buildFinished")

}

})

Gradle 是构建工具本身,而 gradlew 是 Gradle Wrapper 提供的便捷工具,用于在项目中管理和执行 Gradle 构建任务。在 Android 项目中,通常使用 Gradle Wrapper 来统一团队的构建环境,简化项目的配置和维护。

依赖管理

Gradle 提供了强大的依赖管理支持,我们只需声明依赖项即可,Gradle 会帮我们找到所需的 Library,例如依赖项配置和依赖仓库。

dependencies {

implementation("androidx.core:core-ktx:1.9.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") implementation("androidx.activity:activity-compose:1.7.0") implementation(platform("androidx.compose:compose-bom:2023.03.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00")) androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest")

}

 

pluginManagement {

repositories {

google()

mavenCentral()

gradlePluginPortal()

}

}

dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)

repositories {

google()

mavenCentral()

}

}

需要注意的是,implementation 表示当前引用的第三方库仅限于本 Module 使用,api 表示的依赖是可以传递的。

Gradle 依赖管理进行了优化,如果项目存在同一个依赖库的多个版本,默认选择最高版本,Gradle 会自动排除重复的依赖,这在一定程度上防止了依赖冲突,但是为什么有时还是会遇到依赖冲突的问题呢?举个例子,某些第三方库可能会包含特定版本的某个库,并且你自己的项目中也引入了该库的不同版本,这种情况下,Gradle 会自动选择最高的版本,就可能发生依赖冲突。可以通过如下两种方式解决依赖冲突:

使用 exclude 排除依赖

implementation("com.google.android.exoplayer:exoplayer:2.14.0") {

exclude(group = "com.google.guava", module = "guava")

}

使用强制版本

configurations.all {

resolutionStrategy {

force("com.google.android.exoplayer:exoplayer:2.14.0")

}

}

Task

Task 是 Gradle 构建的核心对象,我们可以直接在 build.gradle 文件中创建。

创建 Task

tasks.register("xzj") { println("create task") }

也可以这样创建

task("xzj") { println("create task") }

这里需要注意的是:如果有 Task 同名的话,会编译失败。

自定义 Task

继承 DefaultTask,Action 的方法需要添加 @TaskAction 注解。

open class MyTask : DefaultTask() {

@get:Internal var taskName = "taskName"

@TaskAction fun action1() { println("action1: $taskName") }

@TaskAction fun action2() { println("action2: $taskName") }

@TaskAction fun action3() { println("action3: $taskName") }

}

注册自定义的 Task

tasks.register<MyTask>("myTask") { taskName = "xzj" }

然后我们就可以在 AndroidStudio 的 Gradle 工具面板,Tasks -> other 里找到这个 Task,双击执行即可。

image.png

如果是带构造函数传参的话,可以这样

open class MyTask @Inject constructor(private var taskName: String) : DefaultTask() { @TaskAction fun action1() {

println("action1: $taskName")

}

@TaskAction fun action2() {

println("action2: $taskName")

}

@TaskAction fun action3() {

println("action3: $taskName")

}

}

tasks.register<MyTask>("myTask","argument")

使用 @Inject 注解可以帮助 Gradle 正确地理解带参数的构造函数,并且在创建任务实例时能够正确地调用带参数的构造函数。

doFirst 和 doLast

使用了 doFirst 和 doLast 方法来分别添加在任务执行前和执行后需要执行的动作。

task("hello") {

doFirst { println("doFirst1") }

doFirst { println("doFirst2") }

println("hello")

doLast { println("doLast1") }

doLast { println("doLast2") }

}

执行这个 Task,输出如下:

image.png

doFirst 是倒序执行,doLast 是正序执行,Action 也是正序执行。

当你定义一个任务时,任务的配置代码会在 Gradle 构建脚本解析执行阶段就会执行,而不是在任务实际执行时才执行。因此,当 Gradle 解析执行到 println("hello") 这行代码时,会立即执行该代码并先打印出 hello。

dependsOn

dependsOn 用来定义任务之间的依赖关系,通过使用 dependsOn,可以确保一个任务在另一个任务之前执行,从而控制任务的执行顺序。举个例子,有两个任务 A 和 B,想让任务 B 在任务 A 执行完成后再执行,这样干:

tasks.register("taskA") {

doLast { println("Running task A") }

}

tasks.register("taskB") {

dependsOn("taskA")

doLast { println("Running task B") }

}

执行 taskB 会先执行 taskA

image.png

finalizedBy

dependsOn 指定的是上一个任务,而 finalizedBy 指定下一个任务,比如想让任务 A 执行后,再去执行任务 B,这样干:

tasks.register("taskA") {

finalizedBy("taskB")

doLast { println("Running task A")

}

}

tasks.register("taskB") {

doLast { println("Running task B")

}

}

onlyIf

onlyIf 是一个条件断言方法,用于指定任务是否应该执行的条件。当条件为 true 时,任务才会执行,否则任务将被跳过。

tasks.register("taskA") {

val flag = providers.gradleProperty("flag")

onlyIf {

//如果属性存在,则返回 true

flag.isPresent

}

doLast { println("Running A") } }

enabled

enable 是 Task 的开关,禁用之后任何操作都不会执行,可以实现和 onlyIf 一样的效果。

tasks.register("taskA") { enabled = true doLast { println("Running A") } }

查找 Task

tasks.findByName("taskA")?.doFirst { println("taskA findByName") }

tasks.findByPath("taskA")?.doFirst { println("taskA findByPath") }

tasks.named("taskA") { doLast { println("taskA named") } }

文件操作

open class WriteFileTask : DefaultTask() {

//任务输入参数 @Input var text = ""

//任务输出文件

@OutputFile var outputFile: File? = null

//任务运行时调用的方法

@TaskAction fun writeText() {

outputFile?.createNewFile()

outputFile?.writeText(text)

}

}

tasks.register("writeFileTask", WriteFileTask::class) {

text = "content"

outputFile = File(projectDir, "myFile.txt")

}

这里创建了一个写文件的 Task,执行之后就可以在相应的目录中看到创建的文件。

image.png

下面主要介绍了 Kotlin DSL Gradle 的相关内容,包括打包(apk 打包的流程、签名配置、多渠道打包、文件名修改)、打包成 jar 和 aar 及其应用、自定义插件(实现 Plugin 接口和创建 Task)、扩展插件(定义和使用扩展属性)。

打包

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

签名配置

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

image.png

image.png

image.png

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

 

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 中加入

storeFile=/home/lbrd/Downloads/xzjKeystore.jks storePassword=123456 keyAlias=key0 keyPassword=123456

引用其中定义的变量

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 工具执行打包,如下所示:

image.png

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

image.png

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

image.png

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

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"

}

}

}

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

image.png

多渠道打包

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

先定义个 flavorDimensions

image.png

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

image.png

image.png

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

image.png

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

android {

...

flavorDimensions += listOf("channel")

productFlavors {

create("huawei") {

dimension = "channel"

applicationId = "com.huawei.app"

}

create("xiaomi") {

dimension = "channel"

applicationId = "com.xiaomi.app"

}

}

}

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

image.png

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

image.png

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

image.png

image.png

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

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 获取即可

private fun getChannel() = BuildConfig.CHANNEL_VALUE

打包成 jar 和 aar

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

image.png

直接打包这个 Module

image.png

aar 在这

image.png

jar 在这

image.png

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

implementation(files("libs/mylibrary-release.aar"))

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

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 即可

image.png

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

image.png

image.png

去掉之后 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 方法即可。

open class MyPlugin : Plugin<Project> {

override fun apply(target: Project) {

println("apply")

}

}

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

apply<MyPlugin>()

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

open class MyPlugin : Plugin<Project> {

override fun apply(target: Project) {

target.task("pluginTask") {

doLast { println("running pluginTask")

}

}

}

}

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

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,就可以在对应的目录上看到该压缩文件。

image.png

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

image.png

 

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 中引入该插件,如下所示:

apply("plugin.gradle.kts")

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

apply("../plugin.gradle.kts")

扩展插件

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

定义一些扩展属性

open class Car {

var name: String = "BYD"

var type: String = "ocean"

}

在 plugin 中使用扩展属性

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}")

}

}

}

}

然后将插件引入

apply<MyPlugin>()

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

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)

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

fun Project.customConfig(action: Car.() -> Unit) {

extensions.configure("test", action)

}

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

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

customConfig { name = "xiaomi" type = "SU7" }

Android KTS多渠道打包详解
在Android应用的开发过程中,尤其是在发布时,很常用到多渠道打包的技术。它能够帮助我们一次性构建多个版本的APK,这对于发布到各个应用市场非常有帮助。今天,我将带你一起探索Android KTS(Kotlin DSL)多渠道打包的整个流程。

一、流程概述
多渠道打包的流程大致可以分为以下几个步骤:

步骤                               描述
1. 配置build.gradle        配置多渠道相关属性
2. 创建渠道信息文件     列出所有渠道的信息
3. 编写打包逻辑            使用Kotlin DSL编写多渠道打包逻辑
4. 执行打包                   运行打包命令
5. 检查输出                   输出APK路径,检查各渠道 APK
二、详细步骤
1. 配置build.gradle
首先,我们需要在项目的build.gradle文件中进行多渠道打包的基本配置。以下是一个示例:

android {
    // 定义compileSdkVersion
    compileSdkVersion(31)
    
    defaultConfig {
        applicationId = "com.example.myapp"
        versionCode = 1
        versionName = "1.0"
        
        // 配置渠道分包设置
        flavorDimensions("default")
        productFlavors {
            create("google") {
                // Google渠道的种种配置
                applicationIdSuffix = ".google"
                versionNameSuffix = "-google"
            }
            create("huawei") {
                // 华为渠道的种种配置
                applicationIdSuffix = ".huawei"
                versionNameSuffix = "-huawei"
            }
        }
    }
}

代码注释:此代码展示了在build.gradle中如何定义渠道。我们使用productFlavors创建多个渠道,分别指定了不同的applicationIdSuffix和versionNameSuffix。

2. 创建渠道信息文件
接下来,我们需要创建一个用于存放渠道信息的文件。这个文件通常是一个JSON格式的文件。你可以创建一个名为channels.json的文件,内容如下:

{
  "channels": [
    "google",
    "huawei",
    "xunlei",
    "xiaomi"
  ]
}
代码注释:该JSON文件列出了所有的渠道名称,后续将会用于生成不同的APK。

3. 编写打包逻辑
在Kotlin DSL中编写打包逻辑,可以通过 build.gradle.kts 文件完成。以下是详细示例:

import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import org.gradle.api.DefaultTask
import org.gradle.kotlin.dsl.extra

// 自定义任务:多渠道打包
tasks.register<DefaultTask>("multiChannelBuild") {
    group = "build"
    description = "Builds multiple APKs for each flavor in channels.json"
    
    // 设置任务的执行逻辑
    doLast {
        // 读取渠道信息文件
        val channelsFile = file("path/to/channels.json")
        val channelsJsonString = channelsFile.readText()
        val channels = parseChannels(channelsJsonString)  // 假定parseChannels是解析JSON的函数
        
        // 遍历每个渠道
        channels.forEach { channel ->
            exec {
                val variant = "assemble${channel.capitalize()}"
                commandLine("gradlew", variant)
            }
        }
    }
}

// 解析JSON的方法
fun parseChannels(json: String): List<String> {
    // 实现你的JSON解析逻辑
}

代码注释:上面代码定义了一个Gradle任务multiChannelBuild,它会读取渠道信息,并依次为每个渠道执行打包命令。你需要实现parseChannels方法来解析JSON文件。

4. 执行打包
完成上述步骤后,你只需在命令行中运行以下命令来启动打包:

./gradlew multiChannelBuild

代码注释:此命令启动了我们定义的multiChannelBuild任务,生成各个渠道的APK文件。

5. 检查输出
打包完成后,APK文件将会在app/build/outputs/apk目录下生成。你可以检查每个渠道对应的APK是否生成成功。

三、旅行图与序列图
接下来,我们用图形方式帮助理解整个打包流程。

1. 旅行图


2. 序列图

在本文中,我们详细介绍了如何使用Kotlin DSL在Android中实现多渠道打包。你了解了每一步所需的配置及代码,它们如何共同工作以生成多个APK文件。

希望这篇文章能够帮助你更好地理解Android多渠道打包的原理和实现。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/62125.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库导论

data 数据是数据库中存储的基本数据&#xff0c;描述事物的符号称为数据。 DB 数据库是长期存储在计算机内&#xff0c;有组织&#xff0c;可共享的大量数据的集合。数据库中的数据按照一定的数据模型组织&#xff0c;描述和存储&#xff0c;具有较小的冗余度&#xff0c;较…

HTML实现 扫雷游戏

前言&#xff1a; 游戏起源与发展 扫雷游戏的雏形可追溯到 1973 年的 “方块&#xff08;cube&#xff09;” 游戏&#xff0c;后经改编出现了 “rlogic” 游戏&#xff0c;玩家需为指挥中心探出安全路线避开地雷。在此基础上&#xff0c;开发者汤姆・安德森编写出了扫雷游戏的…

Spring Boot英语知识网站:开发策略

5系统详细实现 5.1 管理员模块的实现 5.1.1 用户信息管理 英语知识应用网站的系统管理员可以对用户信息添加修改删除以及查询操作。具体界面的展示如图5.1所示。 图5.1 用户信息管理界面 5.1.2 在线学习管理 系统管理员可以对在线学习信息进行添加&#xff0c;修改&#xff0…

HTML5和CSS3新增特性

HTML5的新特性 HTML5新增的语义化标签 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 IE9 以上版本的浏览器才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量…

width设置100vh但出现横向滚动条的问题

在去做flex左右固定,中间自适应宽度的布局时, 发现这样一个问题: 就是我明明是宽度占据整个视口, 但是却多出了横向的滚动条 效果是这样的 把width改成100%,就没有滚动条了 原因: body是有默认样式的, 会有一定的默认边距, 把默认边距清除就是正常的了 同时, 如果把高度设…

EasyExcel: 结合springboot实现表格导出入(单/多sheet), 全字段校验,批次等操作(全)

全文目录,一步到位 1.前言简介1.1 链接传送门1.1.1 easyExcel传送门 2. Excel表格导入过程2.1 easyExcel的使用准备工作2.1.1 导入maven依赖2.1.2 建立一个util包2.1.3 ExcelUtils统一功能封装(单/多sheet导入)2.1.4 ExcelDataListener数据监听器2.1.5 ResponseHelper响应值处理…

css:转换

转换 移动 /* transform: translate(100px, 200px); */transform: translateX(100px);transform: translateY(100px); /*一个意思*/ 如果后面跟百分数的意思是移动盒子自身x/y方向长度的百分比&#xff0c;可以用作子绝父相控制盒子水平居中垂直居中 translate里的xy值是相对…

webp 网页如何录屏?

工作中正好研究到了一点&#xff1a;记录下这里&#xff1a; 先看下效果&#xff1a; 具体实现代码&#xff1a; &#xfeff; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…

SpringCloud Gateway转发请求到同一个服务的不同端口

SpringCloud Gateway默认不支持将请求路由到一个服务的多个端口 本文将结合Gateway的处理流程&#xff0c;提供一些解决思路 需求背景 公司有一个IM项目&#xff0c;对外暴露了两个端口8081和8082&#xff0c;8081是springboot启动使用的端口&#xff0c;对外提供一些http接口…

SlickGrid复选框

分析 1、先在columns首列添加复选框&#xff1b; 2、在SlickGrid注册刚添加的复选框&#xff1b; 3、添加复选框变化事件&#xff1b; 4、注册按钮点击事件&#xff0c;点击获取已选中的行。 展示 代码 复选框样式&#xff08;CSS&#xff09; .slick-cell-checkboxsel {bac…

摄像头原始数据读取——V4L2(userptr模式,V4L2_MEMORY_USERPTR)

摄像头原始数据读取——V4L2(userptr模式,V4L2_MEMORY_USERPTR) 用户指针方式允许用户空间的应用程序分配内存&#xff0c;并将内存地址传递给内核中的驱动程序。驱动程序直接将数据填充到用户空间的内存中&#xff0c;从而避免了数据的拷贝过程。 流程&#xff1a; 通过VIDI…

浏览器缓存与协商缓存

1. 强缓存&#xff08;Strong Cache&#xff09; 定义 强缓存是指在缓存的资源有效期内&#xff0c;浏览器会直接使用缓存中的数据&#xff0c;而不会发起网络请求。也就是说&#xff0c;浏览器会直接从本地缓存读取资源&#xff0c;不会与服务器进行任何交互。 如何控制强缓…

AI 写作(一):开启创作新纪元(1/10)

一、AI 写作&#xff1a;重塑创作格局 在当今数字化高速发展的时代&#xff0c;AI 写作正以惊人的速度重塑着创作格局。AI 写作在现代社会中占据着举足轻重的地位&#xff0c;发挥着不可替代的作用。 随着信息的爆炸式增长&#xff0c;人们对于内容的需求日益旺盛。AI 写作能够…

RabbitMQ 篇-深入了解延迟消息、MQ 可靠性(生产者可靠性、MQ 可靠性、消费者可靠性)

??博客主页&#xff1a;【_-CSDN博客】** 感谢大家点赞??收藏评论** 文章目录 ???1.0 RabbitMQ 的可靠性 ? ? ? ? 2.0 发送者的可靠性 ? ? ? ? 2.1 生产者重试机制 ? ? ? ? 2.2 生产者确认机制 ? ? ? ? 2.2.1 开启生产者确认机制 ? ? ? ? 2.2…

问:SpringBoot核心配置文件都有啥,怎么配?

在SpringBoot的开发过程中&#xff0c;核心配置文件扮演着至关重要的角色。这些文件用于配置应用程序的各种属性和环境设置&#xff0c;使得开发者能够灵活地定制和管理应用程序的行为。本文将探讨SpringBoot的核心配置文件&#xff0c;包括它们的作用、区别&#xff0c;并通过…

【机器学习】数据集合集!

本文将为您介绍经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 privacy 更新时间&#xff1a;2024-11-26 访问地址: GitHub 描述&#xff1a; 此存储库包含 TensorFlow Privacy&#xff08;一种 Python&#xff09;的源代码 库&#xff0c;其中包…

Linux V4L2框架介绍

linux V4L2框架介绍 V4L2框架介绍 V4L2&#xff0c;全称Video for Linux 2&#xff0c;是Linux操作系统下用于视频数据采集设备的驱动框。它提供了一种标准化的方式使用户空间程序能够与视频设备进行通信和交互。通过V4L2接口&#xff0c;用户可以方便地实现视频图像数据的采…

[网安靶场] [更新中] UPLOAD LABS —— 靶场笔记合集

GitHub - c0ny1/upload-labs: 一个想帮你总结所有类型的上传漏洞的靶场一个想帮你总结所有类型的上传漏洞的靶场. Contribute to c0ny1/upload-labs development by creating an account on GitHub.https://github.com/c0ny1/upload-labs 0x01&#xff1a;UPLOAD LABS 靶场初识…

SpringBoot社团管理:用户体验优化

3系统分析 3.1可行性分析 通过对本社团管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本社团管理系统采用SSM框架&#xff0c;JAVA作为开发语言&#…

org.apache.log4j的日志记录级别和基础使用Demo

org.apache.log4j的日志记录级别和基础使用Demo&#xff0c;本次案例展示&#xff0c;使用是的maven项目&#xff0c;搭建的一个简单的爬虫案例。里面采用了大家熟悉的日志记录插件&#xff0c;log4j。来自apache公司的开源插件。 package com.qian.test;import org.apache.log…