项目概述

数据同步与离线支持是现代应用的关键功能。在 KMP 框架下,我们需要实现跨平台的数据同步机制、离线缓存和冲突解决。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的数据同步系统,包括增量同步、冲突检测和鸿蒙系统特定的数据管理方式。

与“只在线应用”相比,具备同步与离线能力的应用可以在以下场景显著提升体验:

  • 弱网/无网环境:如地铁、隧道、机舱内仍可正常浏览和操作,待网络恢复后自动同步;
  • 多端使用:手机、平板、PC 等多终端之间保持数据一致;
  • 高可靠业务:例如表单、工单、检查记录等,必须保证数据不丢失且最终一致。

本篇目标是:

  • 抽象统一的 SyncManagerOfflineManager 接口,用于发起同步、管理离线数据;
  • 建模同步结果、同步状态、冲突信息与解决策略;
  • 在 Android、iOS、鸿蒙和 Web 等平台上,用各自的数据能力实现同一套业务语义。

第一部分:数据同步核心概念

数据同步架构

// commonMain/kotlin/com/example/sync/SyncManager.kt
expect class SyncManager {
    suspend fun syncData(dataType: String): SyncResult
    suspend fun startAutoSync(interval: Long): Boolean
    suspend fun stopAutoSync(): Boolean
    suspend fun getLastSyncTime(dataType: String): Long
    suspend fun getSyncStatus(): SyncStatus
    fun addListener(listener: SyncListener)
}

data class SyncResult(
    val success: Boolean,
    val dataType: String,
    val itemsSynced: Int,
    val itemsFailed: Int,
    val timestamp: Long,
    val error: String? = null
)

enum class SyncStatus {
    IDLE, SYNCING, PAUSED, ERROR
}

interface SyncListener {
    fun onSyncStarted(dataType: String)
    fun onSyncProgress(dataType: String, progress: Int, total: Int)
    fun onSyncCompleted(result: SyncResult)
    fun onSyncFailed(dataType: String, error: String)
}

// 离线支持
expect class OfflineManager {
    suspend fun saveOfflineData(data: OfflineData): Boolean
    suspend fun getOfflineData(key: String): OfflineData?
    suspend fun getAllOfflineData(): List<OfflineData>
    suspend fun clearOfflineData(): Boolean
    suspend fun getOfflineDataSize(): Long
}

data class OfflineData(
    val key: String,
    val data: ByteArray,
    val timestamp: Long,
    val priority: Int = 0,
    val retryCount: Int = 0
)

// 冲突解决
sealed class ConflictResolution {
    object Local : ConflictResolution()
    object Remote : ConflictResolution()
    data class Merge(val mergeStrategy: (local: Any, remote: Any) -> Any) : ConflictResolution()
    object Manual : ConflictResolution()
}

data class SyncConflict(
    val key: String,
    val localVersion: Any,
    val remoteVersion: Any,
    val timestamp: Long
)

这一部分定义了整个同步与离线系统的核心抽象:

  • SyncManager 专注于“对某一数据类型进行同步”的流程,支持手动同步、自动定时同步、查询最后同步时间和当前状态等;
  • SyncResultSyncStatus 用来描述同步结果与全局状态,便于在 UI 中进行展示和监控;
  • SyncListener 提供细粒度事件:开始、进度、完成、失败,方便做进度条、提示和日志;
  • OfflineManagerOfflineData 则负责离线数据的存储、查询与容量管理,是“断网期间先落地、后补偿”的基础;
  • SyncConflictConflictResolution 预留了对冲突检测和解决策略的扩展点。

在 KMP 结构中,可以在 common 层实现“业务级同步流程”(例如订单同步、配置同步),而在平台特定层完成具体的网络、存储、数据库调用。

第二部分:平台特定实现

Android 数据同步

// androidMain/kotlin/com/example/sync/SyncManager.kt
import android.content.Context
import android.content.SyncRequest
import android.accounts.Account
import android.content.ContentResolver

actual class SyncManager(private val context: Context) {
    private val listeners = mutableListOf<SyncListener>()
    private var autoSyncJob: Job? = null
    
    actual suspend fun syncData(dataType: String): SyncResult {
        return try {
            listeners.forEach { it.onSyncStarted(dataType) }
            
            val account = Account("default", "com.example.account")
            val bundle = android.os.Bundle().apply {
                putString("dataType", dataType)
            }
            
            ContentResolver.requestSync(account, "com.example.provider", bundle)
            
            SyncResult(
                success = true,
                dataType = dataType,
                itemsSynced = 0,
                itemsFailed = 0,
                timestamp = System.currentTimeMillis()
            )
        } catch (e: Exception) {
            SyncResult(
                success = false,
                dataType = dataType,
                itemsSynced = 0,
                itemsFailed = 0,
                timestamp = System.currentTimeMillis(),
                error = e.message
            )
        }
    }
    
    actual suspend fun startAutoSync(interval: Long): Boolean {
        return try {
            autoSyncJob = GlobalScope.launch {
                while (isActive) {
                    delay(interval)
                    syncData("all")
                }
            }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun stopAutoSync(): Boolean {
        return try {
            autoSyncJob?.cancel()
            true
        } catch (e: Exception) {
            false
        }
    }
}

// Android 离线支持
actual class OfflineManager(private val context: Context) {
    private val database = OfflineDatabase.getInstance(context)
    
    actual suspend fun saveOfflineData(data: OfflineData): Boolean {
        return try {
            database.offlineDataDao().insert(data.toEntity())
            true
        } catch (e: Exception) {
            false
        }
    }
    
    actual suspend fun getOfflineData(key: String): OfflineData? {
        return try {
            database.offlineDataDao().getByKey(key)?.toOfflineData()
        } catch (e: Exception) {
            null
        }
    }
    
    actual suspend fun getAllOfflineData(): List<OfflineData> {
        return try {
            database.offlineDataDao().getAll().map { it.toOfflineData() }
        } catch (e: Exception) {
            emptyList()
        }
    }
}

Android 端示例中:

  • SyncManager 使用 ContentResolver.requestSync 与系统同步框架集成,适合需要与系统账号/Provider 深度集成的场景;
  • startAutoSync 通过协程定时触发 syncData,演示了简单的自动同步机制;
  • OfflineManager 通过本地数据库(如 Room)持久化离线数据,并提供按 key/全部查询等接口。

实际工程中,常见做法是:

  • 在应用层使用自建的同步 API + 本地数据库,而非强绑定系统的 SyncAdapter;
  • 为每种数据类型设计好“变更记录表”,支持增量同步;
  • 对离线队列进行容量控制和重试策略设计。

iOS 数据同步

// iosMain/kotlin/com/example/sync/SyncManager.kt
import platform.CloudKit.*
import platform.Foundation.*

actual class SyncManager {
    private val listeners = mutableListOf<SyncListener>()
    private val container = CKContainer.defaultContainer()
    
    actual suspend fun syncData(dataType: String): SyncResult {
        return try {
            listeners.forEach { it.onSyncStarted(dataType) }
            
            val database = container.privateCloudDatabase
            val query = CKQuery(recordType = dataType, predicate = NSPredicate(value = true))
            
            var itemsSynced = 0
            var itemsFailed = 0
            
            database.performQuery(query, inZoneWithID = null) { records, error ->
                if (error == null) {
                    itemsSynced = records?.size ?: 0
                } else {
                    itemsFailed = 1
                }
            }
            
            SyncResult(
                success = itemsFailed == 0,
                dataType = dataType,
                itemsSynced = itemsSynced,
                itemsFailed = itemsFailed,
                timestamp = System.currentTimeMillis()
            )
        } catch (e: Exception) {
            SyncResult(
                success = false,
                dataType = dataType,
                itemsSynced = 0,
                itemsFailed = 0,
                timestamp = System.currentTimeMillis(),
                error = e.message
            )
        }
    }
}

// iOS 离线支持
actual class OfflineManager {
    private val fileManager = NSFileManager.defaultManager
    
    actual suspend fun saveOfflineData(data: OfflineData): Boolean {
        return try {
            val documentsPath = NSSearchPathForDirectoriesInDomains(
                NSDocumentDirectory,
                NSUserDomainMask,
                true
            ).firstOrNull() as? String ?: return false
            
            val filePath = "$documentsPath/${data.key}"
            fileManager.createFileAtPath(filePath, contents = NSData(data.data), attributes = null)
            true
        } catch (e: Exception) {
            false
        }
    }
}

iOS 端示例基于 CloudKit 和文件系统:

  • 通过 CKContainer 与 iCloud 数据库进行数据查询/同步,适合需要与苹果生态深度整合的应用;
  • 使用 NSFileManager 将离线数据以文件形式存储在 Documents 目录中,结构简单、易于调试。

在实际项目中,也可以选择:

  • 使用自建后端 + URLSession/数据库,替代 CloudKit;
  • 将离线数据持久化到 SQLite/CoreData,并在 KMP 侧只暴露抽象接口。

第三部分:编译后的代码形式

JavaScript 编译版本

// 数据同步管理器 (JavaScript/Web)
class SyncManager {
    constructor() {
        this.listeners = [];
        this.lastSyncTime = {};
    }
    
    async syncData(dataType) {
        try {
            this.listeners.forEach(l => l.onSyncStarted(dataType));
            
            const response = await fetch(`/api/sync/${dataType}`);
            const data = await response.json();
            
            const result = {
                success: response.ok,
                dataType: dataType,
                itemsSynced: data.items?.length || 0,
                itemsFailed: 0,
                timestamp: Date.now()
            };
            
            this.lastSyncTime[dataType] = Date.now();
            this.listeners.forEach(l => l.onSyncCompleted(result));
            
            return result;
        } catch (error) {
            this.listeners.forEach(l => l.onSyncFailed(dataType, error.message));
            return {
                success: false,
                dataType: dataType,
                itemsSynced: 0,
                itemsFailed: 0,
                timestamp: Date.now(),
                error: error.message
            };
        }
    }
    
    async startAutoSync(interval) {
        setInterval(() => {
            this.syncData('all');
        }, interval);
        return true;
    }
}

// 离线管理器
class OfflineManager {
    constructor() {
        this.db = null;
    }
    
    async saveOfflineData(data) {
        try {
            if ('indexedDB' in window) {
                const request = indexedDB.open('OfflineDB', 1);
                request.onsuccess = (event) => {
                    const db = event.target.result;
                    const transaction = db.transaction(['offlineData'], 'readwrite');
                    const store = transaction.objectStore('offlineData');
                    store.put(data);
                };
            }
            return true;
        } catch (error) {
            return false;
        }
    }
}

第四部分:鸿蒙系统实际应用

鸿蒙数据同步

// ohos/kotlin/com/example/sync/SyncManager.kt
import ohos.data.dataability.DataAbilityUtils
import ohos.app.Context

actual class SyncManager(private val context: Context) {
    private val listeners = mutableListOf<SyncListener>()
    
    actual suspend fun syncData(dataType: String): SyncResult {
        return try {
            listeners.forEach { it.onSyncStarted(dataType) }
            
            // Use HarmonyOS DataAbility for sync
            val uri = ohos.utils.net.Uri.parse("dataability://com.example.provider/$dataType")
            val result = context.getContentResolver().query(uri, null, null, null, null)
            
            val itemsSynced = result?.rowCount ?: 0
            
            SyncResult(
                success = true,
                dataType = dataType,
                itemsSynced = itemsSynced,
                itemsFailed = 0,
                timestamp = System.currentTimeMillis()
            )
        } catch (e: Exception) {
            SyncResult(
                success = false,
                dataType = dataType,
                itemsSynced = 0,
                itemsFailed = 0,
                timestamp = System.currentTimeMillis(),
                error = e.message
            )
        }
    }
}

// 鸿蒙离线支持
actual class OfflineManager(private val context: Context) {
    private val database = OfflineDatabase.getInstance(context)
    
    actual suspend fun saveOfflineData(data: OfflineData): Boolean {
        return try {
            database.offlineDataDao().insert(data)
            true
        } catch (e: Exception) {
            false
        }
    }
}

第五部分:高级功能

冲突检测与解决

// commonMain/kotlin/com/example/sync/ConflictResolver.kt
class ConflictResolver {
    suspend fun resolveConflict(
        conflict: SyncConflict,
        resolution: ConflictResolution
    ): Any {
        return when (resolution) {
            ConflictResolution.Local -> conflict.localVersion
            ConflictResolution.Remote -> conflict.remoteVersion
            is ConflictResolution.Merge -> {
                resolution.mergeStrategy(conflict.localVersion, conflict.remoteVersion)
            }
            ConflictResolution.Manual -> conflict.localVersion // Default to local
        }
    }
}

// 增量同步
class IncrementalSync(private val syncManager: SyncManager) {
    suspend fun syncIncremental(dataType: String, lastSyncTime: Long): SyncResult {
        // Only sync data modified after lastSyncTime
        return syncManager.syncData(dataType)
    }
}

在这里插入图片描述

总结

数据同步与离线支持是现代应用的关键功能。通过 KMP 框架,我们可以在共享代码中定义统一的同步接口,同时在各平台上利用原生 API 实现高效的数据管理。鸿蒙系统提供了完整的数据管理 API,使得跨平台实现相对平顺。关键是要合理处理冲突、增量同步和离线缓存。

在工程实践中,可以重点思考:

  • 你的业务到底需要“实时强一致”,还是“最终一致 + 可见提示”?
  • 当前的同步流程是否支持增量,而不是每次全量覆盖?
  • 离线数据的容量、过期策略和安全性是否有清晰的设计?

建议的实战路线:

  • 先为一个具体数据域(如“用户草稿”“待上传记录”)实现完整的离线 + 同步流程;
  • 引入冲突检测与简单的解决策略(如以时间戳/版本号为准);
  • 逐步推广到更多数据域,并通过监控和日志不断优化同步策略与用户体验。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐