作者简介
禹昂,携程移动端资深工程师,专注于 Kotlin 移动端跨平台领域,Kotlin 中文社区核心成员,图书《Kotlin 编程实践》译者。
一、背景
携程机票移动端研发团队自 2021 年始就一直在移动端实践 Kotlin Multiplatform 技术(请见参考链接 1)。由于目前 Kotlin Multiplatform 生态尚处于起步阶段,大部分 Kotlin 开源库都是 JVM only 的,因此在我们团队的日常开发过程中迫切需要一些能够支持 KMM(Kotlin Multiplatform Mobile)的基础库或框架。
在原生移动端开发中,Android SDK 提供了 SharedPreferences,iOS 提供了 NSUserDefaults 用于 KV 存储功能,但这二者在性能要求较高的情况下不能满足需求。后来虽然 Google 推出了 Jetpack Datastore 用于替换 SharedPreferences,但它仅仅支持 Android 平台。
携程的基础框架团队经过一系列评估后决定使用腾讯的开源库 MMKV (参考链接 2)用于满足携程 App 的 KV 存储需求。相较于 SharedPreferences 与 NSUserDefaults,MMKV 拥有更强大的性能;相较于 Jetpack Datastore,MMKV 同时支持多个平台,双端业务逻辑一致性会更好;此外,MMKV 的优势还包括:支持多进程访问、进程被突然杀死时存储依然可以生效等。因此,携程机票移动端研发团队决定基于 MMKV 二次开发,使 MMKV 支持 Kotlin Multiplatform 技术栈。
MMKV-Kotlin 因此应运而生,它拥有极为便捷的集成方式,与 MMKV 高度相似的 API 等诸多特点。对于有 MMKV 使用经验的原移动端开发人员来说,学习迁移成本很低。在经过了大半年的线上实验证明了其稳定性与功能的完整性后,携程机票研发团队决定将其开源,为 Kotlin Multiplatform 开源生态添砖加瓦。MMKV-Kotlin Github 地址详见参考链接 3。
二、简单使用
我们先来简单介绍一下 MMKV-Kotlin 的用法,便于读者对其有个较为直观的认识,也便于后文讨论其内部设计。
2.1 安装与导入
对于 KMM 开发者,在 common source set 中导入 MMKV-Kotlin,在 Gradle 脚本(kts)中添加:
dependencies {
implementation("com.ctrip.flight.mmkv:mmkv-kotlin:1.0.0")
}
如果您是使用 Kotlin 编写纯 Android 程序的用户,则导入方式为在 Gradle 脚本(kts)中添加:
dependencies {
implementation("com.ctrip.flight.mmkv:mmkv-kotlin-android:1.0.0")
}
对于纯 Android 开发者来说,虽然没有跨平台的需求,但 MMKV-Kotlin 的 API 有针对 Kotlin 语法作出的优化。
注意,截至文章发布前,MMKV-Kotlin 的最新版本是 1.2.0,基于 Kotlin 1.7.0,MMKV 1.2.13。
2.2 初始化
MMKV 在使用前需要进行初始化,由于 MMKV-Android 强依赖于 Context
类型,因此 MMKV-Kotlin 的初始化 API 在两端有所区别,需要在 Android 与 iOS 的主工程或 KMM 的平台相关 source set 中分别初始化:
Android:
import com.ctrip.flight.mmkv.initialize
// In Android source set
fun initializeMMKV(context: Context) {
val rootDir = initialize(context)
Log.d("MMKV Path", rootDir)
}
iOS:
import com.ctrip.flight.mmkv.initialize
// In iOS source set
fun initializeMMKV(rootDir: String) {
initialize(rootDir)
Log.d("MMKV Path", rootDir)
}
2.3 简单的读写操作
import com.ctrip.flight.mmkv.defaultMMKV
fun demo() {
val kv = defaultMMKV()
kv.set("Boolean", true)
kv.set("Int", Int.MIN_VALUE)
kv.set("String", "Hello from mmkv")
println("Boolean: ${kv.takeBoolean("Boolean")}")
println("Int: ${kv.takeInt("Int")}")
println("String: ${kv.takeString("String")}")
}
使用方式与 MMKV 的 Java 及 Objective-C API 高度相似。
三、架构设计
MMKV core 采用 C++ 编写,其绝大部分功能都在 core 实现。例如 mmap 提供的内存-文件映射、数据根据 protobuf 协议序列化与反序列化、多进程实现等等。core 直接对外暴露 C++ API,在 Win32、POSIX 等系统上可由开发者直接使用。在 core 的外层 MMKV 提供了多种语言的包装,用于支持多种技术栈。例如:Java(Android)、Objective-C(iOS/macOS)、Dart(Flutter)、 JavaScript(React-Native,非腾讯开发与维护)。
MMKV-Kotlin 在底层需要依赖并调用 MMKV,对上希望暴露与 MMKV 类似的 API 并做一些符合语言特性的封装。
MMKV-Kotlin 需要在两个平台相关的 source set 分别集成 MMKV。在 Android source set 中,如果直接集成 MMKV core 需要手动编写 JNI 来做 JVM 层与 C++ 的交互,投入产出比太小, 因此我们选择直接在 Gradle 脚本中通过 Maven 依赖 MMKV-Android,在 Android source set 中直接调用其 Java API。而在 iOS source set 中,由于 Kotlin 目前只与 C 和 Objective-C 有较为完整的互操作能力,因此直接依赖提供 C++ API 的 MMKV core 也并不合适,我们选择在 Gradle 脚本中通过 CocoaPods 依赖 MMKV-iOS,在 iOS source set 中通过其 Objective-C API 完成对 MMKV 的调用。
MMKV-Kotlin 的总体设计见下图: