上一篇聊了逆向工程,这篇回到跨平台方向。之前第 30 篇聊过 KMM(Kotlin Multiplatform Mobile),那是共享业务逻辑。Compose Multiplatform 更进一步——共享 UI 代码。一套 Composable 同时跑在 Android、iOS、桌面、Web 上,这事靠谱吗?
Compose Multiplatform vs KMM
KMM 是"共享逻辑,各端写 UI"。Compose Multiplatform 是"逻辑和 UI 都共享"。后者野心更大,但 iOS 上的成熟度还在追赶。
支持的平台
Android:原生支持(Jetpack Compose 本身就是 Android 的)
iOS:通过 Kotlin/Native 编译,使用 Skiko(Skia for Kotlin)渲染
桌面(JVM):Windows/macOS/Linux,用 JVM 运行
Web:通过 Kotlin/Wasm 编译为 WebAssembly
项目结构
compose-multiplatform-project/
├── build.gradle.kts
├── shared/ # 共享模块
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/ # 跨平台代码
│ ├── androidMain/ # Android 特定
│ └── iosMain/ # iOS 特定
├── androidApp/ # Android 入口
├── iosApp/ # iOS 入口(Xcode 工程)
├── desktopApp/ # 桌面入口
基础用法
共享 UI 代码:
// shared/src/commonMain/kotlin/App.kt
@Composable
fun App() {
var count by remember { mutableStateOf(0) }
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Click me")
}
}
}
Android 入口:
// androidApp/src/main/java/MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}
iOS 入口:
// shared/src/iosMain/kotlin/MainViewController.kt
fun MainViewController() = ComposeUIViewController {
App()
}
// iosApp/ContentView.swift
struct ContentView: View {
let mainViewController = MainViewControllerKt.MainViewController()
var body: some View {
mainViewController
.ignoresSafeArea()
}
}
平台差异处理
UI 大部分可以共享,但有些地方必须区分平台。用 expect/actual 机制:
// commonMain
expect fun getPlatformName(): String
// androidMain
actual fun getPlatformName() = "Android"
// iosMain
actual fun getPlatformName() = "iOS"
@Composable
fun App() {
Text("Running on ${getPlatformName()}")
}
更复杂的场景用 expect composable:
// commonMain
@Composable
expect fun PlatformCameraView(modifier: Modifier)
// androidMain
@Composable
actual fun PlatformCameraView(modifier: Modifier) {
// Android CameraX 实现
}
// iosMain
@Composable
actual fun PlatformCameraView(modifier: Modifier) {
// AVFoundation 实现
}
主题和资源
跨平台资源管理用版本目录或 moko-resources 库:
// 共享字符串
strings.app_name // "My App"
// 多语言
strings.hello.desc() // 根据 locale 返回
主题用 Compose 的 MaterialTheme,跨平台一致:
@Composable
fun AppTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = if (isSystemInDarkTheme()) darkColorScheme() else lightColorScheme(),
content = content
)
}
iOS 上的限制和注意事项
渲染方式:iOS 上用 Skiko 渲染,不走 UIKit。这意味着 Compose 的 View 不是真正的 UIView,与原生 iOS 组件互操作有障碍。
动画性能:Skiko 渲染的性能在 iOS 上不如原生 Core Animation,复杂动画可能掉帧。
文字输入:IME 和文本选择的体验不如原生。
无障碍:iOS 的 VoiceOver 对 Compose 的支持不完善。
App 体积:Skiko 运行时增加约 10-15MB。
热重载:iOS 上没有热重载,改代码要重新编译。
桌面应用
桌面端相对成熟:
// desktopApp
fun main() = application {
Window(
title = "My App",
state = rememberWindowState(width = 800.dp, height = 600.dp)
) {
App()
}
}
打包用 composeDesktop.application:
// build.gradle.kts
compose.desktop.application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "MyApp"
packageVersion = "1.0.0"
}
}
适用场景评估
适合用 Compose Multiplatform 的场景:
小团队 + 多端需求:一个人/小团队同时做 Android+iOS,共享 UI 效率高
内部工具 / B 端应用:体验要求不极端,快速出活优先
已有 KMM 项目:逻辑已共享,再加一层 UI 共享成本低
桌面 + 移动端:桌面端成熟度高,值得用
不太适合的场景:
C 端高体验要求:iOS 上渲染和交互体验还达不到原生
重度依赖原生组件:地图、相机、AR 等原生组件多的话,互操作代价大
iOS 团队已有 SwiftUI 代码库:迁移不划算
小结
Compose Multiplatform 是跨平台 UI 的一个有力选项,桌面端已经可以生产使用,iOS 端适合对体验要求不极致的场景。如果你已经在用 KMM 共享逻辑,加一层 Compose UI 共享是自然延伸。但如果 iOS 体验是硬指标,目前 SwiftUI 还是更好的选择。
下一篇聊聊 Lifecycle 源码:Lifecycle、LiveData 与 ViewModel 底层原理。