用CMP构建跨平台博客应用:Kotlin全栈实践

Viewed 0

用 CMP 构建跨平台博客应用:一次 Kotlin 的全栈实践

在追求高效开发的时代,跨平台技术已成为移动应用开发的主流选择。本文将介绍基于 CMP(Compose Multiplatform) 构建的开源博客应用 blog_kmp,展示如何用 Kotlin 实现跨平台的应用开发,共享代码并覆盖 Android、iOS 和 Desktop 平台。

Compose Multiplatform

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 平台上提供一致的界面体验,支持深色模式等特性。各平台界面经过优化,确保原生般的交互和视觉效果。

主要技术栈

  1. Ktor 客户端 - 用于网络请求,配置如下:
val httpClient = HttpClient {
       install(ContentNegotiation) {
           json(Json { ignoreUnknownKeys = true })
       }
}
suspend fun loadPosts(): List<Post> =
       httpClient.get("https://cdn.julis/api/posts").body()
  1. 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)
           }
       }
  1. Koin - 用于依赖注入,简化模块管理:
val sharedModule = module {
       single<PostRepository> { PostRepositoryImpl(get()) }
       viewModel { PostViewModel(get()) }
}
  1. Kotlinx.Serialization - 处理 JSON 解析:
@Serializable
data class Post(
       val id: String,
       val title: String,
       val content: String
)
  1. compose-webview-multiplatform - 集成 WebView 浏览器,支持跨平台内容展示:
val state = rememberWebViewState(postUrl)
    WebView(state = state,modifier = Modifier.fillMaxSize())

平台特定实现

UI 层面三端使用同一份代码,但针对不同平台可能需要调整以实现最佳体验。例如,Android 端可使用 AndroidView 直接渲染原生 UI;iOS 端需要 XCode 配合处理开发者账号等配置;桌面端则利用 Compose Desktop 的窗口管理功能实现多开窗口。

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        DesktopAppTheme { AppContent() }
    }
}

性能优化实践

为提高应用性能,实施了以下策略:分页加载通过 LazyColumn 实现懒加载,防止长列表卡顿;本地缓存结合 DataStore 离线存储和 Ktor 缓存策略,提升数据访问效率;图像处理使用 Coil 实现高效图片加载,优化内存和渲染性能。

LazyColumn {
       itemsIndexed(posts) { _, post ->
           PostItem(post)
       }
       item { if (loading) LoadingIndicator() }
}
HttpClient {
       install(HttpCache)
}
AsyncImage(
       modifier = Modifier.size(80.dp)
           .shadow(
               elevation = 5.dp,
               shape = CircleShape,
               spotColor = Color.Black
           )
           .clip(CircleShape)
           .clickable { },
       model = AppConfig.AVATAR,
       contentDescription = AppConfig.AVATAR,
)

开发经验总结

使用 Compose 进行声明式 UI 开发,显著提高了布局效率。状态管理通过 mutableStateOf 和 derivedStateOf 实现响应式更新,导航通过 Compose Navigator 统一路由管理。异步编程使用 Kotlin Flow 简化网络请求,使代码更直观易维护。

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 } }
val gotoDebug: () -> Unit = {
    navController.navigate(Routes.Debug())
}
val goToPostDetail: (Post) -> Unit = { it ->
    navController.navigate(Routes.PostDetail(title = it.title, it.url))
}
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 等 Kotlin 技术在跨平台开发中的应用。KMP/CMP 技术能有效节省开发人力,实现多端代码共享,便于后续维护。但需要注意,相关库对 Kotlin/Java 版本要求较高,且对于原生依赖强的领域(如音视频)可能存在局限性,更适合偏交互业务的开发。项目源码已开源,可供参考。

0 Answers