用 CMP 构建跨平台博客应用:一次 Kotlin 的全栈实践 在追求高效开发的时代,跨平台技术已成为移动应用开发的主流选择,此前基于鸿蒙的开发平台开发 blog_harmony ,将自己博客文章进行展示。本文将介绍基于 CMP(Compose Multiplatform) 构建的开源博客应用 blog_kmp ,展示如何用 Kotlin 实现跨平台的应用开发。
Compose Multiplatform 是 JetBrains 推出的声明式 UI 框架,基于 Jetpack Compose 扩展而来:
核心优势 :用同一套 Kotlin 代码构建 Android、iOS、Desktop 和 Web 应用
开发效率 :实时预览、热重载加速开发迭代
原生性能 :通过 Skia 渲染引擎实现接近原生体验
共享逻辑 :业务逻辑、网络请求、状态管理可 100% 复用
项目架构与技术栈 blog_kmp 采用分层架构设计,核心模块包括:
shared/ ├── src/commonMain/kotlin/ # 共享业务逻辑 │ ├── data / # 数据层 │ ├── domain/ # 领域模型 │ └── presentation/ # UI状态管理 ├── src/androidMain/ # Android 平台代码 └── src/iosMain/ # iOS 平台适配 ├── composeApp │ ├── build.gradle.kts │ └── src │ ├── androidMain # Android 平台代码 │ ├── commonMain # 共享业务逻辑 │ ├── App.kt # 界面展示入口 │ ├── data # 数据层 │ │ ├── api # 网络请求 │ │ ├── di # koin 依赖注入 │ │ ├── model # model 数据 │ │ └── repository # 数据缓存管理 │ │ │ ├── navigation # 页面间导航管理 │ ├── platform # 通过对各个平台抽象的接口 │ └── ui # 通用 UI 逻辑 │ │ ├── desktopMain # Desktop 平台适配 │ └── iosMain # iOS 平台适配
功能预览 Android
深色模式
iOS
Desktop
主要技术栈
Ktor 客户端 - 网络请求
val httpClient = HttpClient { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) } } suspend fun loadPosts () : List<Post> = httpClient.get ("https://cdn.julis/api/posts" ).body()
DataStore - 跨平台数据库
val dataKey = stringPreferencesKey(key)val result = dataStore.data .catch { exception -> if (exception is IOException) { emit(emptyPreferences()) } else { throw exception } } .map { preferences -> val data : String? = preferences[dataKey] if (data == null ) { null } else { if (isJson) Json.decodeFromString<T>(data ) else (data as T) } }
Koin - 依赖注入
val sharedModule = module { single<PostRepository> { PostRepositoryImpl(get ()) } viewModel { PostViewModel(get ()) } }
Kotlinx.Serialization - JSON 解析
@Serializable data class Post ( val id: String, val title: String, val content: String )
compose-webview-multiplatform - WebView 浏览器 使用的第三方开发compose-webview-multiplatform 基于 java-cef 开发,不过这个library 在 desktop 平台表现不是太好,待完善。
val state = rememberWebViewState(postUrl) WebView(state = state,modifier = Modifier.fillMaxSize())
平台特定实现 UI 层面三端能够使用同一份代码,但为了体验,可能需要针对不同的设计,在桌面端可以设计更好地体验UI。这里避免不了 if-else 的UI逻辑,以及一些依赖各种系统的 api 需要单独实现,比如:深色模式监听、资源存储路径、系统信息、状态栏颜色等。
Android 端 Android 特定的功能结合使用起来非常的简单,毕竟都是有血缘关系的。可以使用 AndroidView 直接渲染原生的 UI 页面。
AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> MyView(context) } }, update = { view ->} )
iOS 端 iOS端主要需要 XCode 进行配合,还需要关注开发者账号相关的信息等,其他与 Android 端实现没有太大的差异。
桌面端 利用 Compose Desktop 的窗口管理,可以实现窗口多开。
fun main () = application { Window(onCloseRequest = ::exitApplication) { DesktopAppTheme { AppContent() } } }
🚀 性能优化实践
分页加载 :实现懒加载防止长列表卡顿
LazyColumn { itemsIndexed(posts) { _, post -> PostItem(post) } item { if (loading) LoadingIndicator() } }
本地缓存 :DataStore 离线存储 + Ktor 缓存策略
HttpClient { install(HttpCache) }
图像处理 :搭配 Coil 实现高效图片加载
AsyncImage( modifier = Modifier.size(80. dp) .shadow( elevation = 5. dp, shape = CircleShape, spotColor = Color.Black ) .clip(CircleShape) .clickable { }, model = AppConfig.AVATAR, contentDescription = AppConfig.AVATAR, )
开发经验总结
UI界面 使用 Compose 进行界面布局开发,声明性编程范式相比于传统的 xml 布局开发,高效很多,使用也很方便。使用了这种方式,传统的 UI 开发方式再也回不去了。
状态管理 使用 mutableStateOf
实现响应式更新,或者使用 derivedStateOf
实现派生状态的处理。
var pagIndex by remember { mutableStateOf(0 ) }var errorState by remember { mutableStateOf<String?>(null ) } val themeState by mineViewModel.appTheme.collectAsState()val uiChecked by remember(themeState) { derivedStateOf { themeState == ThemeConstants.DARK } }
导航
实现 Compose Navigator
统一路由管理
val gotoDebug: () -> Unit = { navController.navigate(Routes.Debug()) } val goToPostDetail: (Post) -> Unit = { it -> navController.navigate(Routes.PostDetail(title = it.title, it.url)) }
Kotlin Flow 简化异步编程,让网络请求的代码看起来更直观
fun loadAllPost () : Flow<List<PostV2>> = load("allPosts" ) { postApi.getAllPost()?.data ?: emptyList() } suspend fun getAllPost () : SearchResponse? = request<SearchResponse>(getUrl("api/search.json" ))private suspend inline fun <reified T> request (url: String ) : T? { return try { client.get (url).body() } catch (e: Exception) { if (e is CancellationException) throw e e.printStackTrace() null } }
总结 经过一番各种折腾,将很多在工作上无法使用的能力(Koin、Flow、DataStore……)都体验使用了一下,在业余的时间完成了基于博客文章构建的 App 在三个平台上的开发,实际上最初我也想搭建 WebJs 的平台的,后面删除掉了,因为涉及到 web 平台开发的各种库相比客户端少很多,兼容起来也比较费劲。KMP/CMP 这块技术确实是能很大地节省开发人力,多端使用同一份UI逻辑代码,部分逻辑也可以用 kotlin 统一进行封装,后续维护也会方便很多。但这里有个缺点就是涉及到的库所需要的 kotlin/Java 版本要求比较高,除非开发一些独立的 App,否则公司里的项目想基于这些技术去实现不太大可能。以及如果所需要的能力比较依赖与原生,比如音视频领域就有一定的局限性,总体来讲更适合偏交互业务的开发。
项目源码 : https://github.com/VomPom/blog_kmp