1. 引言:为什么 KMP 能走向全栈

介绍 Kotlin Multiplatform 从移动端共享逻辑走向全栈开发的背景,说明 KMP 在 Android、iOS、服务端以及 AI Agent 场景中的定位与价值。

2. KMP 基础与工程架构

梳理 KMP 的核心概念、共享模块划分、依赖注入与分层架构,为后续全栈扩展打下基础。

下图展示了 KMP 全栈工程的整体架构:共享模块 commonMain 承载业务逻辑与数据模型,Android 与 iOS 平台层分别通过 expect/actual 机制提供平台实现,服务端模块则复用共享代码对外提供接口,各模块之间通过依赖注入保持清晰边界。

flowchart TD
    A[commonMain 共享模块] --> B[androidMain Android 平台层]
    A --> C[iosMain iOS 平台层]
    A --> D[服务端模块]
    B --> E[Android App]
    C --> F[iOS App]
    D --> G[后端服务]
    E --> D
    F --> D

3. 从 Android 到多平台:共享业务逻辑

讲解如何将 Android 项目中的网络层、数据层、领域层迁移到 KMP 共享模块,并处理平台差异。下面以网络层为例,展示如何使用 Ktor 客户端在共享模块中定义接口,并在 Android 和 iOS 中调用。

// 共享模块 commonMain 中定义网络接口
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class User(val id: Int, val name: String)

// 定义统一的网络服务接口
interface UserApi {
    suspend fun getUser(id: Int): User
}

// 使用 Ktor 实现该接口,平台差异通过 expect/actual 处理
class UserApiImpl(private val client: HttpClient) : UserApi {
    override suspend fun getUser(id: Int): User =
        client.get("https://api.example.com/users/$id").body()
}

// 创建 HttpClient 的公共工厂,引擎由各平台提供
expect fun createHttpClient(): HttpClient

// 各平台通过 actual 提供对应的引擎与配置
// Android 端(androidMain):
// actual fun createHttpClient(): HttpClient = HttpClient(Android) {
//     install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
//     // Android 平台可在此配置网络缓存、超时等参数
// }
//
// iOS 端(iosMain):
// actual fun createHttpClient(): HttpClient = HttpClient(Darwin) {
//     install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
//     // iOS 平台可在此配置 TLS 证书校验、代理等参数
// }

上述示例中,expect 与 actual 机制用于处理平台差异:共享模块只声明 createHttpClient() 的签名,而 Android 与 iOS 各自提供基于 Android 或 Darwin 引擎的实现,并可在其中配置平台特有的网络参数,从而在共享业务逻辑的同时保留平台灵活性。

为了更直观地对比三种方案在实现网络层、数据层和领域层时的差异,下表从代码量、维护成本和平台差异处理方式三个维度进行梳理:

维度Android 原生iOS 原生KMP 共享代码
网络层代码量使用 Retrofit/OkHttp 编写,需为每个接口单独定义 Service 与数据模型,代码量较大。使用 URLSession/Alamofire 编写,需为每个接口单独定义请求与模型,代码量较大。在 commonMain 中用 Ktor 统一编写一次接口与模型,Android 与 iOS 复用,代码量显著减少。
数据层代码量Room 数据库、Repository 与 DAO 均需在 Android 侧单独实现。CoreData/SwiftData 与 Repository 均需在 iOS 侧单独实现。数据模型与 Repository 逻辑在 commonMain 共享,仅数据库驱动通过 expect/actual 按平台接入。
领域层代码量用例(UseCase)与业务规则在 Android 侧编写,无法复用。用例与业务规则在 iOS 侧重新编写,逻辑重复。领域层全部沉淀在 commonMain,业务规则只写一次,两端行为天然一致。
维护成本业务逻辑变更需同步修改 Android 代码,并单独维护测试。同一业务逻辑需在 iOS 侧重复实现与测试,双倍维护成本。核心逻辑集中维护,改动一次即可同步两端,测试也只需在共享模块中编写一次。
平台差异处理平台差异天然隔离,但无法复用,需各自处理网络、存储与线程。平台差异天然隔离,但无法复用,需各自处理网络、存储与线程。通过 expect/actual 机制在共享模块声明接口,各平台提供实现,兼顾复用与灵活性。

从表中可以看出,KMP 在代码量、维护成本和平台差异处理上都具有明显优势:共享模块将网络层、数据层和领域层的核心逻辑收敛到一处,既避免了双端重复开发,又通过 expect/actual 保留了平台定制能力,是跨端业务逻辑共享的高效方案。

4. 服务端开发:KMP 与 Kotlin 后端

介绍使用 Kotlin 编写服务端逻辑、共享模型与序列化方案,打通客户端与服务端的代码复用路径。下面以 Ktor 为例,展示如何在服务端复用 commonMain 中定义的数据模型,并对外提供 REST 接口。

// 服务端模块(如 serverMain)中搭建 Ktor 应用
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json

// 直接复用 commonMain 中定义的数据模型,无需在服务端重复声明
// 例如:@Serializable data class User(val id: Int, val name: String)
// 该模型同时被 Android、iOS 客户端与服务端共享,保证字段与序列化格式完全一致

fun main() {
    // 启动函数:创建并启动 Netty 引擎的 Ktor 服务
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        module()
    }.start(wait = true)
}

fun Application.module() {
    // 配置 JSON 序列化:与客户端保持相同的 Json 配置,确保字段解析一致
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
            prettyPrint = true
        })
    }

    // 路由定义:复用共享模型作为响应体,客户端可直接反序列化
    routing {
        get("/users/{id}") {
            val id = call.parameters["id"]?.toIntOrNull() ?: 0
            // 这里直接使用 commonMain 中的 User 模型构造响应
            call.respond(User(id = id, name = "KMP 全栈用户"))
        }
        get("/health") {
            call.respond(mapOf("status" to "ok"))
        }
    }
}

上述示例中,服务端通过 Ktor 直接复用 commonMain 中的 User 数据模型,客户端与服务端共享同一份序列化定义,字段变更时两端无需分别维护。路由、序列化配置与启动函数均集中在服务端模块,而数据模型与业务逻辑仍沉淀在共享模块,从而实现真正的全栈代码复用。

5. 接入 AI Agent:KMP 与 LLM 应用

探讨如何在 KMP 工程中集成大模型调用、提示词管理、工具调用与流式响应,构建 AI Agent 能力。

6. 实战案例:一个跨端 AI 助手

通过一个具体案例串联 Android、服务端与 AI Agent,展示 KMP 全栈开发的完整落地流程。下面我们以「跨端 AI 助手」为例,从数据模型定义、服务端接口、双端调用到流式回复展示,逐步拆解实现步骤。

6.1 定义共享数据模型

AI 助手涉及请求、响应与消息记录三类核心数据。我们将它们统一放在 commonMain 中,借助 kotlinx.serialization 实现跨端序列化,保证 Android、iOS 与服务端使用同一份定义。

// commonMain 中定义 AI 助手的数据模型
package com.example.aiassistant.model

import kotlinx.serialization.Serializable

// 单条对话消息
@Serializable
data class ChatMessage(
    val role: String,      // "user" 或 "assistant"
    val content: String
)

// 客户端发送给服务端的对话请求
@Serializable
data class ChatRequest(
    val messages: List<ChatMessage>,
    val stream: Boolean = true
)

// 服务端返回的对话响应(非流式场景)
@Serializable
data class ChatResponse(
    val reply: String,
    val messageId: String? = null
)

这三个模型被所有端共享:客户端用它构造请求、解析响应,服务端用它接收参数、返回结果。字段一旦变更,只需修改 commonMain 一处,双端与服务端自动同步,避免重复维护。

6.2 服务端复用模型提供对话接口

服务端模块直接复用 commonMain 中的 ChatRequest 与 ChatResponse,通过 Ktor 对外暴露一个 POST 接口。服务端收到请求后调用大模型,并把结果封装成共享模型返回。

// serverMain 中搭建对话接口
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.routing.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json
import com.example.aiassistant.model.ChatRequest
import com.example.aiassistant.model.ChatResponse

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        module()
    }.start(wait = true)
}

fun Application.module() {
    install(ContentNegotiation) {
        json(Json { ignoreUnknownKeys = true })
    }

    routing {
        // 对话接口:复用 commonMain 中的 ChatRequest / ChatResponse
        post("/api/chat") {
            val request = call.receive<ChatRequest>()
            // 这里调用大模型(如 OpenAI、Claude 等),此处以简化逻辑示意
            val reply = "你好,我是 AI 助手。你刚才说:${request.messages.lastOrNull()?.content}"
            call.respond(ChatResponse(reply = reply))
        }
    }
}

由于请求与响应模型都来自 commonMain,服务端无需重复声明字段,客户端与服务端的序列化格式天然一致,接口联调成本大幅降低。

6.3 Android 客户端调用接口并展示流式回复

Android 端复用共享模块中的 ChatApi 与数据模型,通过 Ktor 客户端发起请求。为展示流式回复,我们使用 SSE(Server-Sent Events)逐行读取服务端推送的内容,并实时更新界面。

// androidMain 中调用对话接口并展示流式回复
import androidx.compose.runtime.*
import com.example.aiassistant.model.ChatMessage
import com.example.aiassistant.model.ChatRequest
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*

// 共享模块中定义的接口
interface ChatApi {
    suspend fun sendMessage(messages: List<ChatMessage>): String
}

// Android 端实现:使用 Ktor 发起请求,按行读取流式响应
class AndroidChatApi(private val client: HttpClient) : ChatApi {
    override suspend fun sendMessage(messages: List<ChatMessage>): String {
        val request = ChatRequest(messages = messages, stream = true)
        val response = client.post("http://10.0.2.2:8080/api/chat") {
            contentType(ContentType.Application.Json)
            setBody(request)
        }
        // 简化处理:这里直接返回 body 文本,实际可按行解析 SSE 事件
        return response.bodyAsText()
    }
}

// Compose 界面中维护消息列表并实时追加流式内容
@Composable
fun ChatScreen(chatApi: ChatApi) {
    var messages by remember { mutableStateOf(listOf<ChatMessage>()) }
    var input by remember { mutableStateOf("") }

    // 发送消息并展示流式回复
    fun send() {
        val userMsg = ChatMessage(role = "user", content = input)
        messages = messages + userMsg
        // 启动协程调用接口,将返回内容追加为 assistant 消息
        // (实际项目中可结合 Flow 逐段更新 UI)
    }

    // 界面渲染逻辑省略
}

Android 端通过 AndroidChatApi 复用共享模型与接口定义,界面层只负责展示。流式回复可结合协程 Flow 将服务端推送的每个片段逐段追加到消息列表,实现打字机效果。

6.4 iOS 客户端调用接口并展示流式回复

iOS 端同样复用 commonMain 中的模型与接口,通过 Ktor 的 Darwin 引擎发起请求。SwiftUI 界面通过 ObservableObject 监听消息状态,收到流式片段后实时刷新列表。

// iosMain 中调用对话接口
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import com.example.aiassistant.model.ChatMessage
import com.example.aiassistant.model.ChatRequest

// iOS 端实现:使用 Darwin 引擎的 HttpClient
class IosChatApi(private val client: HttpClient) : ChatApi {
    override suspend fun sendMessage(messages: List<ChatMessage>): String {
        val request = ChatRequest(messages = messages, stream = true)
        val response = client.post("http://localhost:8080/api/chat") {
            contentType(ContentType.Application.Json)
            setBody(request)
        }
        return response.bodyAsText()
    }
}

// SwiftUI 侧通过桥接层调用 Kotlin 的 IosChatApi
// 收到流式片段后,使用 @Published 属性驱动列表刷新

iOS 端与 Android 端共享同一套 ChatApi 接口与数据模型,仅引擎实现不同。业务逻辑、序列化与接口契约全部沉淀在 commonMain,双端行为保持一致。

6.5 整体调用流程

下图展示了跨端 AI 助手的完整调用链路:双端客户端复用共享模型构造请求,服务端接收后调用大模型,再以流式方式把回复推回客户端实时展示。

flowchart LR
    A[Android App] -->|ChatRequest| B[Ktor 服务端]
    C[iOS App] -->|ChatRequest| B
    B -->|调用大模型| D[LLM / AI Agent]
    D -->|流式回复| B
    B -->|ChatResponse 流式推送| A
    B -->|ChatResponse 流式推送| C
    A -->|复用 commonMain 模型| E[commonMain]
    C -->|复用 commonMain 模型| E

通过上述步骤,一个跨端 AI 助手便完成了从数据模型、服务端接口到双端流式展示的完整闭环。所有核心代码集中在 commonMain,服务端与客户端共享同一份定义,真正体现了 KMP 全栈开发的价值。

7. 总结与展望

通过前文从工程架构、跨端业务共享、服务端开发到 AI Agent 接入的完整梳理,以及「跨端 AI 助手」这一实战案例的落地,KMP 全栈开发的价值与挑战已经清晰呈现。下面从收益、挑战与未来演进三个维度进行总结。

7.1 关键收益

结合前文案例,KMP 全栈开发在代码复用、开发效率与团队协作三方面带来了显著收益:

  • 代码复用:正如 6.1 节所示,ChatMessage、ChatRequest 与 ChatResponse 三个数据模型统一沉淀在 commonMain,Android、iOS 与服务端共享同一份定义。字段一旦变更,只需修改一处即可同步三端,彻底消除了双端与服务端重复维护模型的问题。第 3 节中网络层、数据层与领域层全部收敛到共享模块,也印证了业务逻辑只写一次、多端复用的核心价值。
  • 开发效率:6.2 至 6.4 节展示了服务端与双端客户端如何复用同一套 ChatApi 接口与模型。服务端无需重复声明字段,客户端与服务端序列化格式天然一致,接口联调成本大幅降低。第 4 节中服务端直接复用 User 模型构造响应,进一步说明全栈复用能显著减少重复编码与联调时间,让团队把精力集中在业务本身。
  • 团队协作:共享模块成为团队统一的「契约层」,客户端与服务端开发者围绕同一份模型与接口协作,减少了跨端沟通成本。6.5 节的整体调用流程表明,双端行为因共享逻辑而天然一致,测试也只需在共享模块中编写一次,降低了多端回归验证的负担,让团队协作更加顺畅。

7.2 当前面临的挑战

尽管收益明显,KMP 全栈开发在落地过程中仍面临以下挑战:

  • 学习曲线:开发者需要同时掌握 Kotlin 多平台工程结构、expect/actual 机制、各平台引擎差异(如 Android 与 Darwin 引擎的配置)以及服务端框架(如 Ktor),上手门槛相对较高。第 3 节中 createHttpClient() 的跨平台实现就要求开发者理解多平台构建与平台差异处理。
  • 生态成熟度:部分第三方库对 KMP 的支持仍在完善中,某些平台特定能力(如 iOS 的 CoreData 深度集成)仍需通过 expect/actual 自行封装,生态相比纯原生方案仍有差距。第 3 节表格中数据层需按平台接入数据库驱动,正体现了这一现状。
  • 构建复杂度:多平台工程的构建配置、依赖管理与平台目标(Android、iOS、服务端)的协调较为复杂,编译时间与调试成本也高于单一平台项目。第 2 节架构图中多模块的依赖注入与边界划分,需要团队投入额外精力维护。

7.3 展望:AI Agent 场景下的演进方向

随着大模型与 AI Agent 的普及,KMP 全栈开发将迎来新的演进机遇,结合前文案例可预见以下方向:

  • 共享 AI 编排逻辑:提示词管理、工具调用与上下文组装等 AI 编排逻辑可像 ChatApi 一样沉淀在 commonMain,双端与服务端复用同一套 Agent 行为,确保多端体验一致,同时降低重复实现成本。
  • 端侧智能与隐私计算:借助 KMP 共享模型,可在客户端本地运行轻量模型或执行敏感数据预处理,减少与服务端的交互,兼顾实时性与隐私保护,这与 6.3 节流式回复的本地展示能力相辅相成。
  • Agent 服务化与全栈闭环:服务端可复用共享模型将 Agent 能力封装为标准化接口,客户端通过 Ktor 统一调用,形成「共享模型 + 服务端 Agent + 双端交互」的全栈闭环,进一步放大 KMP 在 AI 驱动应用中的复用价值。

总体而言,KMP 全栈开发已在代码复用、开发效率与团队协作上展现出切实收益,虽面临学习曲线、生态与构建复杂度等挑战,但在 AI Agent 场景下,其共享逻辑与全栈复用的优势将愈发凸显,值得团队持续投入与探索。

Logo

一站式 AI 云服务平台

更多推荐