Compose Multiplatform 如何实现 Android、iOS、Web 的代码复用?
解读
国内面试官问这道题,核心想验证三件事:
- 你是否真的用 Kotlin Multiplatform(KMP)落地过业务,还是只停留在“Hello World”;
- 能否把“复用”拆成 UI、业务逻辑、平台能力三条线,讲清楚各自边界;
- 对国内特殊场景(如微信登录、支付宝支付、国产 ROM 兼容、合规隐私弹窗)有没有踩坑经验。 答得太泛(“一套代码跑三端”)会被认为纸上谈兵;答得太细(“把 Skia 画到 SurfaceFlinger”)又容易超时。最佳策略是“总-分-总”:先给出官方架构图的文字版,再按“common、androidMain、iosMain、jsMain”三段式展开,最后落地到国内工程模板(gradle.kts + expect/actual + CMP 1.6+)。
知识点
- Kotlin Multiplatform 编译链:Kotlin/JVM、Kotlin/Native、Kotlin/JS 三套 IR 后端,共用 common 模块的 Kotlin 源码。
- Compose 运行时拆分:compose-runtime、compose-foundation、compose-material 在 Android 走 AOSP 原生路径;iOS 通过 Kotlin/Native 编译为 Apple Framework,内部用 Skiko(Skia + Metal)渲染;Web 用 Kotlin/JS + WASM 或 JS-IR 目标,最终落到 Canvas 或 WebGL。
- expect/actual 机制:common 里声明 expect 接口,平台模块提供 actual 实现,编译期链接,无反射无运行时开销。
- 平台能力注入:Android 用 Activity 上下文、iOS 用 UIViewController、Web 用 Window,统一封装在 Platform 接口,通过 rememberPlatform() 注入 Compose UI 树。
- 国内合规桥接:Android 侧把微信、支付宝 SDK 做成 AndroidLibrary,暴露 expect 接口;iOS 侧用 CocoaPods 引入同样 SDK;Web 侧走官方 JSBridge,三端 actual 实现各自调用,common 层保持接口一致,方便单元测试。
- 构建产物:Android 输出 .aar,iOS 输出 .framework + .dSYM,Web 输出 .wasm + .js;国内持续集成常用 GitLab CI + 自建 Mac mini 农场 + 阿里 OSS 缓存,Gradle 8+ 配置 org.jetbrains.compose 插件 1.6+ 即可。
答案
Compose Multiplatform 的代码复用是“分层复用”而不是“一刀切”:
- UI 层:Compose 声明式语法在 common 里编写,三端共享;平台差异通过 expect/actual 封装。例如,common 里写 expect fun openSharePanel(text: String),androidMain 里用 Intent.createChooser,iosMain 里用 UIActivityViewController,jsMain 里用 Navigator.share。
- 业务逻辑层:ViewModel、Repository、UseCase 纯 Kotlin 编写,放在 common 模块,依赖 Ktor、SQLDelight、Kotlin Coroutines,编译到三端零成本;国内网络层可插拔,common 里定义 expect fun httpEngine(): HttpClientEngine,androidMain 返回 OkHttp,iosMain 返回 Darwin,jsMain 返回 Js,既满足合规(国密、证书校验)又保持接口一致。
- 平台能力层:摄像头、蓝牙、支付、推送等通过 expect/actual 下沉到各平台,common 仅保留类型安全接口;国内场景下,Android 侧接入微信 OpenSDK、iOS 侧同样接入微信 SDK,Web 侧走 JSSDK,三端 actual 实现统一回调到 common 的 ShareResult sealed class,Compose UI 层直接 collectAsState(),实现“一次编写,三端表现一致”。
- 构建与分发:Android 直接集成 :shared:android 模块,输出 .aab 上架国内各大商店;iOS 通过 Xcode 链接生成的 .framework,使用 CocoaPods 本地路径依赖,满足 App Store 合规签名;Web 部署到阿里云 OSS + CDN,wasm gzip 后体积可压到 1.3 M 以内,首屏 1.5 s 可完成。
一句话总结:Compose Multiplatform 通过“Compose 统一 UI + KMP 分层编译 + expect/actual 隔离平台差异”,让 70% 代码留在 common,20% 做平台适配,10% 写合规桥接,真正落地到国内生产环境。
拓展思考
- 性能边界:iOS 端 Skiko 目前仍多一次 GPU 纹理拷贝,复杂列表滚动 120 Hz 机型实测比原生 SwiftUI 掉帧 3–5%,可用 rememberDisplayMetrics() 手动降级 60 Hz;Web 端 WASM 线程模型下,WebGL 上下文切换成本高于 JS,长列表建议虚拟化 + LazyColumn。
- 国内合规:iOS 隐私清单 PrivacyInfo.xcprivacy 需把 Ktor 网络、SQLDelight 本地存储手动补录;Android targetSdk 34 后台启动限制下,common 层调用 expect fun checkBackgroundStart(),androidMain 侧用 ActivityManager.getRunningAppProcesses() 判断,避免弹窗被系统拦截。
- 混合栈迁移:老项目已有 Flutter 模块,可在 FlutterEngine 里通过 MethodChannel 调用 KMP 生成的 .framework,把 Compose Multiplatform 当作“共享内核”,逐步替换,降低一次性重写风险。
- 未来趋势:Google 在 KMP 路线与 Android Gradle Plugin 深度整合,预计 2025 年 AS Arctic Fox 之后版本将内置 Compose Multiplatform 模板;国内厂商(华米 OV)已开始要求 SDK 提供 KMP 双端库,提前掌握 expect/actual 设计模式,可在技术评审中占据主动权。