对基础和高级项目结构概念的概述应该能让你理解源集和依赖关系管理。那么,组织源集并依赖于这些依赖关系的模块又是什么呢?
本文专门讨论KMP项目。如需了解模块化决策的总体思路,请参阅Android模块化入门介绍。
最优模块结构
最佳模块结构会根据您的目标和所需实现而有所不同。您可以分析KMP IDE 插件向导在不同配置和目标集下的输出结果,以了解我们默认的项目组织方式。
总体方法可概括如下:
应用程序的入口点应该包含在单独的模块中,每个模块都依赖于必要的共享代码模块。
共享代码通常分为业务逻辑和用户界面两部分,其策略是避免不必要的依赖关系:
如果您使用 KMP 项目开发的所有应用程序都使用共享的 UI 代码以及共享的业务逻辑,那么一个
shared包含所有共享代码的模块就足够了。如果你的应用的 UI 是用原生代码编写的(例如,你用纯 Swift 实现了 iOS UI),那么将 UI 代码与业务逻辑分离就很有意义,这样可以避免在不需要的地方引入 Compose Multiplatform 依赖项。因此,你可以创建一个模块
sharedLogic和一个业务逻辑sharedUI模块,并根据需要将它们作为依赖项添加到入口点模块中。
如果你的项目包含需要与客户端应用程序共享逻辑的服务器端代码,建议的结构如下:
一个
app包含入口点模块和客户端通用代码模块的文件夹,其组织方式如上所述。server包含服务器特定代码的模块。core用于服务器和客户端之间共享代码的模块,例如模型和验证。
如果您的项目使用较旧的结构,应用程序入口点和共享代码包含在单个模块中,您可以按照以下指南将入口点提取到单独的模块中。
如果您打算使用 Android Gradle Plugin 9 或更高版本,则必须将 Android 应用的入口点与公共代码分离。详情请参阅我们的AGP 9 迁移文章。
创建单独的模块作为应用程序入口点
我们将使用一个较旧的 Compose Multiplatform 示例项目来说明如何过渡到推荐的结构,该示例可以在示例存储库的old-project-structure分支中找到。
该示例包含一个 Gradle 模块(composeApp),其中包含所有共享代码和 KMP 入口点,以及iosApp包含 iOS 项目代码和配置的文件夹。
要提取到其自身模块的入口点,您需要创建该模块,移动代码,并相应地调整新模块和通用代码模块的配置。
Android 应用入口点的模块
创建并配置 Android 应用模块
要创建 Android 应用模块(androidApp):
androidApp在项目根目录下创建目录。在该目录下,创建一个空
build.gradle.kts文件和该src目录。settings.gradle.kts在文件中,通过在文件末尾添加以下行,将新模块添加到项目设置中:include(":androidApp")在主菜单中选择“构建 | 将项目与 Gradle 文件同步”,或者单击编辑器中的 Gradle 刷新按钮。
配置 Android 应用的构建脚本
为新模块配置 Gradle 构建脚本:
在
gradle/libs.versions.toml文件中,将 Kotlin Android Gradle 插件添加到您的版本目录中:[plugins] kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }在
androidApp/build.gradle.kts文件中,指定 Android 应用模块所需的插件:plugins { alias(libs.plugins.kotlinAndroid) alias(libs.plugins.androidApplication) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) }请确保在根 文件中提及所有这些插件
build.gradle.kts:plugins { alias(libs.plugins.kotlinAndroid) apply false alias(libs.plugins.androidApplication) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false // ... }要添加必要的依赖项,请从构建脚本
androidMain.dependencies {}的依赖项块中复制现有依赖项composeApp,并将依赖项添加到composeApp模块本身。在本例中,结果应如下所示:kotlin { dependencies { implementation(projects.composeApp) implementation(libs.androidx.activity.compose) implementation(libs.compose.uiToolingPreview) } }android {}将文件中包含 Android 特定配置的整个代码块复制composeApp/build.gradle.kts到该androidApp/build.gradle.kts文件中。androidTarget {}将文件中的编译器选项复制composeApp/build.gradle.kts到文件target {}的相应代码块中androidApp/build.gradle.kts:kotlin { target { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } }如果在构建脚本中设置了任何其他插件或属性
composeApp,请确保androidApp也将其迁移到构建脚本中。将模块的配置
composeApp从 Android 应用程序更改为 Android 库,因为它实际上已经变成了一个库composeApp/build.gradle.kts。更改对 Gradle 插件的引用:
前
alias(libs.plugins.androidApplication)后
alias(libs.plugins.androidLibrary)
从代码块中移除应用程序属性行
android.defaultConfig {}:前
defaultConfig { applicationId = "com.jetbrains.demo" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() versionCode = 1 versionName = "1.0" }后
defaultConfig { minSdk = libs.versions.android.minSdk.get().toInt() }在主菜单中选择“构建 | 将项目与 Gradle 文件同步”,或者单击编辑器中的 Gradle 刷新按钮。
移动代码并运行 Android 应用
将目录移动
composeApp/src/androidMain到androidApp/src/目标目录,但请记住哪些代码应该保持跨平台:像我们的示例中那样的入口点代码
MainActivity.kt必须位于androidApp模块中,才能正确构建 Android 应用。所有预期和实际的声明都必须保留在公共模块(在本例中为
<module>)的源文件中,composeApp才能在所有平台上可用。当您设置对androidApp<module>的依赖关系时composeApp,这些声明也将在入口点代码中可用。
将目录重命名
androidApp/src/androidMain为main.如果所有配置都正确,文件中的导入就能正常
androidApp/src/main/.../MainActivity.kt工作,代码也能编译通过。当您使用 IntelliJ IDEA 或 Android Studio 时,IDE 会识别新模块并自动创建一个新的运行配置androidApp。如果未自动创建,请手动修改composeApp Android 运行配置:
在运行配置下拉菜单中,选择“编辑配置”。
在Android类别中查找composeApp配置。
在“常规 | 模块”字段中,更改
demo.composeApp为demo.androidApp.
启动新的运行配置,以确保应用程序按预期运行。
如果一切正常,请在
composeApp/build.gradle.kts文件中删除该kotlin.sourceSets.androidMain.dependencies {}代码块。
您已将 Android 入口点提取到单独的模块中。现在,请更新通用代码模块以使用新的 Android-KMP 库插件。
桌面 JVM 应用
创建并配置桌面应用程序模块
要创建桌面应用程序模块(desktopApp):
desktopApp在项目根目录下创建目录。在该目录下,创建一个空
build.gradle.kts文件和该src目录。settings.gradle.kts通过在文件中添加以下代码行,将新模块添加到项目设置中:include(":desktopApp")
配置桌面应用程序的构建脚本
要使桌面应用程序构建脚本正常工作:
在
gradle/libs.versions.toml文件中,将 Kotlin JVM Gradle 插件添加到您的版本目录中:[plugins] kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }在
desktopApp/build.gradle.kts文件中,指定共享 UI 模块所需的插件:plugins { alias(libs.plugins.kotlinJvm) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) }请确保在根 文件中提及所有这些插件
build.gradle.kts:plugins { alias(libs.plugins.kotlinJvm) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false // ... }要添加对其他模块的必要依赖项,请从构建脚本的
<dependencies>commonMain.dependencies {}和<dependencies> jvmMain.dependencies {}代码块中复制现有依赖项composeApp。在本例中,最终结果应如下所示:kotlin { dependencies { implementation(projects.sharedLogic) implementation(projects.sharedUI) implementation(compose.desktop.currentOs) implementation(libs.kotlinx.coroutinesSwing) } }compose.desktop {}将包含桌面特定配置的代码块从文件复制composeApp/build.gradle.kts到该desktopApp/build.gradle.kts文件:compose.desktop { application { mainClass = "compose.project.demo.MainKt" nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "compose.project.demo" packageVersion = "1.0.0" } } }在主菜单中选择“构建 | 将项目与 Gradle 文件同步”,或者单击编辑器中的 Gradle 刷新按钮。
移动代码并运行桌面应用程序
配置完成后,将桌面应用程序的代码移动到新目录:
在
desktopApp/src目录下创建一个新main目录。将目录移动
composeApp/src/jvmMain/kotlin到desktopApp/src/main/目标目录中:软件包坐标与compose.desktop {}配置保持一致非常重要。如果所有配置都正确,文件中的导入
desktopApp/src/main/.../main.kt就能正常工作,代码也能编译通过。要运行桌面应用程序,请修改composeApp [jvm]运行配置:
在运行配置下拉菜单中,选择“编辑配置”。
在Gradle类别中查找composeApp [jvm]配置。
在Gradle 项目字段中,更改
ComposeDemo:composeApp为ComposeDemo:desktopApp.
启动更新后的配置,以确保应用程序按预期运行。
如果一切正常:
删除该
composeApp/src/jvmMain目录。在文件中
composeApp/build.gradle.kts删除与桌面相关的代码:街区
compose.desktop {},jvmMain.dependencies {}KotlinsourceSets {}代码块内的代码块jvm()代码块内的目标声明kotlin {}。
Web应用程序
创建并配置 Web 应用程序模块
要创建 Web 应用程序模块(webApp):
webApp在项目根目录下创建目录。在该目录下,创建一个空
build.gradle.kts文件和该src目录。settings.gradle.kts在文件中,通过在文件末尾添加以下行,将新模块添加到项目设置中:include(":webApp")
配置 Web 应用程序的构建脚本
要使 Web 应用程序构建脚本正常工作:
在
webApp/build.gradle.kts文件中,指定共享 UI 模块所需的插件:```kotlin plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) } ```请确保在根 文件中提及所有这些插件
build.gradle.kts:plugins { alias(libs.plugins.kotlinMultiplatform) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false // ... }将文件中的 JavaScript 和 Wasm 目标声明复制
composeApp/build.gradle.kts到文件kotlin {}中的相应代码块中webApp/build.gradle.kts:kotlin { js { browser() binaries.executable() } @OptIn(ExperimentalWasmDsl::class) wasmJs { browser() binaries.executable() } }添加对其他模块的必要依赖项:
kotlin { sourceSets { commonMain.dependencies { implementation(projects.sharedLogic) // Provides the necessary entry point API implementation(compose.ui) } } }在主菜单中选择“构建 | 将项目与 Gradle 文件同步”,或者单击编辑器中的 Gradle 刷新按钮。
移动代码并运行 Web 应用程序
配置完成后,将Web应用程序的代码移动到新目录:
将整个
composeApp/src/webMain目录移动到webApp/src目标目录。如果一切配置正确,webApp/src/webMain/.../main.kt文件中的导入语句就能正常工作,代码也能编译通过。在
webApp/src/webMain/resources/index.html文件中更新脚本名称:从composeApp.js到webApp.js。要运行您的 Web 应用程序,请修改composeApp [wasmJs]运行配置:
在运行配置下拉菜单中,选择“编辑配置”。
在Gradle类别中查找composeApp [wasmJs]配置。
在Gradle 项目字段中,更改
ComposeDemo:composeApp为ComposeDemo:webApp.
对composeApp [js]重复上述步骤,以便也能运行 JavaScript 版本。
启动运行配置,确保应用程序按预期运行。
如果一切正常:
删除该
composeApp/src/webMain目录。在
composeApp/build.gradle.kts文件中,删除与网络相关的代码:webMain.dependencies {}KotlinsourceSets {}代码块内的代码块js {}代码块内的 and targetwasmJs {}声明kotlin {}。
配置共享模块
在示例应用程序中,UI 和业务逻辑代码都是共享的,因此只需要一个共享模块来保存所有公共代码:您可以简单地将其重新用作composeApp公共代码模块。
除了与入口点模块的连接之外,Gradle 配置中唯一需要调整的是新的 Android Library Gradle 插件。该插件专为多平台项目而设计,并且是使用 AGP 9 及更高版本所必需的。
以下是必要的更改:
在 中
gradle/libs.versions.toml,将 Android-KMP 库插件添加到您的版本目录中:[plugins] androidMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" }在
composeApp/build.gradle.kts文件中,添加共享 UI 模块所需的插件:plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidMultiplatformLibrary) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.composeCompiler) }在根
build.gradle.kts文件中,添加以下行以避免应用插件时发生冲突:alias(libs.plugins.androidMultiplatformLibrary) apply false在
composeApp/build.gradle.kts文件中,用以下kotlin.androidTarget {}代码块替换原kotlin.androidLibrary {}代码块:androidLibrary { namespace = "compose.project.demo.composedemo" compileSdk = libs.versions.android.compileSdk.get().toInt() compilerOptions { jvmTarget = JvmTarget.JVM_11 } androidResources { enable = true } }android {}从文件中删除根代码块composeApp/build.gradle.kts。移除
androidMain依赖项,因为所有代码都已移至应用程序模块:删除该kotlin.sourceSets.androidMain.dependencies {}代码块。检查安卓应用是否运行正常。
(可选)分离共享逻辑和共享用户界面
更新 iOS 集成
由于 iOS 应用入口点并非构建为单独的 Gradle 模块,因此您可以将源代码嵌入到任何模块中。在本例中,您可以将其保留在以下位置shared:
将该目录移动
composeApp/src/iosMain到该shared/src目录中。配置 Xcode 项目以使用模块生成的框架
shared:选择“文件 | 在 Xcode 中打开项目”菜单项。
在项目导航器工具窗口中单击iosApp项目,然后选择“构建阶段”选项卡。
找到编译 Kotlin 框架阶段。
找到以“:”开头的行
./gradlew,并将其替换composeApp为sharedUi:./gradlew :shared:embedAndSignAppleFrameworkForXcode请注意,文件中的导入语句
ContentView.swift必须保持不变,因为它匹配的baseName是 iOS 目标 Gradle 配置中的参数,而不是模块的实际名称。如果您更改了文件中的框架名称shared/build.gradle.kts,则需要相应地更改导入指令。
从 Xcode 运行应用程序,或者在 IntelliJ IDEA 中使用iosApp运行配置。