🚀 一、背景与挑战

KMP (Kotlin Multiplatform) 正在成为越来越多企业的跨端选型。在 2025 Kotlin 中文开发者大会上,某大型App团队分享了覆盖“渲染一致性、性能瓶颈、多端协作、工程化体系”等典型难题的实战经验。

核心讲师:资深架构师(移动端架构与跨端基建方向负责人),长期聚焦架构与研发效能。


📊 二、KMP 三端落地背景与核心结果

2.1 为什么大型App需要 KMP?

不是“想跨端”,而是**“不得不提效”**。
面临的现实拷问:

  • 鸿蒙适配工程量巨大:以 ArkTS 为主,完全重写不现实。
  • 复用存量代码:希望复用 Android 存量代码来降低迁移与维护成本。

2.2 阶段性结果

某头部App在鸿蒙 KMP 落地中取得了以下工程化指标:

  • 覆盖率:覆盖鸿蒙业务 70%。
  • 迁移量:迁移代码占鸿蒙总代码 40%+。
  • 提效:整体提效 30%+。
  • 标杆案例:IMSDK(11 万行代码),由 1 名 iOS 工程师完成迁移,迁移速度 3500+ 行/天,提效 50%+。

💡 初学者抓重点
KMP 落地不是一句“代码复用率多少”就结束,而是要回答三件事:

  1. 迁移速度能不能快?
  2. 线上稳定性能不能扛?
  3. 长期工程成本能不能降?

🛠 三、KMP 迁移过程解析

3.1 你以为的 KMP(理想态)

理想态通常是:业务逻辑搬到 shared 模块,三端 UI 自己画,剩下用 expect/actual 把平台能力补齐。

// commonMain
expect class PlatformLogger() {
    fun i(tag: String, msg: String)
}

// androidMain
actual class PlatformLogger {
    actual fun i(tag: String, msg: String) = android.util.Log.i(tag, msg)
}

// iosMain
import platform.Foundation.NSLog
actual class PlatformLogger {
    actual fun i(tag: String, msg: String) = NSLog("[$tag] $msg")
}

3.2 现实做法:渐进式迁移

这类“巨型 App”的现实做法是:迁移 Android 存量代码
先做语法转换(Java→Kotlin),再做平台依赖解耦,最终编译为多平台产物接入。

+--------------------------------+
|  存量 Android/Java 业务逻辑     |
+--------------------------------+
               ↓
+--------------------------------+
|  语法转换: Java -> Kotlin       |
+--------------------------------+
               ↓
+--------------------------------+
|  解耦平台依赖                   |
|  (抽接口/expect-actual/注入)    |
+--------------------------------+
               ↓
+--------------------------------+
|  编译成多端产物                 |
|  (Android/iOS/鸿蒙)            |
+--------------------------------+
               ↓
+--------------------------------+
|  各端原生 UI & 壳工程接入        |
+--------------------------------+

3.3 关键技术:“胶水层注入”

大项目里,业务代码到处在调平台能力(网络、存储、日志、线程)。实践中强调**“原生能力注入”**,Wrapper 接入简单,业务无感知。

可以用一个最小可用的 Service Manager(能力注入容器) 把平台差异隔离:

// [commonMain] 1. 定义能力接口
interface DeviceInfo {
    fun osName(): String
    fun osVersion(): String
}

object ServiceManager {
    // 简化写法:实际可做成多实例 + key
    lateinit var deviceInfo: DeviceInfo
}

// [commonMain] 2. 业务代码只依赖接口
class UserAgentBuilder {
    fun build(): String {
        val di = ServiceManager.deviceInfo
        return "MyApp/${di.osName()} ${di.osVersion()}"
    }
}

// [androidMain] 3. 注入实现
class AndroidDeviceInfo : DeviceInfo {
    override fun osName() = "Android"
    override fun osVersion() = android.os.Build.VERSION.RELEASE ?: "unknown"
}

// Application.onCreate()
ServiceManager.deviceInfo = AndroidDeviceInfo()

🏗 四、生产直播链路 KMP 落地实践

4.1 架构阶段:数据模型收敛

:对象拷贝(成本高 & 一致性风险)。如果把原生 Model 全量 copy 到 shared,会出现字段变更同步难、大对象拷贝性能差等问题。

解法:对象代理 (Proxy) + 单实例
核心思路:shared 不拥有“数据实体”,只拥有“访问契约”。

// [commonMain] 用接口定义“字段访问契约”
interface LiveRoomProfile {
    val roomId: String
    val anchorId: String
    val title: String?
}

// [commonMain] 业务只依赖接口
class LiveEntryUseCase {
    fun canShowTitle(p: LiveRoomProfile): Boolean = !p.title.isNullOrBlank()
}

// [androidMain] 把原生 Java/Kotlin Model 包起来
class LiveRoomProfileProxy(private val raw: NativeLiveRoomModel) : LiveRoomProfile {
    override val roomId get() = raw.roomId
    override val anchorId get() = raw.anchorId
    override val title get() = raw.title
}

4.2 开发阶段:跨语言调用 (K/N ↔ C/C++)

4.2.1 C++ 调用策略
  • C:K/N 对 C 友好,直接用 cinterop
  • C++:需要“C API 桥”。常见做法是把 C++ 能力用 extern "C" 导出成 C 接口。
// [C++ Side] foo_bridge.h
#ifdef __cplusplus
extern "C" {
#endif

typedef void* FooHandle;
FooHandle foo_create();
void foo_destroy(FooHandle h);
int foo_sum(FooHandle h, int a, int b);

#ifdef __cplusplus
}
#endif
// [Kotlin/Native Side]
import kotlinx.cinterop.*

class Foo(private val handle: COpaquePointer) {
    companion object {
        fun create(): Foo = Foo(foo_create()!!)
    }

    fun sum(a: Int, b: Int): Int = foo_sum(handle, a, b)
    fun close() { foo_destroy(handle) }
}
4.2.2 生命周期绑定 (Cleaner)

在 Kotlin/Native 中,createCleaner 是一个至关重要的 API(类似 Java 的 Cleaner 或 C++ 的析构函数),用于自动管理非 Kotlin 堆内存资源

  • 原理:它将一个 Cleanup Action(清理闭包)绑定到一个 Kotlin 对象上。当该 Kotlin 对象被 GC 回收时,运行时会自动执行这个闭包。
  • 用途:主要用于释放 C/C++ 侧分配的内存(如 mallocnew)或关闭文件句柄,防止内存泄漏。
  • 注意:清理闭包不能捕获被绑定对象本身,否则会导致循环引用无法回收(Cleaners are not guaranteed to run if the object is reachable)。
import kotlin.native.ref.createCleaner

class FooSafe private constructor(
    private val handle: COpaquePointer
) {
    // ⚠️ 关键点:handle 必须作为参数传入闭包,不能直接在闭包里用 this.handle
    // 这样当 FooSafe 实例被 GC 时,cleaner 就会自动调用 foo_destroy(h)
    @OptIn(ExperimentalStdlibApi::class)
    private val cleaner = createCleaner(handle) { h ->
        foo_destroy(h) // 兜底释放 native 资源
    }
}
4.2.3 循环引用 (WeakReference)

使用 WeakReference 打断循环引用。

import kotlin.native.ref.WeakReference

class Node {
    var next: Node? = null
    var prev: WeakReference<Node>? = null // 弱引用避免环
}

4.3 调试与上线阶段

4.3.1 GC 卡顿排查

不要瞎猜 GC,用工具验证。开启 Safepoint Signposts,在 Apple 平台的 Instruments 中追踪 GC 暂停。

gradle.properties:

kotlin.native.binary.enableSafepointSignposts=true
4.3.2 产物与包体
  • iOS: Framework
  • Android/鸿蒙: SO / AAR
  • 建议: 大工程统一出包入口,利用“壳工程”减少 Runtime 符号冗余。

🎯 五、标杆业务场景:怎么“选业务”?

优先上 KMP 的业务 (胜率高)

  • SDK/中间件:IM、上传、弹幕、埋点、加解密(逻辑重、UI 少)。
  • 高并发/高 IO:网络链路、数据流处理、序列化、缓存。
  • 跨端一致性强诉求:同一套规则/策略必须三端一致。

暂缓上 KMP 的业务 (坑多)

  • UI 复杂且强依赖平台控件(除非有 CMP 或自研 UI 映射层)。
  • 深度依赖各端生态差异(如专属系统能力、权限、后台策略)。

🔮 六、未来规划

未来的 KMP 演进路线:

  1. 场景扩张:从“鸿蒙落地提效” -> “三端一码多投” -> “全场景跨端”。
  2. 工程优化:持续改进 K/N 的 GC 性能、包体大小、断点调试体验。
  3. 社区共建:计划开源 KMP 方案。

❓ 七、QA 高频问题

问题 核心结论
KMP 是“写一次,到处跑”吗? 不是。是“共享业务逻辑,生成平台产物”。
为什么适合“渐进式迁移”? 可以从 shared 模块开始(网络/存储/策略),逐步蚕食存量代码。
iOS 卡顿怎么查? 开启 safepoint signposts,用 Instruments 对齐卡顿时间线。
C++ 怎么调? 先做 C API Bridge,再用 cinterop

📝 附录:关键词小词典

  • KMP (Kotlin Multiplatform): 把 Kotlin 编译到多平台目标产物的体系。
  • K/N (Kotlin/Native): Kotlin 编译为原生可执行/库,支持与 C/ObjC/Swift 互操作。
  • IR (Intermediate Representation): Kotlin 编译中间表示,用于优化与生成代码。
  • cinterop: K/N 与 C 互操作工具链(解析头文件生成绑定)。
  • Cleaner: Native 对象回收后的清理回调。
  • Safepoint Signposts: 标记 GC 暂停的调试工具。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐