使用 Amper 管理 KMP 应用
文章目录
我假设你已经阅读了 环境配置与运行 并完成了相关环境变量的配置。本文将深入探讨如何打包 KMP 应用。
根据官方文档介绍,JetBrains 推出了一个全新的构建工具 Amper ,可以统一处理构建、打包和发布的全流程:
它是一个以 Gradle 为后端,以 YAML 配置文件为前端的强大工具。它能大大简化应用的配置流程,提高开发效率。
可以查看 Amper 的文档: 来了解更多细节。
新建项目
由于 Amper 目前仍处于活跃开发阶段,文档和功能都在不断更新。对于新项目来说,使用最新的工具链会更加便捷。
因此,我建议使用 KMP Wizard 来创建新项目:
- 在模板库(Template Gallery)中选择 "Shared UI Mutilplatform App"
下载并解压项目文件后,使用 Android Studio 打开它。首先需要同步 Gradle 项目:
项目结构解析
这是一个标准的 Gradle 项目结构,主要包含以下核心文件夹:
目前项目包含了 Android 和 iOS 两个平台的支持,我们接下来将添加桌面端支持。
让我们先了解一下几个关键文件夹的作用:
module.yaml
:子项目的模块配置文件src
目录:存放源代码,其中每个子模块都包含 Kotlin 文件。值得注意的是,Kotlin 代码可以与平台原生代码(如 Swift)实现互操作
扩展项目:添加桌面端支持
让我们先明确一点:Amper 的设计目标是简化配置流程,统一桌面端和移动端的配置项,从而降低项目管理的复杂度。它的强大之处在于不仅支持 KMP 项目,还完整支持纯 JVM 应用开发。
添加桌面端模块
- 创建
jvmApp
目录 - 添加以下文件:
module.yaml
配置文件src/main.kt
源代码文件
配置文件内容:
1product: jvm/app
初始源代码:
1fun main() {
2 println("Hello, world!")
3}
接下来在 settings.gradle.kts
中引入新模块:
1include(":jvmApp")
完成后,执行项目同步:
同步成功后,你会看到 jvmApp 项目的图标发生变化:
现在我们就可以运行 main.kt
了。
添加项目依赖
在 Amper 中,JVM 项目的依赖管理变得更加简洁。我们不需要修改 Gradle 文件,只需要更新 module.yaml
即可:
1product: jvm/app
2
3dependencies:
4 - org.jetbrains.kotlinx:kotlinx-datetime:0.4.0 # 添加这行
同步项目后,我们就可以使用新添加的依赖了。让我们修改 main.kt
来测试一下:
1import kotlinx.datetime.Clock
2import kotlinx.datetime.TimeZone
3import kotlinx.datetime.toLocalDateTime
4
5fun main() {
6 println("Hello, KMP!")
7 println("It's ${Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault())} here")
8}
运行后,你将看到输出当前时间:
开始打包应用
在 yaml 文件中添加必要的配置项:
1settings:
2 kotlin:
3 languageVersion: 1.8
4 jvm:
5 release: 17
6
7packaging:
8 - type: fatJar
执行项目同步后,通过命令行或在 IDE 中找到打包任务 jvmApp:distZip:
1./gradlew jvmApp:distZip
打包完成后,在 jvmApp/build/distributions
目录中可以找到生成的 zip 文件。
文件大小大约在 1.8MB 左右,这说明它是一个不包含 JVM 运行时的精简包。
解压文件并查看其内容:
1$ tree jvmApp/build/distributions/jvmApp
2jvmApp/build/distributions/jvmApp
3├── bin
4│ ├── jvmApp
5│ └── jvmApp.bat
6└── lib
7 ├── annotations-13.0.jar
8 ├── jvmApp-jvm.jar
9 ├── kotlin-stdlib-2.0.21.jar
10 └── kotlinx-datetime-jvm-0.4.0.jar
11
123 directories, 6 files
运行打包好的应用:
1cd jvmApp/build/distributions/jvmApp
2./bin/jvmApp
添加 KMP 到项目中
现在让我们配置 Compose Multiplatform:
1product: jvm/app
2
3dependencies:
4 # ...other dependencies...
5
6 # add Compose dependencies
7 - $compose.foundation
8 - $compose.material3
9 - $compose.desktop.currentOs
10
11settings:
12 # ...other settings...
13
14 # enable the Compose framework toolchain
15 compose:
16 enabled: true
接着修改 main.kt:
1import androidx.compose.foundation.text.BasicText
2import androidx.compose.ui.window.Window
3import androidx.compose.ui.window.application
4
5fun main() = application {
6 Window(onCloseRequest = ::exitApplication) {
7 BasicText("Hello, World!")
8 }
9}
从这里开始,后续修改时不再需要同步 gradle 项目。
运行项目结果如下:
引入 shared 模块
首先在 jvmApp 的配置中引入 shared 模块作为依赖:
1dependencies:
2 - ../shared
然后修改 main.kt,引入 shared 模块中定义的 App 类:
1import androidx.compose.ui.window.Window
2import androidx.compose.ui.window.application
3import com.jetbrains.kmpapp.App
4
5fun main() {
6 application {
7 Window(onCloseRequest = ::exitApplication) {
8 App()
9 }
10 }
11}
错误1: KoinApplication has not been started
这个错误提示我们需要先启动 KoinApplication。
Koin 是一个轻量级的依赖注入框架,由纯 Kotlin 编写,非常适合在 KMP 项目中使用。
查看项目中的 Koin 初始化方式,我们可以在 iOS 模块中找到相关实现:
其中调用了 initKoin() 方法:
1fun initKoin() {
2 startKoin {
3 modules(
4 dataModule,
5 screenModelsModule,
6 )
7 }
8}
将这个初始化代码添加到 main.kt 中:
1fun main() {
2 initKoin()
3 application {
4 Window(onCloseRequest = ::exitApplication) {
5 App()
6 }
7 }
8}
错误2: Failed to find HTTP client engine
查看控制台输出:
1Caused by: java.lang.IllegalStateException: Failed to find HTTP client engine implementation in the classpath: consider adding client engine dependency. See https://ktor.io/docs/http-client-engines.html
Ktor 是一个轻量级的 HTTP 客户端框架,它定义了通用的协议标准,允许每个平台选择最适合的具体实现。不同的客户端实现在功能支持上各有特点,比如对 WebSocket、HTTP/2 等特性的支持程度就不尽相同:
这里我们选择使用 OkHttp 作为实现,在 shared/module.yaml 中添加平台特定依赖:
1dependencies@jvm: # 针对 jvm 平台
2 - $libs.ktor.client.okhttp
错误3: Module with the Main dispatcher is missing
1Module with the Main dispatcher is missing.
2Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'
这个错误是因为项目中虽然引入了 kotlinx-coroutines-core,但还需要添加平台特定的协程实现。
在项目的 gradle/libs.versions.toml
文件中,已经为我们预定义了 kotlinx-coroutines-swing
依赖:
gradle/libs.versions.toml
1[versions]
2androidx-activityCompose = "1.9.2"
3androidx-ui-tooling = "1.7.0"
4androidx-lifecycle = "2.8.4"
5coroutines = "1.8.1"
6kamel = "0.9.5"
7koin = "3.5.6"
8ktor = "2.3.12"
9voyager = "1.0.0"
10
11[libraries]
12androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
13androidx-compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "androidx-ui-tooling" }
14androidx-lifecycle-runtime-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
15kamel = { module = "media.kamel:kamel-image", version.ref = "kamel" }
16kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
17koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
18ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
19ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
20ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
21ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
22ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
23voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" }
24voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
添加这个依赖到配置文件中:
1dependencies@jvm: # 针对 jvm 平台
2 - $libs.kotlinx.coroutines.swing
现在再次运行项目,终于成功了,我们现在可以看到项目截图如下:
查看视频可知,各种常规功能,包括点击,滚动,页面跳转,窗口大小变动的响应式,都比较正常。
再次打包
现在我们已经完成了一个完整的 KMP 项目,可以进行打包了。
这时候,我们回到 gradle 界面中,发现刚刚的 distZip 任务已经不存在了,这个是因为在配置文件中
引入了 compose: true
的原因。这个配置项会引入 gradle 插件,导致出现不同的 tasks。
这次我们使用更加通用的 shadowJar 方案来打包
首先,amper 支持和 gradle 共存,所以,直接在 jvmApp/ 下新建一个 build.gradle.kts
文件,内容如下:
1println("The jvmApp build.gradle.kts is running.")
2
3plugins {
4 id("com.gradleup.shadow") version "9.0.0-beta4"
5}
6
7tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
8 archiveClassifier.set("JvmApp") // 可选:设置输出 JAR 的名称
9 manifest {
10 attributes["Main-Class"] = "top.kikt.kmp.jvm.app.MainKt" // 替换为你的 Main 类
11 }
12
13 // 执行结束后,显示输出的 JAR 文件路径
14 doLast {
15 println("Output JAR file: ${archiveFile.get().asFile.path}")
16 }
17}
上面加一个 println 是为了看到这个文件是否被加载到。
gradle 文件的修改和引入都需要 sync project,这点和 amper 配置文件是不一样的。
我们 sync project 后,发现 tasks 中多了一个分组
接下来,我们执行这个 task
可以看到输出的 JAR 文件路径,接着,运行一下这个 jar 包:
1java -jar jvmApp/build/libs/jvmApp-JvmApp.jar
总结
这就是使用 Amper 打包 KMP 应用的完整流程。 最后虽然还是使用的传统 shadowJar 方式来打包的,但是这种方案最终