K2 编译器带来的变化

Kotlin 2.0 起默认启用 K2 编译器:前端完全重写,编译速度显著提升,同时为多平台(KMP)提供了统一的中间表示。对开发者而言最直观的感受是:编译更快、IDE 分析更准、智能补全更可靠。

一、KMP 项目结构

// build.gradle.kts (:shared)
plugins {
    kotlin("multiplatform")
    kotlin("plugin.compose")
    id("org.jetbrains.compose")
}

kotlin {
    androidTarget()
    jvm("desktop")
    iosArm64()
    iosSimulatorArm64()
    js(IR) { browser() }
    wasmJs { browser() }

    sourceSets {
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material3)
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
            implementation("io.ktor:ktor-client-core:3.0.0")
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
        }
        androidMain.dependencies {
            implementation("io.ktor:ktor-client-okhttp:3.0.0")
        }
        iosMain.dependencies {
            implementation("io.ktor:ktor-client-darwin:3.0.0")
        }
    }
}

核心思路:业务逻辑、网络、数据层写在 commonMain,平台差异用 expect/actual 声明。

// commonMain
expect class PlatformInfo {
    val name: String
    val version: String
}

// androidMain
actual class PlatformInfo {
    actual val name = "Android"
    actual val version = android.os.Build.VERSION.RELEASE
}

// iosMain
actual class PlatformInfo {
    actual val name = "iOS"
    actual val version = platform.UIKit.UIDevice.currentDevice.systemVersion
}

二、共享业务层

// 共享的 repository,所有平台复用
class ArticleRepository(private val client: HttpClient) {
    private val cache = mutableMapOf<String, Article>()

    suspend fun load(id: String): Article = coroutineScope {
        cache[id]?.let { return@coroutineScope it }

        val dto: ArticleDto = client.get("https://api.example.com/articles/$id").body()
        val article = dto.toDomain()
        cache[id] = article
        article
    }

    // 并发加载多个
    suspend fun loadAll(ids: List<String>): List<Article> = coroutineScope {
        ids.map { async { load(it) } }.awaitAll()
    }
}

三、Compose Multiplatform UI

// commonMain/ui/ArticleScreen.kt
@Composable
fun ArticleScreen(vm: ArticleViewModel = remember { ArticleViewModel() }) {
    val state by vm.state.collectAsState()

    MaterialTheme {
        Scaffold(topBar = { TopAppBar(title = { Text("文章") }) }) { padding ->
            when (val s = state) {
                is UiState.Loading -> Box(Modifier.fillMaxSize(), Alignment.Center) {
                    CircularProgressIndicator()
                }
                is UiState.Error -> Text("出错了:${s.message}", Modifier.padding(padding))
                is UiState.Data -> {
                    LazyColumn(Modifier.padding(padding)) {
                        items(s.articles, key = { it.id }) { a ->
                            ArticleRow(a, onClick = { vm.open(a) })
                        }
                    }
                }
            }
        }
    }
}

@Composable
private fun ArticleRow(a: Article, onClick: () -> Unit) {
    ListItem(
        headlineContent = { Text(a.title, maxLines = 2) },
        supportingContent = { Text(a.summary, maxLines = 1) },
        modifier = Modifier.clickable(onClick = onClick),
    )
}

同一份 UI 代码可运行在:Android(View 层)、iOS(Skiko 渲染,UIKit 互操作)、桌面(JVM)、Web(Wasm)。

四、Web 端:Kotlin/Wasm 的突破

# 构建 Wasm 产物
./gradlew :composeApp:wasmJsBrowserDistribution

Kotlin/Wasm 编译到 WebAssembly(GC 提案),配合 Compose 可以把整套 UI 跑在浏览器里,产物比 Kotlin/JS 更小、启动更快。

五、工程实践建议

// 用 sealed interface 表达状态,编译器强制穷尽
sealed interface UiState {
    data object Loading : UiState
    data class Error(val message: String) : UiState
    data class Data(val articles: List<Article>) : UiState
}

// 通用 ViewModel 基类,避免在 commonMain 依赖 AndroidX
abstract class BaseViewModel : ViewModel() {
    protected val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

    fun handleError(e: Throwable) {
        // 统一上报
        println("error: ${e.message}")
    }
}

注意事项:

  1. iOS 端的并发:Kotlin 的多线程协程在 Native 上有冻结限制,推荐使用新内存管理器(默认启用)并把状态限制在单一线程。
  2. UI 一致性:Compose 在 iOS 上不是 UIKit 原生组件,需要与设计师确认视觉规范。
  3. 依赖成熟度:数据库推荐 SQLDelight,DI 推荐 Koin 或 kotlin-inject,优先选择明确支持 KMP 的库。

小结

KMP + Compose Multiplatform 让“一套业务逻辑、多端共享”真正落地,尤其适合内部工具、B 端应用与创业团队。K2 编译器和 Kotlin/Wasm 的成熟,是这套方案从尝鲜走向生产的关键拐点。