# PureFlow 简析：用 Kotlin 跨平台构建 RSS 阅读器

原文：https://blog.grtsinry43.com/posts/kmp-pureflow/

作者：grtsinry43

发布时间：2025-09-22T12:29:52Z

内容更新时间：2025-09-22T12:29:52Z

之前研究那么久 KMP...但是直到现在也没写什么 demo 项目，这次因为身体原因刚好出不去，唉，正好有时间探索这个了。

> [!NOTE]
>
> 目前只是个 demo ，之后我有时间有精力会慢慢添加功能

项目地址：[https://github.com/grtsinry43/PureFlow](https://github.com/grtsinry43/PureFlow)

整个项目其实就写了四天，不过因为几乎 90% 的时间都在写代码，并且现在有 claude code 能干一点脏活累活，所以说自己写好重要部分就行。

![](https://blog.grtsinry43.com/uploads/2025/09/22/image-20250922180629650.png_7e149187-25e4-4656-9f28-4396195307f8.png)


它基于 Kotlin Multiplatform 和 Compose Multiplatform 技术栈，一套代码，同时支持 Android、iOS、macOS、Windows、Linux 甚至 Web 平台。

~~原本我计划用 ovCompose 跨到 HarmonyOS 的，但是没有 UI 库，先这样吧。~~

![](https://blog.grtsinry43.com/uploads/2025/09/22/2025-09-22_20.28.40.jpg_8c46d838-cf62-44d8-b0b5-6e617974228f.jpg)


## 技术栈总览

Kotlin Multiplatform 和 Compose Multiplatform 无疑是项目首选，实现了几乎全栈 Kotlin，从 UI 到业务逻辑，甚至服务端和数据库交互，带来的是开发者开发效率提升和心智统一。

- **核心框架**:
  - `Kotlin Multiplatform`: 跨平台的核心引擎。
  - `Compose Multiplatform`: 声明式 UI 的未来，一套代码搞定多端 UI。
- **数据层**:
  - `SQLDelight`: 类型安全的 SQL 数据库。
  - `Multiplatform Settings`: 跨平台配置存储。
- **网络层**:
  - `Ktor Client`: JetBrains 自家的 HTTP 客户端，跨平台支持很到位。
  - `RSS Parser`: 使用 `com.prof18.rssparser` 开源解析库。
  - `Coil`: 强大的图片加载和缓存库，多平台版本用起来也很香。
- **状态管理与异步**:
  - `StateFlow` & `SharedFlow`: 响应式状态管理的核心，数据流转非常清晰。
  - `ViewModel`: MVVM 架构模式中的核心组件。
  - `Kotlin Coroutines`: 异步编程的利器，让复杂任务变得简单。

这几乎是一个纯正的 Kotlin 全家桶方案，也是我认为其值得一试的原因所在。

## 核心架构设计

PureFlow 设计成一个典型的模块化架构，就像搭积木一样，每块积木都有明确的功能和边界。

```bash
PureFlow/
├── composeApp/          # UI 层 - Compose Multiplatform 应用
├── shared/              # 共享业务逻辑层 (一次编写，逻辑共享)
├── server/              # 服务端 API (Ktor - 目前还未集成)
├── webApp/              # Web 前端 (React/TypeScript - 待完善或探索)
└── iosApp/              # iOS 特异性代码 (主要用于启动和平台特有集成)
```

### 1. `shared` 模块

包含了所有平台共享的业务逻辑、数据模型、数据库接口、网络请求封装和 RSS 解析器。它的 `build.gradle.kts` 配置编译到了各个平台目标：

```kotlin
// shared/build.gradle.kts
kotlin {
    androidTarget {
        compilations.all { kotlinOptions { jvmTarget = "11" } }
    }
    iosArm64() // iOS ARM64 设备
    iosSimulatorArm64() // iOS 模拟器
    jvm() // Desktop (Windows, macOS, Linux)
    js {
        moduleName = "shared"
        browser() // 浏览器环境
        binaries.library() // 打包成 JS 库
        generateTypeScriptDefinitions() // Web 集成，自动生成 TS 定义
        compilerOptions { target = "es2015" } // 兼容性考虑
    }
}
```

通过这个配置，共享代码能编译成 Android 的 `.jar`、iOS 的 `.framework`、桌面端的 `.jar` 和 Web 用的 `.js` 库，真正实现了一套代码，多端运行。其中 `generateTypeScriptDefinitions()` 为 `webApp` 模块的 TypeScript 集成铺平了道路，TypeScript 可以直接使用 Kotlin 共享模块定义的接口和数据结构，减少了重复定义。

### 2. `composeApp` 模块：统一的 UI 界面

这里是 Compose Multiplatform 的战场...终于 iOS stable，可以放心去写。它负责所有平台的 UI 渲染。得益于 `shared` 模块提供的稳定业务逻辑，`composeApp` 可以专注于 UI 交互和视觉呈现。

```kotlin
// composeApp/build.gradle.kts 关键配置
kotlin {
    androidTarget { /* ... */ }
    listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "ComposeApp"
            isStatic = true // 静态库便于集成
        }
    }
    jvm() // Desktop 应用
}
```

`isStatic = true` 让 `ComposeApp` 模块在 iOS 上打包成静态框架，便于在 `iosApp` 中集成。

### 3. `server` 模块：轻量级后端支持（Planned）

虽然 PureFlow 主要是一个客户端应用，但我还是打算用 Ktor 实现一个轻量级后端。它的主要任务是提供 RSS 同步和用户数据管理功能，为将来的云同步、多设备数据一致性打下基础。

## 数据层：SQLDelight

数据存储是任何应用的核心，这里选择 SQLDelight 来处理本地数据库。它提供类型安全的 Kotlin API，避免了运行时错误，开发体验直线提升。

### 数据库设计

项目使用 SQLDelight 配置了数据库：

```kotlin
// shared/build.gradle.kts
sqldelight {
    databases {
        create("PureFlowDatabase") {
            packageName.set("com.grtsinry43.pureflow.database")
        }
    }
}
```

#### 实际的数据库表结构

从项目的 `RssDatabase.sq` 文件可以看到表设计：

1. **RSS 条目表 (`rssItem`)**

   ```sql
   CREATE TABLE rssItem (
       id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
       title TEXT NOT NULL,
       description TEXT,
       link TEXT NOT NULL,
       pubDate TEXT,
       pubDateTimestamp INTEGER, -- 添加时间戳字段用于排序
       guid TEXT UNIQUE, -- 保证 GUID 唯一性
       author TEXT,
       imageUrl TEXT,
       content TEXT,
       sourceUrl TEXT NOT NULL, -- RSS 源 URL
       createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
   );
   ```

2. **订阅源表 (`subscription`)**

   ```sql
   CREATE TABLE subscription (
       id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
       url TEXT NOT NULL UNIQUE, -- RSS 源的唯一 URL
       name TEXT,                -- 用户友好的订阅源名称
       createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
   );
   ```

3. **收藏表 (`favorite`)**

   ```sql
   CREATE TABLE favorite (
       id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
       title TEXT NOT NULL,
       description TEXT,
       link TEXT NOT NULL,
       pubDate TEXT,
       pubDateTimestamp INTEGER,
       guid TEXT UNIQUE, -- 保证同一篇文章只能收藏一次
       author TEXT,
       imageUrl TEXT,
       content TEXT,
       sourceUrl TEXT NOT NULL, -- 原始RSS源
       sourceName TEXT, -- 收藏时记录的源名称，防止源被删除后无法显示
       createdAt INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
   );
   ```

#### 性能优化索引设计：

```sql
-- rssItem 表索引
CREATE INDEX idx_rss_source_url ON rssItem(sourceUrl);
CREATE INDEX idx_rss_created_at ON rssItem(createdAt);

-- subscription 表索引
CREATE INDEX idx_subscription_url ON subscription(url);

-- favorite 表索引
CREATE INDEX idx_favorite_guid ON favorite(guid);
CREATE INDEX idx_favorite_created_at ON favorite(createdAt);
```

这些索引优化了常见查询的性能，比如按订阅源查询文章、按时间排序等操作。

### expect/actual 机制在数据库驱动中的应用

还记得之前说的 `expect/actual` 吗？[（现代 Android 开发探索）](https://blog.grtsinry43.com/posts/modern-android-explore)

它在这里完美解决了不同平台数据库驱动的差异：

```kotlin
// shared 模块中声明期望
expect object DatabaseModule {
    fun provideDatabaseDriverFactory(): DatabaseDriverFactory
}

// 各平台模块中提供实际实现
// Android: AndroidSqliteDriver
// iOS: NativeSqliteDriver  
// Desktop: SqliteDriver
```

这种分离让共享逻辑无需关心底层实现细节。

## UI 实现细节：Compose Multiplatform 的响应式与主题

PureFlow 的 UI 完全由 Compose Multiplatform 构建，目标是实现真正的“一次编写，多端体验优化”。

### 实际的依赖配置

从 `composeApp/build.gradle.kts` 可以看到项目使用的 UI 相关依赖：

```kotlin
commonMain.dependencies {
    implementation(compose.runtime)
    implementation(compose.foundation)
    implementation(compose.material3)
    implementation(compose.ui)
    implementation(compose.components.resources)
    implementation(compose.components.uiToolingPreview)
    implementation(compose.materialIconsExtended)
    implementation(libs.androidx.lifecycle.viewmodelCompose)
    implementation(libs.androidx.lifecycle.runtimeCompose)
    implementation(libs.ksoup)  // HTML解析
    implementation(libs.coil.compose)  // 图片加载
    implementation(libs.coil.network.ktor)
    implementation(projects.shared)
}
```

项目采用了完整的 Material 3 设计系统，使用 Coil 进行图片加载，KSoup 进行 HTML 解析。

### 主题系统

项目包含了完整的主题系统，在 `THEME_GUIDE.md` 中详细说明了如何使用和自定义主题。支持浅色/深色模式切换，以及 Material Design 3 的动态颜色特性。

（这部分是Claude Code写的）

## 网络层核心实现

网络请求选择了 Ktor Client，它的跨平台能力让不同平台复用了 HTTP 客户端的配置。

### 实际的 RSS 解析实现

项目使用了 `com.prof18.rssparser` 库而不是手动解析：

```kotlin
class FeedParser(
    private val httpClient: HttpClient,
    private val rssParser: RssParser
) {
    suspend fun parseFeed(url: String): List<RssItem> {
        return try {
            // 1. 使用 Ktor 发起 GET 请求，获取 RSS 源的 XML 文本内容
            val xmlContent = httpClient.get(url).bodyAsText()

            // 2. 使用 RssParser 解析 XML 文本
            // 这个库会自动识别是 RSS 还是 Atom 格式
            val channel = rssParser.parse(xmlContent)

            // 3. 将解析后的数据模型映射到我们自己的 RssItem
            val rssItems = channel.items.map { feedItem ->
                RssItem(
                    title = feedItem.title,
                    link = feedItem.link,
                    description = feedItem.description,
                    author = feedItem.author,
                    categories = feedItem.categories,
                    guid = feedItem.guid,
                    pubDate = feedItem.pubDate,
                    imageUrl = feedItem.image,
                    content = feedItem.content
                )
            }
            rssItems
        } catch (e: Exception) {
            emptyList()
        }
    }
}
```

这种做法的好处是利用了成熟的开源库，避免了重复造轮子，同时保持了代码的简洁性。

### 平台特异性网络配置

 `shared/build.gradle.kts` 为不同平台选择了合适的 HTTP 引擎：

```kotlin
// 平台特异性实现
androidMain.dependencies {
    implementation(libs.ktor.client.android)
}
iosMain.dependencies {
    implementation(libs.ktor.client.darwin)
}
jvmMain.dependencies {
    implementation(libs.ktor.client.okhttp)
}
```

这个没啥说的：Android 使用 Android 引擎，iOS 使用 Darwin 引擎，Desktop 使用 OkHttp 引擎。

## 数据管理：Repository 模式

项目采用了 Repository 模式来抽象数据源，实际的实现比较直接：

```kotlin
class RssRepository(
    databaseDriverFactory: DatabaseDriverFactory,
    private val feedParser: FeedParser
) {
    private val database = PureFlowDatabase(databaseDriverFactory.createDriver())
    private val queries = database.rssDatabaseQueries

    suspend fun fetchAndCacheRssFeed(url: String): Result<List<RssItem>> {
        return try {
            // 1. 从网络获取RSS数据
            val networkItems = feedParser.parseFeed(url)

            // 2. 缓存到本地数据库（使用事务确保数据完整性）
            database.transaction {
                cacheRssItems(networkItems, url)
            }

            Result.success(networkItems)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    fun getRssItemsFromCache(url: String): Flow<List<RssItem>> {
        return queries.selectBySourceUrl(url)
            .asFlow()
            .mapToList(Dispatchers.IO)
            .map { dbItems ->
                dbItems.map { dbItem ->
                    // 映射数据库对象到业务模型
                    RssItem(/* ... */)
                }
            }
    }
}
```

这种实现直接使用 Kotlin 的 `Result` 类型处理成功/失败状态，并通过 SQLDelight 的 `Flow` 支持实现响应式数据更新。

## 服务层架构

项目实现了一个 `RssServiceManager` 作为核心服务管理器：

```kotlin
class RssServiceManager private constructor(
    private val databaseDriverFactory: DatabaseDriverFactory,
    private val feedParser: FeedParser
) {
    companion object {
        fun getInstance(
            databaseDriverFactory: DatabaseDriverFactory,
            feedParser: FeedParser
        ): RssServiceManager {
            // 单例模式实现
        }
    }

    val repository: RssRepository by lazy {
        RssRepository(databaseDriverFactory, feedParser)
    }

    // 各种业务服务的懒加载实例
    private val subscriptionService: SubscriptionService by lazy { /* ... */ }
    private val incrementalUpdateService: IncrementalUpdateService by lazy { /* ... */ }
    private val scheduledSyncService: ScheduledSyncService by lazy { /* ... */ }
}
```

这个服务管理器整合了所有核心功能，包括订阅管理、增量更新、定时同步等。

## 依赖注入：expect/actual 模式

项目采用了简洁的依赖注入方案，基于 `expect/actual` 模式：

```kotlin
expect object DatabaseModule {
    fun provideDatabaseDriverFactory(): DatabaseDriverFactory
}

expect object NetworkModule {
    fun provideHttpClient(): HttpClient
}

object ServiceModule {
    private var serviceManager: RssServiceManager? = null

    fun provideRssServiceManager(): RssServiceManager {
        return serviceManager ?: run {
            val databaseDriverFactory = DatabaseModule.provideDatabaseDriverFactory()
            val httpClient = NetworkModule.provideHttpClient()
            val rssParser = RssParser()
            val feedParser = FeedParser(httpClient, rssParser)

            val newServiceManager = RssServiceManager.getInstance(
                databaseDriverFactory = databaseDriverFactory,
                feedParser = feedParser
            )
            serviceManager = newServiceManager
            newServiceManager
        }
    }
}
```

这种方式避免了重型 DI 框架的复杂性，同时保持了清晰的依赖关系。

## 构建和部署细节

项目实现了多平台的自动化构建和发布。

### 实际的打包脚本 (`package.sh`)

项目包含了一个真实的多平台打包脚本，支持：

```bash
if [[ "$OSTYPE" == "darwin"* ]]; then
    AVAILABLE_FORMATS="dmg"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
    AVAILABLE_FORMATS="deb rpm appimage"
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]]; then
    AVAILABLE_FORMATS="msi exe"
fi

case $FORMAT in
    "dmg")
        ./gradlew packageDmg
        ;;
    "linux")
        ./gradlew packageDeb packageRpm packageAppImage
        ;;
    "windows")
        ./gradlew packageMsi packageExe
        ;;
    "all")
        ./gradlew packageDistributionForCurrentOS
        ;;
esac
```

### 桌面应用打包配置

从 `composeApp/build.gradle.kts` 可以看到详细的桌面应用打包配置，包括：

- 根据操作系统自动选择打包格式
- 平台特有的配置（如 macOS 的 Bundle ID，Windows 的升级 UUID）
- 应用程序的元数据和图标配置

### CI/CD 流水线

GitHub Actions 配置实现了：

- **多平台构建矩阵**: 在不同环境中分别构建各平台的安装包
- **标签驱动的自动发布**: 基于 Git 标签自动触发发布流程
- **构建缓存优化**: 使用 Gradle 缓存提升构建速度
- **详细的构建报告**: 自动生成构建状态总结

## 总结与思考

这个项目算是一个活生生的案例，证明了在保证代码质量的同时，完全可以实现真正的一次编写、多端运行。

因为时间问题，这里选择了务实写法，使用成熟的第三方库而不是重复造轮子，采用简洁的依赖注入方案而不是重型框架

如果你也对跨平台开发感兴趣，或者正纠结于技术选型，希望这篇文章能为你带来一些启发和帮助。项目展现了现代 Kotlin 生态的强大能力，也让我对跨平台开发的未来充满信心。
