# KMM & Compose Multiplatform 跨平台开发入门指南:构建高效的移动应用 站点:答源(https://www.dandroid.cn/) 原文:https://www.dandroid.cn/archives/22818 Canonical:https://www.dandroid.cn/archives/22818 作者:技术信源组 发布日期:2025-03-04 最后更新:2026-11-06 18:51 分类:Android应用源码 / 跨平台源码 内容类型:post 说明:本页是原文的纯文本镜像,供大模型与检索器完整读取;不含导航、广告、付费框。引用请以上方「原文」链接为准。 ## 正文 1. 介绍 引言 在移动应用开发领域,Kotlin Multiplatform Mobile (KMM) 和 Compose Multiplatform 的结合正在成为一种强大的解决方案。它们不仅解决了传统跨平台开发中的诸多痛点,还提供了许多独特的优势,使得开发者能够更加高效地构建和维护跨平台应用。 以下是它的主要优势: 代码复用 KMM 和 Compose Multiplatform 允许开发者在 Android 和 iOS 之间共享大部分代码,包括业务逻辑和 UI 组件。 性能优化 与某些其他跨平台框架不同,KMM 生成的代码是直接运行在目标平台的原生环境中的。这意味着开发者可以享受到与原生开发相同的性能和系统级优化。 平台特性和灵活性 KMM 通过 expect 和 actual 关键字,允许开发者为不同的平台编写特定的实现。这种灵活性使得开发者可以充分利用各个平台的特性,而不会妥协于跨平台的限制。Compose Multiplatform 同样支持这种灵活性,确保 UI 设计可以根据平台的需求进行优化。 一致的开发体验 Kotlin 作为一种现代语言,拥有简洁的语法和强大的功能特性,深受开发者喜爱。利用 Kotlin 和 Compose Multiplatform,开发者能够在相同的开发环境中进行多平台开发,提升了开发者的体验和生产力。使用 Android Studio 和 Xcode 无缝集成,开发者可以在熟悉的 IDE 中进行跨平台开发和调试。 现代化的 UI 构建 Compose Multiplatform 基于声明式编程范式,简化了复杂的 UI 构建。开发者可以通过简明的代码定义动态界面,减少了样板代码和 UI 更新的复杂度。这种现代化的 UI 构建方式,不仅使代码更加清晰和易于维护,还提升了开发效率。 使用 KMM 和 Compose Multiplatform,开发团队可以在保证高性能和平台特性的同时,实现代码的最大复用和开发效率的提升。这种结合不仅为项目带来了显著的优势,还为开发者提供了创新和灵活的开发体验。接下来,让我们进入 KMM + Compose Multiplatform 的学习与使用。 基础概念 什么是 Kotlin Multiplatform (KMP)? Kotlin Multiplatform (KMP) 是 JetBrains 提供的一项功能,允许使用 Kotlin 编写可以在多个平台上运行的代码。KMP 的目标是通过 共享业务逻辑代码 来减少不同平台开发的重复工作。 KMP 支持的平台包括但不限于: JVM:用于服务器端开发以及 Android 开发。 JavaScript:用于前端 Web 开发。 原生平台 (Native):如 iOS、Windows、macOS、Linux 等。 KMP@2x.png 什么是 Kotlin Multiplatform Mobile (KMM)? Kotlin Multiplatform Mobile (KMM) 是专注于移动平台的一种 KMP 实现。KMM 让开发者能够使用 Kotlin 编写可以在 Android 和 iOS 上运行的共享代码,主要关注点是在移动设备上的应用开发。 简单来说,KMM 是 KMP 的一个子集或特化实现,专注于移动开发。 什么是 Compose multiform? Compose Multiplatform 是 JetBrains 开发的一种跨平台用户界面 (UI) 框架,它基于 Kotlin 和 Jetpack Compose 的设计理念,旨在通过声明式编程范式简化 跨平台 UI 构建。 什么是 Kotlin/Native、Kotlin/JVM、Kotlin/JS?与 KMP 的关系? Kotlin 是一种现代化的编程语言,由 JetBrains 开发,支持多平台开发,包括 JVM、JavaScript 和原生平台。为了更高效地开发跨平台应用,Kotlin 提供了三种主要的编译器后端:Kotlin/JVM、Kotlin/JS 和 Kotlin/Native。 Kotlin/JVM 是最初的 Kotlin 编译器后端,也是最常用的一种。它将 Kotlin 代码编译成可以在 Java 虚拟机 (JVM) 上运行的字节码。由于 Kotlin 与 Java 高度互操作,Kotlin/JVM 可以直接利用现有的 Java 库和框架,使得开发者能够轻松地将 Kotlin 与 Java 项目集成。使用场景有 Android 开发与服务器开发。 Kotlin/JS 将 Kotlin 编译为 JavaScript 代码,从而可以在浏览器或 Node.js 环境中运行。Kotlin/JS 使得前端开发者可以使用 Kotlin 编写网页应用,同时充分利用现有的 JavaScript 库和框架。使用场景是 Web 前端开发。 Kotlin/Native 将 Kotlin 编译为原生二进制代码,可以直接运行在不依赖 JVM 或 JavaScript 引擎的环境中。Kotlin/Native 支持多种平台,包括 iOS、Windows、MacOS、Linux、嵌入式设备等,甚至于 Android、鸿蒙也可以使用 KN 跨平台开发(需要编写 JNI or NAPI 桥接代码)。 KMP 结合了 Kotlin/JVM、Kotlin/JS 和 Kotlin/Native 的优势,使开发者能够在统一的代码库中,针对不同平台进行开发,实现跨平台方案。 2. KMM 环境搭建 KDoctor kdoctor 是一个用于验证 Kotlin Multiplatform Mobile (KMM) 开发环境是否正确配置的命令行工具。它会检查系统上的各种依赖和配置,确保已经安装并正确配置了所有必要的软件,例如 Android Studio、Xcode、CocoaPods 等。如果是第一次设置 KMM 环境,强烈建议使用 kdoctor 来确认一切都已正确配置。 安装 kdoctor 对于 macOS 系统: 确保 Homebrew 已安装 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 安装 kdoctor brew install kdoctor 使用 kdoctor 进行环境检查 安装完 kdoctor 后,通过以下命令检查 KMM 开发环境: kdoctor kdoctor 将检查以下内容: JDK:是否安装了 Java Development Kit。 Android Studio:是否安装了 Android Studio 和必要的组件。 Xcode:是否安装了 Xcode 和命令行工具。 CocoaPods:是否安装了 CocoaPods(用于管理 iOS 依赖)。 运行 kdoctor 后的示例输出: $ kdoctor [ ✓ ] Checking the Java version (11.0.8). [ ✓ ] Checking the Android Studio installation. [ ✓ ] Checking the Xcode installation. [ ✓ ] Checking the CocoaPods installation. Conclusion: ✓ Your operation system is ready for Kotlin Multiplatform Mobile Development! 如果 kdoctor 报告某些组件未正确安装或配置,按照指导信息进行相应的操作来解决问题。 在这个示例输出中,kdoctor 表明所有必要的软件都已正确安装,已准备好进行 KMM 开发。 Kotlin Multiplatform Plugin 在 Android Studio 中安装 Kotlin Multiplatform 插件: 打开 Android Studio。 进入 Settings -> Plugins。 搜索并安装 Kotlin Multiplatform 插件。 重启 Android Studio 以激活插件。 3. 创建 KMM 项目 新建项目 打开 Android Studio,选择 New Project。 在项目模板中,选择 Kotlin Multiplatform App。   项目模板选择 配置项目名称、保存路径和包名。   配置项目   这里 iOS 依赖管理我选择使用 CocoaPods,可以简化依赖管理和配置,尤其是在需要集成大量第三方 iOS 库的项目中。 点击 Finish。 项目结构 生成的 KMM 项目包含 Android 和 iOS 两个平台的代码,以及一个共享代码模块: MyKMMApp/ ├── androidApp/ # Android 壳工程 ├── iosApp/ # iOS 壳工程 ├── shared/ # 共享代码模块,目前仅包含业务逻辑,未来还会在此添加 UI 组件、资源等 │ ├── src/ │ │ ├── commonMain/ # 共享逻辑 │ │ ├── androidMain/ # Android 平台代码 │ │ ├── iosMain/ # iOS 平台代码 └── build.gradle # 项目级 build 文件 配置 build.gradle 在 shared 模块的 build.gradle.kts 文件中,配置 Kotlin Multiplatform 插件和依赖项: plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.kotlinCocoapods) alias(libs.plugins.androidLibrary) } kotlin { androidTarget { compilations.all { kotlinOptions { jvmTarget = "1.8" } } } iosX64() iosArm64() iosSimulatorArm64() cocoapods { summary = "Some description for the Shared Module" homepage = "Link to the Shared Module homepage" version = "1.0" ios.deploymentTarget = "16.0" podfile = project.file("../iosApp/Podfile") framework { baseName = "shared" isStatic = true } } sourceSets { val commonMain by getting { dependencies { //put your multiplatform dependencies here } } commonTest.dependencies { implementation(libs.kotlin.test) } val androidMain by getting { dependencies { // Android 平台依赖 } } val iosX64Main by getting val iosArm64Main by getting val iosSimulatorArm64Main by getting val iosMain by creating { dependsOn(commonMain) iosX64Main.dependsOn(this) iosArm64Main.dependsOn(this) iosSimulatorArm64Main.dependsOn(this) dependencies { // iOS 平台依赖 } } } } android { namespace = "com.dixon.app.kmmsample" compileSdk = 34 defaultConfig { minSdk = 24 } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } } 4. 添加 Compose Multiplatform 依赖 上述项目仅支持业务逻辑共享,支持 UI 共享需要添加 Compose Multiplatform 依赖。 依赖管理和 Gradle 配置 配置所需依赖 在 gradle - libs.versions.toml 文件中添加所需依赖。 [versions] ... [versions] agp = "8.2.2" kotlin = "1.9.20" compose = "1.5.4" compose-compiler = "1.5.4" compose-material3 = "1.1.2" androidx-activityCompose = "1.8.0" # compose multiplatform support compose-plugin = "1.6.1" [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } androidLibrary = { id = "com.android.library", version.ref = "agp" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" } # compose multiplatform support composeLibrary = { id = 'org.jetbrains.compose', version.ref = "compose-plugin" } 配置共享模块 在你的共享模块(通常是 shared)的 build.gradle.kts 文件中添加 Compose Multiplatform 的依赖。 plugins { ... // compose multiplatform support alias(libs.plugins.composeLibrary) } kotlin { ... sourceSets { val commonMain by getting { dependencies { // put your multiplatform dependencies here // compose multiplatform support api(compose.material3) // md 设计的组件 api(compose.foundation) // 基础布局组件 api(compose.ui) // 测量、布局、绘制、事件、Modifier api(compose.runtime) // 树管理能力 api(compose.animation) // 动画 } } commonTest.dependencies { implementation(libs.kotlin.test) } ... } } 创建跨平台的 Compose UI // shared/src/commonMain/kotlin/com/example/shared/Greeting.kt import androidx.compose.runtime.Composable import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.sp import androidx.compose.material.Text @Composable fun Greeting(name: String) { Text( text = "Hello, $name!", style = TextStyle(fontSize = 24.sp) ) } Android 项目中使用 Compose Multiplatform 在 androidApp 模块中,引入并使用共享模块的 Compose 组件: // androidApp/src/main/java/com/example/mykmmapp/MainActivity.kt package com.example.mykmmapp import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import com.example.shared.Greeting class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Greeting("Android") } } } Configuration 选择 AndroidApp 运行。 iOS 项目中使用 Compose Multiplatform iOS 无法直接使用 Compose UI 组件,但是通过包装成 UIViewController,可以确保这些组件能够在 iOS 的 UIKit 和 SwiftUI 中使用,并充分利用 iOS 平台的视图管理机制。 在 iosMain 目录下包装 Compose UI 组件 // shared/src/iosMain/kotlin/com/example/mykmmapp/ui/App.kt package com.example.mykmmapp import androidx.compose.ui.window.ComposeUIViewController import com.example.mykmmapp.ui.App fun AppViewController() = ComposeUIViewController { App() } 在 iosApp 模块中,引入并使用 UIViewController 组件 // iosApp/iosApp/ContentView.swift import SwiftUI import shared // 创建一个 Swift 结构体来包装 UIViewController struct ComposeUIViewControllerRepresentable: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { ComposeViewControllerKt.AppViewController() // 调用 Kotlin 中创建的 Compose UIViewController } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } struct ContentView: View { let greet = Greeting().greet() var body: some View { ComposeUIViewControllerRepresentable() .edgesIgnoringSafeArea(.all) // 如果只想忽略水平方向的 Safe Area // .edgesIgnoringSafeArea(.horizontal) // 如果只想忽略水平方向的 Safe Area // .edgesIgnoringSafeArea([]) // 默认不忽略 Safe Area // .all 状态栏顶部高度、横向 safe area 都会忽略 } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } Configuration 选择 iosApp 运行,也可以 Xcode 开启 iosApp 目录运行。 5. 实现实例应用 基础环境已经配置完毕,并可以运行在不同平台的设备上。接下来,我将开发一个简单的实例应用,包含网络请求、图片展示、本地资源管理、日志、路由、序列化等应用开发非常基础的能力,为大家演示 KMM + Compose Multiplatform 项目的实战能力。 开始前,推荐简单了解 kmp-awesome 仓库,它整理和推荐了一些 Kotlin Multiplatform 相关的优秀库、工具和资源,是一个非常有价值的资源集合,适合那些想要深入了解和使用 Kotlin Multiplatform 的开发者。 I. 导航 介绍 Navigator 用于在多平台项目中进行高效导航,如果不使用导航,你可能需要在不同的平台下创建页面(如 Android 平台页面由 Activity 承载),并桥接跳转逻辑。而 Navigator 使你可以编写一次导航逻辑,并在 iOS 和 Android 上重用,且提供了一种简洁的方法来管理应用的所有路由。 这里我选用 PreCompose 库提供的跨平台导航能力。 依赖管理和 Gradle 配置 配置所需依赖 [versions] ... precompose = "1.6.1" [libraries] ... precompose = { module = "moe.tlaster:precompose", version.ref = "precompose" } precompose-viewmodel = { module = "moe.tlaster:precompose-viewmodel", version.ref = "precompose" } 配置共享模块 val commonMain by getting { // 跨平台导航库 api(libs.precompose) // ViewModel implementation(libs.precompose.viewmodel) } 使用导航 在 Compose Root UI 组件使用导航: package com.dixon.app.kmmsample.ui import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import moe.tlaster.precompose.PreComposeApp import moe.tlaster.precompose.navigation.NavHost import moe.tlaster.precompose.navigation.Navigator import moe.tlaster.precompose.navigation.rememberNavigator import moe.tlaster.precompose.navigation.transition.NavTransition /* navigator 使用: Navigator.navigate(route: String, options: NavOptions? = null) Navigator.goBack() Navigator.canGoBack: Boolean */ val LocalNavigator = staticCompositionLocalOf { error("Navigator not provided") } @Composable fun App() { PreComposeApp { // your app's content goes here val navigator = rememberNavigator() CompositionLocalProvider(LocalNavigator provides navigator) { NavHost( // Assign the navigator to the NavHost navigator = navigator, // Navigation transition for the scenes in this NavHost, this is optional navTransition = NavTransition(), // The start destination initialRoute = PAGE_HOME_MAIN_ROUTE, ) { pages.forEach { scene( // Scene's route path route = it.route, // Navigation transition for this scene, this is optional navTransition = NavTransition(), content = it.content ) } } } } } 后续任意 Composable 使用 LocalNavigator.current.navigate(route, options) 进行页面跳转。 关于路由的详细用法和封装可以跳转此 示例仓库 查看。 在导航依赖里,我同时导入了 ViewModel 依赖。这是因为在导航返回时,Composable 函数会重新执行,部分数据需要通过 ViewModel 缓存起来。可以说,导航和 ViewModel 是密不可分的。在下边的网络库中你会看到相关应用。 II. 网络库 介绍 Ktor Client 是 Ktor 框架的一部分,由 JetBrains 开发,用于构建高效、非阻塞的 HTTP 客户端。Ktor Client 支持多个平台,如 JVM、Android、JavaScript、Native 等,使其成为 Kotlin Multiplatform 项目中理想的选择。 依赖管理和 Gradle 配置 配置所需依赖 [versions] ... kotlinxSerializationJson = "1.6.1" kotlinxSerializationPlugin = "2.0.0" # ktorClientCore = "3.0.0-beta-2" # 新版本有问题弃用 ktorClientCore = "2.3.12" # 选用稳定版本 [libraries] kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktorClientCore" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorClientCore" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktorClientCore" } ktor-client-ios = { module = "io.ktor:ktor-client-ios", version.ref = "ktorClientCore" } ktor-client-json = { module = "io.ktor:ktor-client-json", version.ref = "ktorClientCore" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktorClientCore" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktorClientCore" } ktor-client-serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktorClientCore" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktorClientCore" } [plugins] kotlinSerializationPlugin = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinxSerializationPlugin" } 配置共享模块 plugins { ... // 添加 Kotlin Serialization 插件,注意低版本无效 // 配套 libs.kotlinx.serialization.json 库使用 alias(libs.plugins.kotlinSerializationPlugin) } kotlin { ... sourceSets { val commonMain by getting { dependencies { // put your multiplatform dependencies here ... // 跨平台网络库 implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.logging) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.serialization.kotlinx.json) } } val androidMain by getting { dependencies { // 平台网络库引擎 implementation(libs.ktor.client.okhttp) } } ... val iosMain by creating { ... dependencies { // 平台网络库引擎 implementation(libs.ktor.client.ios) } } } } 使用网络库 这里我使用 狗狗免费 API 来测试我的网络功能。 初始化 KtorClient 实例 import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.network.sockets.ConnectTimeoutException import io.ktor.client.plugins.ClientRequestException import io.ktor.client.plugins.HttpRequestTimeoutException import io.ktor.client.plugins.ServerResponseException import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.cookies.AcceptAllCookiesStorage import io.ktor.client.plugins.cookies.HttpCookies import io.ktor.client.plugins.logging.DEFAULT import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logger import io.ktor.client.plugins.logging.Logging import io.ktor.client.request.get import io.ktor.serialization.kotlinx.json.json val ktorClient = HttpClient { install(ContentNegotiation) { json() } install(HttpCookies) { storage = AcceptAllCookiesStorage() } install(Logging) { logger = Logger.DEFAULT level = LogLevel.INFO } expectSuccess = true } // ---- 简单封装 ---- private const val KTOR_TAG = "KtorSafeCall" // append try-catch suspend fun safeCall( client: HttpClient = ktorClient, call: suspend HttpClient.() -> T ): Result { return try { val result = call(client) com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Request succeed: $result" } Result(data = result) } catch (e: ConnectTimeoutException) { com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Connection timed out: ${e.message}" } Result(exception = e) } catch (e: HttpRequestTimeoutException) { com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Request timed out: ${e.message}" } Result(exception = e) } catch (e: ClientRequestException) { // 4xx responses com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Client request error: ${e.response.status.description}" } Result(exception = e) } catch (e: ServerResponseException) { // 5xx responses com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "Server response error: ${e.response.status.description}" } Result(exception = e) } catch (e: Exception) { com.dixon.app.kmmsample.core.base.Logger.i(KTOR_TAG) { "An unexpected error occurred: ${e.message}" } Result(exception = e) } } suspend inline fun apiGet( source: String ): Result = safeCall { get(source).body() } data class Result( val data: T? = null, val exception: Throwable? = null ) fun Result.isSucceed() = data != null fun Result.isFailed() = exception != null inline fun Result.dispose( onSucceed: (T) -> Unit, onFailed: (Throwable) -> Unit ) { if (data != null) { onSucceed.invoke(data) } else { onFailed.invoke(exception ?: RuntimeException("Unknown Exception")) } } inline fun Result.disposeSucceed( onSucceed: (T) -> Unit, ) = this.apply { if (data != null) { onSucceed.invoke(data) } } inline fun Result.disposeFailed( onFailed: (Throwable) -> Unit, ) = this.apply { if (exception != null) { onFailed.invoke(exception) } } 创建 ViewModel 发起并保存请求 package com.dixon.app.kmmsample.logic.home import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import com.dixon.app.kmmsample.bean.DogData import com.dixon.app.kmmsample.core.base.Logger import com.dixon.app.kmmsample.core.base.apiGet import com.dixon.app.kmmsample.core.base.disposeFailed import com.dixon.app.kmmsample.core.base.disposeSucceed import kotlinx.coroutines.launch import moe.tlaster.precompose.viewmodel.ViewModel import moe.tlaster.precompose.viewmodel.viewModelScope class HomeViewModel : ViewModel() { private var _data: MutableState = mutableStateOf(null) val data: State = _data // 请求数据,并将返回的 Json 转为 DogData 保存起来 fun fetchData() { viewModelScope.launch { apiGet>("https://api.thecatapi.com/v1/images/search?api_key=请自行申请 api_key") .disposeSucceed { _data.value = it.first() }.disposeFailed { Logger.e("HomeViewModel#fetchData") { it.message.toString() } } } } } Bean 类: package com.dixon.app.kmmsample.bean import kotlinx.serialization.Serializable /** * 测试接口返回的 json 数据 */ @Serializable data class DogData( val id: String, val url: String, val width: Int, val height: Int ) 页面使用 ViewModel package com.dixon.app.kmmsample.ui.home import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.dixon.app.kmmsample.MR import com.dixon.app.kmmsample.core.base.Logger import com.dixon.app.kmmsample.logic.home.HomeViewModel import com.dixon.app.kmmsample.ui.Builder import com.dixon.app.kmmsample.ui.LocalNavigator import com.dixon.app.kmmsample.ui.PAGE_IMAGE_DETAIL_ROUTE import com.dixon.app.kmmsample.ui.build import com.dixon.app.kmmsample.ui.put import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import moe.tlaster.precompose.viewmodel.viewModel /** * Navigator与传统Activity不同,跨页面要使用ViewModel缓存数据,否则返回页面会重新请求 */ @Composable fun HomeMain() { Logger.i("HomeMain") { "Composable Root : HomeMain" } val navigator = LocalNavigator.current val homeVM = viewModel( modelClass = HomeViewModel::class, creator = { HomeViewModel() }) val data by homeVM.data Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { Button(onClick = { homeVM.fetchData() }) { Text("点击获取随机图片数据") } Spacer(Modifier.height(20.dp)) Text( text = data?.toString() ?: "请先获取数据!", fontSize = 14.sp, color = Color.Gray ) Spacer(Modifier.height(20.dp)) Button(onClick = { // 导航到图片展示页,详见下文图片库 navigator.Builder(PAGE_IMAGE_DETAIL_ROUTE) .put("resource", data?.url) .build() }) { Text("展示网图") } } } 关于网络库的详细用法和封装可以跳转此 示例仓库 查看。 III. 日志 介绍 演示项目相对简单,日志功能我选择了自行实现,并借此演示如何使用平台能力实现跨平台代码。 代码实现 CommonMain 添加跨平台代码 // shared/src/commonMain/kotlin/com/dixon/app/kmmsample/core/base/Logger.kt package com.dixon.app.kmmsample.core.base object Logger { enum class LogLevel { DEBUG, INFO, WARNING, ERROR, NONE } var currentLogLevel: LogLevel = LogLevel.NONE private set var isLoggingEnabled: Boolean = false private set // 首次调用 Ln 时执行 init { enableLogging(true) setLogLevel(LogLevel.DEBUG) } fun setLogLevel(level: LogLevel) { currentLogLevel = level } fun enableLogging(enable: Boolean) { isLoggingEnabled = enable } fun d(tag: String, message: () -> String) { if (isLoggingEnabled && currentLogLevel <= LogLevel.DEBUG) { print(LogLevel.DEBUG, tag, message.invoke()) } } fun i(tag: String, message: () -> String) { if (isLoggingEnabled && currentLogLevel <= LogLevel.INFO) { print(LogLevel.INFO, tag, message.invoke()) } } fun w(tag: String, message: () -> String) { if (isLoggingEnabled && currentLogLevel <= LogLevel.WARNING) { print(LogLevel.WARNING, tag, message.invoke()) } } fun e(tag: String, message: () -> String) { if (isLoggingEnabled && currentLogLevel <= LogLevel.ERROR) { print(LogLevel.ERROR, tag, message.invoke()) } } } internal expect fun print(level: Logger.LogLevel, tag: String, message: String) 在不同平台上,输出日志的方式是不一致的(如 Android 使用 Log,iOS 使用 NSLog)。为了支持跨平台的开发,Kotlin 提供了 expect 和 actual 关键字。expect 声明用于在共享模块中声明一个平台无关的接口或方法,而 actual 声明用于在平台特定的模块中实现这些接口或方法。实际上项目依赖的基础能力库都是通过这样的方式实现了跨平台能力。 实现 Android 平台代码 // shared/src/androidMain/kotlin/com/dixon/app/kmmsample/core/base/Logger.android.kt package com.dixon.app.kmmsample.core.base import android.util.Log internal actual fun print(level: Logger.LogLevel, tag: String, message: String) { when (level) { Logger.LogLevel.INFO -> Log.i(tag, message) Logger.LogLevel.DEBUG -> Log.d(tag, message) Logger.LogLevel.WARNING -> Log.w(tag, message) Logger.LogLevel.ERROR -> Log.e(tag, message) Logger.LogLevel.NONE -> Log.i(tag, message) } } 实现 iOS 平台代码 // shared/src/iosMain/kotlin/com/dixon/app/kmmsample/core/base/Logger.ios.kt package com.dixon.app.kmmsample.core.base import platform.Foundation.NSLog /* 关于在 iosMain 中使用 ios 原生/第三方 api: 1. 在 Kotlin Multiplatform 项目中,NSLog 以及其他 NS 前缀的函数和类来自于 Kotlin/Native 提供的对 Apple 平台(iOS 和 macOS)的基础库的绑定。 这些绑定是 Kotlin/Native 标准库的一部分,允许你在 Kotlin 代码中直接使用 Apple 平台的 API。 2. 通过使用 Kotlin/Native 提供的 CocoaPods 集成,你可以在 Kotlin Multiplatform 项目中使用 iOS 的第三方 SDK。 */ internal actual fun print(level: Logger.LogLevel, tag: String, message: String) { val logMessage = when (level) { Logger.LogLevel.DEBUG -> "DEBUG[$tag]: $message" Logger.LogLevel.INFO -> "INFO[$tag]: $message" Logger.LogLevel.WARNING -> "WARN[$tag]: $message" Logger.LogLevel.ERROR -> "ERROR[$tag]: $message" Logger.LogLevel.NONE -> "NONE[$tag]: $message" } NSLog(logMessage) } 使用日志 Logger.i(TAG) { "Request succeed: $xx" } IV. 图片库 介绍 一般应用都有展示本地图片、网络图片的能力,本地图片涉及本地资源,我们先跳过,这里我们演示如何展示网络图片。 这里我选用 Coil3 库提供的跨平台图片库能力。 Coil3 目前只有 Alpha 版本,有精力也可以通过桥接各端图片库自行实现。 依赖管理和 Gradle 配置 配置所需依赖 [versions] coil3 = "3.0.0-alpha08" # 09 没有对应版本的 ktor [libraries] coil3-core = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" } coil3-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil3" } coil3-ktor = { module = "io.coil-kt.coil3:coil-network-ktor", version.ref = "coil3" } 配置共享模块 val commonMain by getting { dependencies { // put your multiplatform dependencies here ... // 图片库 目前还是Alpha版本 implementation(libs.coil3.core) implementation(libs.coil3.ktor) implementation(libs.coil3.compose) } } 使用图片库 AsyncImage( modifier = Modifier.fillMaxWidth().align(Alignment.Center) .clip(RoundedCornerShape(10.dp)), model = resource, contentDescription = null, contentScale = ContentScale.FillWidth ) 在 Demo 项目里,我创建了一个新的页面用于展示网络库请求到的狗狗图片,使用导航可以携带图片 url 跳转到该页面,并且通过 navigator.goBack() 可以重新返回至上一级页面。 package com.dixon.app.kmmsample.ui.image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import coil3.compose.AsyncImage import com.dixon.app.kmmsample.core.base.Logger import com.dixon.app.kmmsample.ui.LocalNavigator @Composable fun ImageDetail(resource: String?) { Logger.i("ImageDetail") { "Composable Root : ImageDetail $resource" } val navigator = LocalNavigator.current if (resource.isNullOrEmpty()) { Logger.i("ImageDetail") { "Composable Root : ImageDetail ErrorTipPage" } Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() ) { Text( text = "Error", fontSize = 20.sp, fontWeight = FontWeight.ExtraBold ) Spacer(Modifier.height(20.dp)) Text(text = "Please request image first!") Spacer(Modifier.height(20.dp)) Button(onClick = { navigator.goBack() }) { Text("返回") } } } else { Logger.i("ImageDetail") { "Composable Root : ImageDetail ContentPage" } Box( modifier = Modifier .fillMaxSize() .background(Color.Black) ) { /* or ImageRequest、Painter */ AsyncImage( modifier = Modifier.fillMaxWidth().align(Alignment.Center) .clip(RoundedCornerShape(10.dp)), model = resource, contentDescription = null, contentScale = ContentScale.FillWidth ) Button(onClick = { navigator.goBack() }) { Text("Back") } } } } 建议结合 示例仓库 查看相关代码。 V. 本地资源 介绍 Compose UI 支持跨平台,相应的,string、image、fonts、colors 等本地资源也需要跨平台支持。 moko-resources 是一个 Kotlin Multiplatform 库和 Gradle 插件,旨在提供在 macOS、iOS、Android、JVM 和 JS/Browser 上访问本地资源的功能。 依赖管理和 Gradle 配置 配置根目录 build.gradle buildscript { dependencies { // 本地资源配置1 classpath("dev.icerock.moko:resources-generator:0.24.1") } } 配置共享模块 plugins { ... // 本地资源配置2 id("dev.icerock.mobile.multiplatform-resources") } kotlin { sourceSets { val commonMain by getting { dependencies { //put your multiplatform dependencies here ... // 本地资源配置3 api("dev.icerock.moko:resources:0.24.1") api("dev.icerock.moko:resources-compose:0.24.1") } } ... } } // 本地资源配置 4 multiplatformResources { resourcesPackage.set("com.dixon.app.kmmsample") // required // resourcesClassName.set("SharedRes") // optional, default MR // resourcesVisibility.set(MRVisibility.Internal) // optional, default Public iosBaseLocalizationRegion.set("en") // optional, default "en" iosMinimalDeploymentTarget.set("16.0") // optional, default "9.0" } iOS 配置 由于 iOS 是静态库,不支持直接打包资源,因此需要通过脚本将资源拷贝到 iOS 应用中。 配置步骤 3.1. 在 gradle.properties 中添加以下配置: moko.resources.disableStaticFrameworkWarning=true 3.2. 在 Xcode 中配置脚本: * 打开 `iosApp` * 点击 `iosApp` 目录 * 选择 `Build Phases` * 点击 "+" * 选择 `New Run Script Phase` * 将以下脚本粘贴进去: # Type a script or drag a script file from your workspace to insert its path. "$SRCROOT/../gradlew" -p "$SRCROOT/../" :shared:copyFrameworkResourcesToApp -Pmoko.resources.BUILT_PRODUCTS_DIR="$BUILT_PRODUCTS_DIR" -Pmoko.resources.CONTENTS_FOLDER_PATH="$CONTENTS_FOLDER_PATH" -Pkotlin.native.cocoapods.platform="$PLATFORM_NAME" -Pkotlin.native.cocoapods.archs="$ARCHS" -Pkotlin.native.cocoapods.configuration="$CONFIGURATION" 更多资源配置与脚本配置细节详见 GitHub 项目文档 使用本地资源 添加本地资源 新增资源目录: MyKMMApp/ ├── shared/ # 共享模块 │ ├── src/ │ │ ├── commonMain/ # 共享的主要代码(跨平台) │ │ │ ├── kotlin/ # 共享的 Kotlin 源代码文件夹 │ │ │ ├── moko-resources/ # 共享的本地资源文件夹 │ │ │ │ ├── images/ # 共享的本地图片资源文件夹 │ │ │ │ │ ├── demo@2x.png # 共享的本地图片资源 │ │ │ │ ├── base/ # 共享的本地字符串资源文件夹 │ │ │ │ │ ├── strings.xml # 共享的本地字符串资源 │ │ │ │ ├── zh-CN/ # 共享的本地字符串资源文件夹 中文 │ │ │ │ │ ├── strings.xml # 共享的本地字符串资源 │ │ │ ├── ... │ ├── ... ├── ... 使用资源 // 图片 painterResource(MR.images.demo) // 字符串 stringResource(MR.strings.welcome_message) 详见 示例仓库 。 VI. 效果演示 示例仓库 演示: 首页.png   图片演示页.png 6. KMM & Compose Multiplatform 进阶 & 畅想 使用 KMM(Kotlin Multiplatform Mobile)和 Compose Multiplatform 结合开发具有如下优点: 优点 共享逻辑:利用 KMM 共享业务逻辑和数据处理代码。 共享 UI:通过 Compose Multiplatform 共享 UI 组件和布局。 这种结合使得开发人员可以在业务逻辑和界面上均实现代码共享,从而提高开发效率和一致性,同时保留对各个平台进行特定优化和实现的灵活性。 跨平台特性 由于 KMM 和 Compose Multiplatform 出色的跨平台特性,即便是新的平台也可以低成本支持。据笔者所知,某厂支持了包括鸿蒙平台在内的跨移动三端的 KMM & Compose Multiplatform,并基于此开发跨三端的业务代码。相信这些代码不久的将来就会开源出来。 支持鸿蒙平台 Compose Multiplatform 仍然可以基于 Skiko 渲染,KMM 可以使用 Kotlin/JS 桥接 ArkTs,但是 Kotlin 的很多功能特性会丧失。因此,建议使用 Kotlin/Native + N-API 桥接层,这样可以用 Kotlin 语言编写高性能的本地代码,并将其作为原生扩展在 Node.js 应用程序中使用。 基本原理: 编写 Kotlin/Native 代码:使用 Kotlin/Native 编写需要实现的功能代码,并将其编译为共享库(例如 .so 文件)。 使用 N-API:在 Node.js 端使用 N-API 管理和调用这一共享库。 7. 进阶学习和资源 Kotlin Multiplatform Mobile 官方文档 KMM 官方博客,最新消息 Compose-multiplatform 官方 Github KMM 开源三方库整合 8. 总结 Kotlin Multiplatform Mobile (KMM) 和 Compose Multiplatform 未来充满前景。以下是一些核心展望: 更多生产级应用:更多企业和开发者将采用这些技术构建跨平台应用。 改进的工具链:IntelliJ IDEA 和 Android Studio 将提供更强大的支持,提升开发效率。 更多平台支持:支持范围将扩展到桌面和 Web 平台,实现更广泛的跨平台覆盖。 性能优化:进一步提升生成代码的性能,接近原生体验。 壮大的库生态系统:更多第三方库和插件将出现,简化开发过程。 社区贡献:活跃的社区将提供丰富的开源项目和知识分享,推动技术发展。 改进的文档和教育资源:官方和社区将提供更全面的教程和学习材料,助力开发者快速上手。 广泛的企业级应用采纳:随着技术的成熟和优化,更多企业将采用这些技术开发关键应用。 总之,KMM 和 Compose Multiplatform 将大幅简化跨平台开发,提高效率和应用质量,成为跨平台开发的重要选择。 [TOC] --- 由 答源 DAN 底座 1.4.1 生成 · 2026-11-13T11:20:16+00:00