
做安卓的人应该都有这种感觉这两年KMPKotlin Multiplatform这个词在技术社区出现的频率越来越高但很多人一看到缩写就下意识想到那个字符串匹配算法然后一脸懵。这里说的KMP是JetBrains推出的Kotlin跨平台方案目标是用一套Kotlin代码同时支撑Android、iOS、桌面端和Web端的业务逻辑开发。我最初接触它的时候也觉得有点鸡肋毕竟Flutter和React Native已经占了很大市场但真正把一个小项目跑通、并且让iOS和Android两边共用同一套网络层和数据处理逻辑之后我改变了一个看法KMP可能是当前最符合安卓工程师既有技能树的跨平台方案。这篇文章不是教你照搬某个模板而是把我在实际迁移过程中总结出来的能力模型和实战路径完整拆开讲一遍。内容包括安卓工程师在KMP转型中到底要补哪些能力、哪些已有的经验可以直接复用、从零搭建一个KMP项目的完整流程、常见坑的排查方式以及团队切换到KMP之后工程规范和协作模式怎么调整。适合正在评估KMP、打算用KMP改造现有安卓项目、或者想在团队内部推动跨平台技术方案的技术人员。1. 能力模型全景从安卓工程师到KMP跨平台工程师1.1 先搞清楚KMP在跨平台格局里的真实位置很多人容易把KMP和Flutter、React Native放在同一个维度比较实际上它们的定位差别很大。Flutter和RN解决的是UI跨平台问题渲染层也由框架接管KMP则把核心放在业务逻辑共享UI层你可以自由选择。如果你用Compose MultiplatformJetBrains推出的声明式UI框架那KMP也能做到UI跨平台但哪怕你只用KMP共享逻辑、UI保持原生这套方案的收益也依然成立。这个定位决定了KMP不是一个替代安卓开发的方案而是一个升级安卓开发的方案。它没有要求你放弃Android SDK和Jetpack全家桶而是让你在原有的技术积累上多出一个iOS端、甚至桌面端的输出通道。对于安卓工程师来说这非常重要——意味着你不必从零学一门新语言或全新的渲染原理只需要把Kotlin的能力边界往更底层和更平台上延伸。我的判断是KMP适合那些业务逻辑复杂、多端复用价值高、但又不想被单一跨平台UI框架绑死的团队。比如电商App的购物车逻辑、物流查询的州状态机、金融产品的利息计算、内容产品的推荐策略——这些逻辑写成一份Android和iOS各自调用维护成本直接减半。1.2 安卓工程师手里已有的三张牌第一张牌是Kotlin语言本身。KMP使用Kotlin作为共享逻辑的开发语言这意味着你在安卓项目里写的协程、Flow、密封类、扩展函数、数据类这些特性在共享模块里全部可以继续用。不用担心语法切换的问题这和Swift开发者的过渡成本完全不是一个量级。第二张牌是Android Studio的熟悉度。KMP的开发主力IDE就是Android Studio你不需要重新适应Xcode的工程结构才能写逻辑代码。虽然调试iOS端的时候免不了打开Xcode但日常的代码编写、单元测试、依赖管理都可以在Android Studio里完成。第三张牌是对Gradle构建体系的理解。KMP的构建基于Gradle安卓工程师对Gradle脚本、依赖项声明、变体配置、AGP各种版本兼容这些已经是家常便饭而这恰好是很多iOS开发者面对KMP时最头疼的部分。这三张牌意味着安卓工程师进入KMP的门槛天然就低真正要花时间啃的是那些以前不需要了解的部分。1.3 需要补齐的知识短板短板主要集中在这几个方向iOS平台的基础常识。你不需要成为iOS开发专家但至少要知道Xcode工程的基本结构、CocoaPods或Swift Package Manager怎么工作、UIViewController和Activity在生命周期上的差异、iOS沙盒目录结构跟Android的data/data目录有什么不同。expect/actual这套平台的抽象机制。这是KMP的设计核心理解它才能真正写好共享代码。Kotlin/Native的编译和内存模型。KMP共享代码在iOS端是编译成原生二进制的不是跑在虚拟机里所以并发模型、对象生命周期、以及某些API行为会和JVM环境有微妙差异。多平台工程的概念。一个KMP项目里有androidMain、iosMain、commonMain这样的源码集每个target有自己的编译目标Gradle配置比普通安卓项目复杂一个量级。这四块补起来并不难但如果不认真学后面会出现一堆代码写得没问题跑起来就是不对的玄学问题。2. 五个核心能力拆解写共享代码不只是翻译代码2.1 Kotlin语言功底决定共享代码的质量上限KMP共享代码是给多端使用的这意味着它对代码质量的要求比普通安卓业务代码更高。你写的每个函数都可能在iOS、桌面、服务端等不同环境被调用任何隐藏的语言层面的问题都会被放大。在Kotlin语言层面我最想强调三个点第一是协程的正确使用。在commonMain里你可以直接使用kotlinx.coroutines但要注意Dispatchers的差异。Dispatchers.Main在Android上对应主线程在iOS上由Kotlin/Native的调度器实现行为并非完全一致。共享代码里尽量编写挂起函数把线程切换逻辑留给各端自己处理这样既保证逻辑一致性又避免在平台调度上踩坑。第二是泛型和类型安全。共享网络层和数据层必须充分利用Kotlin的泛型机制比如写一个通用的ApiResult 封装所有接口统一返回这个类型两端拿到的是同一个结构不需要各自做转换。第三是DSL和扩展函数的使用。KMP共享代码特别适合把业务规则抽成DSL。比如配置一个支付流程你可以用DSL串起创建订单-请求支付-轮询状态-回调通知iOS端和Android端各自只需要传参不关心内部实现。写共享代码和写安卓业务代码最大的区别是你不再为某一个页面服务而是在定义一套两端共用的业务语言。设计API时要问自己一个问题如果iOS同事拿到这个函数他能不能只看函数签名就明白该怎么调用能说明你的抽象是成功的不能说明你的设计还带着安卓的习惯。2.2 expect/actual机制是KMP的命门expect/actual是KMP最核心、也最容易让人困惑的语法。它做的事其实很直白在commonMain里声明一个接口或函数在androidMain和iosMain里分别给出实现。调用方只依赖commonMain里的声明具体用的是哪份实现由构建系统自动匹配。用一句大白话解释你在一楼写了张需要一个插座的清单然后在二楼放了一个国标插座在三楼放了一个欧标插座任何一层的人想用电都只需要说给我插座不用关心自己到底在几楼。需要留意的是expect/actual不只是函数级还可以用于类、伴生对象、注解、枚举等。最常见的用途是获取平台信息、访问平台API、接入系统能力如获取设备ID、震动、推送令牌等。写expect/actual时我总结了几条实用经验要克制。同一个功能尽最大化放到commonMain里用纯Kotlin实现真正无法统一的那一小部分才用expect/actual。如果一个业务逻辑可以用标准库或Ktor这类跨平台库解决就不要自己写expect/actual。不要过度设计。很多新手一上来就把整个类设计成expect class完全没有必要。优先用接口工厂模式让实际实现类的细节留在各平台源码集。注意iOS端的特殊限制。Kotlin/Native的某些API在不同Apple平台iOS、macOS、watchOS行为不一致声明expect时要考虑你的target范围。2.3 Gradle多平台构建是及格线如果说expect/actual是逻辑层面的核心Gradle多平台配置就是工程层面的核心。一个KMP共享模块的build.gradle.kts大概会长这样plugins { kotlin(multiplatform) id(com.android.library) } kotlin { androidTarget { compilations.all { kotlinOptions { jvmTarget 17 } } } listOf( iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget - iosTarget.binaries.framework { baseName Shared isStatic true } } sourceSets { commonMain.dependencies { implementation(org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1) implementation(io.ktor:ktor-client-core:3.0.3) implementation(org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3) } androidMain.dependencies { implementation(io.ktor:ktor-client-okhttp:3.0.3) } iosMain.dependencies { implementation(io.ktor:ktor-client-darwin:3.0.3) } } }这段配置的要点有这么几个androidTarget必须在plugins里引入com.android.library且AGP版本和Kotlin版本有兼容关系并非越新越好。iOS的三个target分别对应真机arm64、模拟器x64和Apple Silicon模拟器arm64如果你的开发机是老的Intel Mac只要配置前两个就行但最好一次性全部配上方便团队其他人clone后直接跑。framework的baseName建议用产品名后面iOS工程依赖这个framework时用这个名字。isStatic和isStatic设为true可以让最终iOS包体更小但也会带来一些编译链接上的问题具体取舍后面再展开。构建配置没有太多玄学本质上就是告诉Kotlin你在为哪些平台编译、每个平台需要什么依赖。遇到问题先看报错信息里的target名字再根据target去检查对应的source set配置大部分问题都能定位。2.4 iOS基础知识只需要够用让安卓工程师去系统学一遍iOS开发既不现实也没必要但以下几个概念必须懂否则KMP的iOS集成无从谈起Xcode工程的基本文件结构xcodeproj、xcworkspace、info.plist、entitlements这几个概念要知道是干什么的。两种依赖管理方式CocoaPods和Swift Package Manager。KMP官方早年支持CocoaPods集成现在Swift Package Manager也逐步完善但CocoaPods还是最常见的接入方式。Framework和静态库的区别KMP编译出来的产物在iOS端是一个framework毛结构需要把它嵌到Xcode工程里才能被Swift/ObjC代码调用。iOS生命周期AppDelegate、SceneDelegate、UIViewController的基本职责至少能看懂iOS同事在说什么。线程和队列GCD、DispatchQueue这些概念因为Kotlin/Native的并发模型和线程调度在iOS上会和JVM上有差异。不需要学语法细节但认知层面要打通。一个常见场景是你的KMP网络层在后台线程回调结果iOS同事问你要不要在DispatchQueue.main.async里更新UI。这时候你要能跟上他的思路并且在共享代码里设计好回调线程不然就会出现逻辑对但UI不刷新的尴尬。2.5 架构设计共享边界比代码本身更重要KMP最大的设计决策不是用什么网络库、序列化库而是哪些代码放共享、哪些代码留在各端。边界划分错了KMP就成了两个原生项目之间互相传字符串的笨工具根本发挥不出复用的价值。我一般按这个优先级决定是否放共享模块第一优先共享数据模型、网络请求层、数据存储层的逻辑、状态管理、业务规则价格计算、表单校验、排序筛选。第二优先共享日志上报、埋点、A/B测试、配置管理等基础能力。不建议共享UI层除非用Compose Multiplatform且团队完全接受、设备专属API摄像头滤镜、传感器融合、AR等、推送SDK的接入逻辑、支付SDK的交互流程。一个合格的KMP架构应该长这样底层是完全跨平台的Kotlin库中间是业务领域层最上层是Android/iOS各自的UI层。领域层输出状态给UI层消费UI层把用户操作传给领域层。Android端用ViewModelCompose或XML都行iOS端用SwiftUI或UIKit都行两边互不干扰。3. 实操落地从零到一搭建一个KMP项目3.1 环境准备清单动手之前先把环境理清楚这几个版本号都是我在实际项目里验证过的组合macOS 14Apple Silicon最佳Intel机器也能跑但编译会慢JDK 17Android Studio Ladybug或更高版本Xcode 15Kotlin 2.1.xAndroid Gradle Plugin 8.5.xCocoapods用Homebrew安装brew install cocoapodsKotlin和AGP的版本兼容表很关键不匹配直接编译失败。快速查询的办法是打开Kotlin官方文档的「Kotlin Multiplatform compatibility」章节确认Kotlin版本支持的AGP版本范围。3.2 创建共享模块的两种方式第一种是直接用JetBrains提供的Kotlin Multiplatform Wizard网站生成项目模板。选择目标和UI方案下载zip用Android Studio打开即可。这种方式适合快速验证缺点是生成的模板结构固定后面改起来有些麻烦。第二种是手写。在现有Android项目的根目录新增一个共享模块比如叫shared然后在settings.gradle.kts里加上include(:shared)再创建src/commonMain、src/androidMain、src/iosMain等目录结构。这种方式适合把KMP集成进已有项目因为你可以控制模块的依赖关系不让共享模块反向依赖原有代码。我个人更推荐第二种因为真实项目中KMP不可能从一开始就是绿地开发大概率是在现有安卓项目旁边加一个共享模块然后逐步把业务逻辑迁移进去。3.3 用expect/actual落地一个获取平台名称的最小演示我先用一个最简单的例子走通链路理解了这条路后面做网络层和业务层就顺了。在commonMain里声明expect fun platformName(): String在androidMain里实现actual fun platformName(): String Android在iosMain里实现actual fun platformName(): String iOS在commonMain里调用fun greet(): String Hello from ${platformName()}这个Demo虽然简单但包含了expect/actual最核心的流程。当你执行./gradlew :shared:compileKotlinIosArm64时Gradle会选择iosMain里的actual实现去编译。从这个点开始你可以继续扩展获取设备的网络状态、读取App版本号、生成设备唯一标识。3.4 网络层与数据层Ktor kotlinx.serialization组合网络层是共享模块的重点收益项。用Ktor client作为HTTP引擎用kotlinx.serialization做JSON序列化两端只需要配置不同的底层引擎Android用OkHttpiOS用Darwin这是标准做法。共享模块里的网络层代码大致这样class ApiService(private val client: HttpClient) { suspend fun fetchUser(id: String): ApiResultUser { return try { val response client.get(https://api.example.com/user/$id) if (response.status.isSuccess()) { ApiResult.Success(response.bodyUser()) } else { ApiResult.Error(response.status.value) } } catch (e: Exception) { ApiResult.Exception(e) } } }所有序列化模型都放到commonMain里定义Serializable data class User( val id: String, val name: String, val email: String? null )调用方完全不用关心底层是用OkHttp还是Darwin引擎业务代码只需要面对HttpClient和挂起函数。缓存逻辑也可以用Ktor插件实现比如HttpCache插件两端默认行为一致不用单独写iOS版本。3.5 UI层选型Compose Multiplatform还是原生UIKMP项目迟早会遇到UI层选择的问题我的看法是分阶段走。如果团队是安卓工程师主导、iOS端暂时没有深度定制需求可以考虑直接用Compose Multiplatform一套UI两端通用开发效率最高。代价是iOS上的UI质感需要花时间调而且Compose Multiplatform在iOS上的性能虽然已经不错但复杂动画和列表滚动和原生SwiftUI还是有一点差距。如果团队已经有一定iOS开发积累或者App的UI对原生依赖很强比如复杂的跨页转场、系统控件联动那就别碰共享UI共享模块只负责逻辑UI各写各的。这个方案在工程上最稳妥也是KMP生态最早成熟的用法。从我实践的经验看多数团队最终会走向先逻辑共享、后按需共享UI的路线。一开始就上Compose Multiplatform容易把战线拉得太长把业务逻辑先抽出来、让两个端都能跑通同一套代码收益是立竿见影的。4. 常见问题与排查技巧实录4.1 编译速度慢与内存问题Kotlin/Native编译比JVM编译慢一个数量级尤其是iOS的target第一次编译可能要好几分钟。这是正常现象不是配置出错了。优化手段有几个在Gradle配置里开启Kotlin/Native增量编译官方默认部分开启老版本需要自己设置kotlin.native.enableKlibsTreeChaching。减少每次构建的target数量开发期只保留iosSimulatorArm64或iosX64发布前再构建所有target。如果本机内存小于16G编译大项目时建议关掉Chrome等内存大户否则容易OOM。4.2 iOS集成失败的常见原因KMP编译出来的framework集成到Xcode项目最常见的坑有三个framework名称和Xcode里配置的不一致。检查build.gradle.kts里baseName和Xcode target的链接配置名称多一个字符都连不上。静态库导出后的符号冲突。如果共享模块和某个第三方SDK同时包含了同一个Kotlin库的符号链接时会报duplicate symbols。这时候考虑把isStatic改为false或者调整依赖配置用api代替implementation。CocoaPods版本和Kotlin插件版本不一致。Kotlin的CocoaPods插件对CocoaPods版本有要求过老的版本会导致pod install阶段svn失败。4.3 平台API差异导致的行为不一致同一个业务逻辑在两端跑出不同结果的情况不少见。比如字符串格式化Android的String.format和iOS的String(format:)在浮点数位数控制上就有细微差别日期解析java.time和NSDateFormatter的默认时区处理也不一样。解决思路有两种一是写共享的纯Kotlin实现比如用kotlinx-datetime替代java.time和NSDateFormatter二是在数字和日期处理的边界上加一层自定义格式策略明确规定用哪种规则避免各端自由发挥。4.4 调试技巧速查KMP的调试没法像纯安卓项目那样一路断点到底。共享代码在Android端调试和普通安卓项目一样即可在iOS端需要把framework编译进Xcode项目用Xcode的断点调试并且要确认Kotlin/Native的调试信息debug symbols已正确生成。如果断点踩不进去先怀疑是不是编译成了Release模式framework的编译模式是Debug还是Release需要在构建脚本里显式区分。另外日志在两端的行为也有差异Android端logcat和iOS端console看日志格式和过滤方式不同建议在共享模块里封装一个Logger接口统一日志输出格式。4.5 兼容性与版本锁定KMP生态的版本演进很快版本更新往往会带来行为变化。我在项目里要求所有共享库的版本锁在一个常量表里升级前先看官方changelog确认没有破坏性变更再统一升级。Kotlin版本、Coroutines版本、Ktor版本、AGP版本这四个是互相绑定的必须一起评估。这里给出一个我们在用的版本组合作为参考组件版本说明Kotlin2.1.20编译核心Ktor3.0.3网络引擎客户端侧kotlinx-coroutines1.8.1协程核心库kotlinx-serialization1.7.3JSON序列化AGP8.5.2Android构建插件不要盲目追新生产项目稳比新重要。5. 团队协作与工程规范KMP不是一个人的事5.1 代码仓库结构与分支策略KMP项目通常是一个仓库包含多个模块app-android、app-ios、shared。shared是纯KMP模块被两端依赖。我在实际项目里用了一个简单有效的仓库结构repo/ ├── shared/ │ ├── src/commonMain/ │ ├── src/androidMain/ │ ├── src/iosMain/ │ └── build.gradle.kts ├── androidApp/ ├── iosApp/ ├── gradle/ └── build.gradle.kts分支策略沿用Git Flow变体shared模块的变更走feature branchPR合并前要求在两端都跑通构建。如果iOS端暂时没人维护至少要保证CI里执行compileKotlinIosArm64不然合进主干后反而埋雷。5.2 自动化构建与CIKMP项目对CI的要求比普通安卓项目严格因为多了一个iOS端的构建产物。我建议CI分三条流水线第一条Android端构建单测跑Android target的单元测试。第二条iOS端framework构建跑iosArm64和iosSimulatorArm64的编译任务。第三条共享模块的通用检查ktlint、API变更检测。检测API变更可以用binary-compatibility-validator插件它会在共享模块的公共API发生变化时生成报告方便团队评估影响范围。5.3 文档与知识沉淀KMP的共享代码没有局部的UI界面可看新同事上手的难度比普通业务代码高。我的习惯是在共享模块的readme里写明三件事模块的边界定义哪些放共享、哪些不放、依赖库清单及选型理由、每种expect/actual存在的原因。特别强调最后一条。很多expect/actual在设计时是合理的但半年后维护的人可能完全不知道为什么不能在commonMain里直接实现了。把决策理由写在注释里比写一万行概念文档都有用。5.4 影响范围评估团队把安卓代码往KMP迁移时心里要有个清单每次往共享模块写入一个类影响的范围就不再只是安卓端而是所有接入端。比如你在网络层加了一个超时时间的常量Android改了就要同步改iOS否则两端行为不一致用户感知非常明显。建议在共享模块的注释里标注该类的调用方包括androidApp/iosApp当有人改动核心逻辑时可以快速评估影响面。这个要求执行起来不难但对长期维护的价值极大。关于KMP转型的一些心里话有人问我KMP到底值不值得学、值不值得用。我的实际感受是如果你是一个有三年以上经验的安卓工程师并且所在的团队有iOS端点需求KMP很可能是你职业生涯里投入产出比最高的一次技能升级。它没有推翻你已有的知识体系而是在你熟悉的地基上盖一层新的楼。但我也要说句实在话KMP不是银弹。它解决的是业务逻辑复用的问题不解决UI差异、团队沟通、跨端Bug定位这些更靠近人的问题。如果你所在的项目本身逻辑简单、一个端就够那没必要为了跨平台而跨平台。相反如果你的项目逻辑复杂、维护成本高、多端并行迭代吃力那KMP带来的收益值得认真评估。最后分享一个很小的经验刚开始不要追求把整个项目迁到KMP从一个独立的业务模块试起比如用户登录、订单状态查询走通共享模型-共享网络-两端调用的最小链路。跑通那一刻的实际体感比任何资料和评测都更有说服力。技术选型这种事最终还是要信自己踩过的坑、跑通的路。