MMKV原理与实战:高性能键值存储组件深度解析
1. 项目概述为什么我们需要MMKV在移动端开发尤其是Android和iOS平台上数据持久化是一个绕不开的话题。如果你做过几年开发肯定对SharedPreferencesAndroid和NSUserDefaultsiOS又爱又恨。爱的是它们简单易用恨的是它们在性能、稳定性和多进程支持上的种种“坑”。比如SharedPreferences的commit是同步的会阻塞UI线程而apply虽然是异步的但在某些极端情况下如进程被杀可能导致数据丢失更别提它那糟糕的多进程支持了。当你的应用需要存储一些简单的键值对比如用户设置、登录状态、缓存标记时你需要的不是一个重型数据库而是一个快、稳、小的存储方案。这就是MMKV诞生的背景。它是由微信团队开源的一个基于内存映射mmap的键值对存储组件。我第一次在项目里引入MMKV替换掉老旧的SharedPreferences时最直观的感受就是“顺滑”——读取几乎无感写入速度快得惊人而且再也没遇到过因为存储导致的ANR应用无响应。它本质上解决的不是“能不能存”的问题而是“存得是否高效可靠”的问题。对于中高级开发者来说理解MMKV的原理能让你在遇到复杂数据存储场景如跨进程、大数据量、高并发写入时心里更有底。这篇文章我就结合自己多次集成和封装的经验把MMKV从里到外讲透包括它的核心原理、基本使用、进阶技巧以及如何根据项目需求进行二次封装让你能真正“驾驭”这个利器而不是仅仅停留在调API的层面。2. MMKV核心原理深度拆解要用好一个工具必须理解它的工作原理。MMKV的高性能并非魔法而是建立在几个关键的技术选择之上。2.1 基石内存映射mmap技术这是MMKV所有特性的基石。传统文件IO如Java的FileOutputStream需要经过“用户缓冲区 - 内核缓冲区 - 磁盘”的多次拷贝。而mmap是一种将文件或设备直接映射到进程地址空间的方法。它是如何工作的当MMKV初始化时它会通过系统调用将一个文件比如mmkv.default映射到当前进程的一块虚拟内存区域。之后你对这块内存的读写操作操作系统会在背后自动同步到对应的文件上。这带来了几个根本性优势极高的读写速度省去了数据在用户态和内核态之间来回拷贝的开销。读取数据相当于直接读内存写入数据也相当于写内存由操作系统负责写回磁盘效率极高。数据可靠性由于映射关系由操作系统内核管理即使进程意外崩溃内核也会尽力确保已写入映射区域的数据同步到磁盘取决于映射模式这比SharedPreferences的apply机制更可靠。跨进程共享潜力多个进程可以将同一个文件映射到各自的地址空间从而实现内存共享这是实现高效跨进程通信的基础。注意mmap有两种常见模式。MAP_SHARED表示映射区域的修改会写回文件并允许其他映射了同一文件的进程看到更改MMKV主要使用此模式。MAP_PRIVATE则创建写时复制Copy-on-Write的私有映射修改不会影响原文件。2.2 数据结构Protobuf编码与顺序写入MMKV没有采用B-Tree或LSM-Tree等复杂结构它存储的是一系列键值对Key-Value Pair。其内部存储可以简化为一个连续的内存块也是文件内容。写入过程 当你调用mmkv.encodeInt(“key”, 123)时MMKV并不是在原地修改某个值。它的流程是这样的序列化将键“key”和值123用Protocol BuffersProtobuf格式进行序列化生成一段二进制数据。Protobuf编码非常紧凑体积比XML或JSON小很多。追加写入将序列化后的这条键值对数据追加到当前内存映射区域的末尾。更新索引在内存中维护一个哈希表或字典将键“key”映射到这条数据在内存映射区域中的起始位置指针和长度。标记旧数据如果键“key”之前已经存在那么旧数据所在的位置不会被立即擦除而是被标记为“无效”。整个文件看起来就是一系列新旧数据交替的片段。读取过程当你要读取“key”时MMKV先在内存的索引哈希表里查找。找到该键对应的最新数据的指针和长度。直接从内存映射区域的对应位置读取二进制数据。用Protobuf反序列化得到值123。这种追加写入和内存索引的设计使得写入操作几乎总是O(1)的复杂度只需追加和更新哈希表读取也是O(1)哈希表查找。而标记旧数据产生的“碎片”则通过下面这个机制来处理。2.3 空间回收全量写入与重整随着不断更新和删除键值对文件中会积累大量被标记为无效的“碎片”空间。如果放任不管文件会无限膨胀。MMKV采用了一种简单而有效的策略空间不足时触发全量重整。触发时机当一次新的写入操作发现剩余空间不足时或者碎片太多达到一定阈值MMKV不会直接扩容而是先尝试“垃圾回收”。重整过程遍历有效数据MMKV遍历内存索引找出所有未被标记为无效的、最新的键值对数据。写入新文件将这些有效数据按顺序、紧凑地写入一个临时的新文件或内存缓冲区。原子替换用这个新的、紧凑的数据文件原子性地替换掉旧的、充满碎片的文件。在Android/iOS上这通常通过重命名文件操作来完成保证在替换瞬间发生崩溃数据也不会损坏要么是旧文件要么是新文件。重建内存映射重新建立对新文件的内存映射并重建内存索引哈希表。这个过程类似于数据库的“VACUUM”操作。虽然是一次成本较高的操作但由于MMKV存储的数据量通常不大适合存储配置而非大量业务数据且发生频率不高因此总体性能影响很小。这种设计在空间利用率和写入性能之间取得了很好的平衡。2.4 多进程协同文件锁与状态同步MMKV宣称支持多进程访问这是它比SharedPreferences强大的关键一点。其核心是通过文件锁来实现进程间同步。基本原理写锁独占锁当一个进程需要写入时它会尝试获取文件的写锁。如果获取成功其他进程的读写操作都会被阻塞直到该进程释放锁。这保证了写入的原子性防止数据混乱。读锁共享锁多个进程可以同时持有读锁进行读取操作。但只要有一个进程持有写锁其他进程就无法获取读锁。状态同步仅仅锁住写入还不够。进程A写入后进程B需要知道文件内容已经变了。MMKV通过比较文件的实际大小或一个CRC校验码来判断。每个进程在读取前会检查这些元信息是否与上次读取时一致。如果不一致说明有其他进程修改了数据当前进程就需要重新加载整个文件重新mmap并重建索引以获取最新数据。一个常见的坑 假设进程A和B同时启动。A先写入B后读取。如果B在初始化时加载了旧的数据快照那么它可能读不到A刚写入的数据。因此在多进程环境下比较推荐的做法是在每次读取关键数据前主动调用MMKV.mmkvWithID()并指定MMKV.MULTI_PROCESS_MODE来获取实例这个操作内部会检查并处理可能的更新。或者使用MMKV提供的进程间通信通知如Android上的ContentProvider或文件描述符通知让一个进程的数据变更能主动通知到其他进程。3. 从零开始MMKV的集成与基础使用理解了原理我们来看看如何把它用起来。这里以Android平台为例iOS的集成方式类似主要通过CocoaPods或手动导入。3.1 环境集成与初始化首先在项目的build.gradle文件中添加依赖dependencies { implementation com.tencent:mmkv:1.3.4 // 请使用最新版本 }然后在Application的onCreate方法中进行初始化。这一步至关重要必须在所有MMKV实例创建之前完成。class MyApp : Application() { override fun onCreate() { super.onCreate() val rootDir MMKV.initialize(this) Log.i(MMKV, MMKV存储根路径: $rootDir) // 通常路径是 /data/data/your.package.name/files/mmkv/ } }MMKV.initialize(Context)会设置默认的根目录。你也可以传入一个自定义的绝对路径字符串。3.2 核心API使用详解获取MMKV实例是最常见的操作。默认情况下MMKV会提供一个单例的默认实例对应一个名为mmkv.default的文件。// 获取默认实例单例对应 mmkv.default 文件 val kv MMKV.defaultMMKV() // 存储各种类型的数据 kv.encode(bool, true) kv.encode(int, 123) kv.encode(long, 456789L) kv.encode(float, 3.14f) kv.encode(double, 2.71828) kv.encode(string, Hello MMKV) kv.encode(byteArray, byteArrayOf(1, 2, 3)) // 读取数据第二个参数是默认值当key不存在时返回 val b kv.decodeBool(bool, false) val i kv.decodeInt(int, 0) val s kv.decodeString(string, ) // 删除数据 kv.removeValueForKey(key_to_remove) // 或删除多个 kv.removeValuesForKeys(arrayOf(key1, key2))这里有个非常重要的细节encode和decode系列方法都是强类型的。你不能用decodeString去读一个用encodeInt存储的key否则会得到类型错误或默认值。MMKV在存储时会将值的类型信息也一并编码。这就要求我们在设计Key的时候最好保持其类型不变或者有清晰的约定。3.3 多实例与多进程模式如果你的应用数据需要分类存储或者需要多进程访问就需要创建不同的MMKV实例。// 1. 获取一个指定ID的实例对应 mmkv.[mmapID] 文件 val separateKV MMKV.mmkvWithID(myStorage) // 2. 获取一个指定ID且支持多进程的实例 val multiProcessKV MMKV.mmkvWithID(interProcessData, MMKV.MULTI_PROCESS_MODE) // 3. 获取一个指定ID、支持多进程、且加密的实例 val cryptKey My-Encryption-Key.toByteArray() val secureKV MMKV.mmkvWithID(secureData, MMKV.MULTI_PROCESS_MODE, cryptKey)关于多进程模式的注意事项MMKV.MULTI_PROCESS_MODE底层使用了文件锁性能相比单进程模式有损耗但比SharedPreferences的MODE_MULTI_PROCESS可靠得多。加密功能使用的是AES CFB-128算法。密钥至关重要如果密钥丢失数据将无法解密。建议将密钥存储在安全的地方如Android Keystore。3.4 与SharedPreferences的迁移对于存量项目MMKV贴心地提供了从SharedPreferences迁移数据的一键功能。这可以在初始化后立即进行。class MyApp : Application() { override fun onCreate() { super.onCreate() MMKV.initialize(this) // 从默认的SharedPreferences迁移 MMKV.defaultMMKV()?.let { mmkv - val oldSp getSharedPreferences(“default_sp_name”, Context.MODE_PRIVATE) mmkv.importFromSharedPreferences(oldSp) oldSp.edit().clear().apply() // 可选迁移后清空旧数据 Log.i(“MMKV”, “数据迁移完成”) } } }迁移操作是增量的且会覆盖MMKV中已有的同名Key。建议在版本升级时执行一次即可。4. 进阶实践性能优化与陷阱规避掌握了基本用法我们来看看如何用得更好、更稳。以下都是我在实际项目中踩过坑或优化后总结的经验。4.1 性能关键避免高频次写入与Value尺寸控制MMKV的写入很快但任何持久化操作都有成本。不当的使用模式会成为性能瓶颈。反面案例// 在列表滚动时频繁更新同一个标记位 fun onScrollStateChanged(newState: Int) { MMKV.defaultMMKV().encode(“last_scroll_state”, newState) // 错误高频写入 }优化方案合并写入对于非实时性要求极高的数据可以积累多次变更在一次事务中写入。MMKV本身不支持事务但你可以通过封装来实现比如先写入内存缓存定时或特定时机如页面onPause再批量持久化。使用内存缓存对于极高频读取、低频修改的数据可以在内存中维护一份副本直接读取内存仅在数据变更时更新MMKV。控制Value大小MMKV适合存储配置、状态等小数据。切勿将大型对象如图片Bitmap、长JSON文本直接序列化后存入。对于大文件应该存储在文件系统中而在MMKV里只存其路径或元信息。Protobuf虽然高效但巨大的Value会导致每次写入和重整GC时内存和IO压力剧增。4.2 多进程数据同步的“延迟”问题正如原理部分提到的MMKV的多进程同步依赖于文件锁和文件状态检查这并非实时通知。进程B可能无法立刻感知进程A的写入。解决方案主动检查在读取关键数据前尤其是从后台进程切换到前台时可以考虑调用mmkv.sync()或重新获取MMKV实例MMKV.mmkvWithID(...)强制进行一次同步检查。结合其他IPC对于需要强实时同步的场景可以结合使用其他IPC机制。例如进程A写入后通过Broadcast、ContentProvider或AIDL等通知进程B“数据已变请重新加载”。进程B收到通知后再调用MMKV的重新加载逻辑。设计降级从架构上思考是否真的需要强实时很多场景下轻微延迟几百毫秒是可以接受的。明确业务对一致性的要求级别。4.3 数据备份与恢复策略MMKV文件虽然可靠但存放在应用沙盒内。当用户清除应用数据或卸载重装时数据会丢失。对于需要备份的配置如用户个性化设置需要有自己的备份方案。常见方案备份到云端将关键的MMKV数据通过mmkv.allKeys()和decode系列方法获取在登录后同步到服务器。备份到外部存储定期将MMKV文件位于/data/data/package/files/mmkv/拷贝到外部存储或应用专属目录。注意Android 11API 30以上的分区存储限制。导出为可读格式可以提供一个“导出设置”功能将MMKV中的数据转换为JSON或XML文件让用户自己保存。恢复时逆向操作即可。但要注意版本兼容性如果数据结构Key或Value类型在新版本中已改变需要编写迁移代码。4.4 监控与调试技巧当存储出现异常时如何排查查看文件内容仅限调试 MMKV文件是二进制的无法直接查看。但你可以将文件从设备中拉取出来adb pull /data/data/your.package/files/mmkv/mmkv.default .使用strings命令查看其中的字符串片段strings mmkv.default或者写一个简单的调试工具遍历所有Key并打印出来。关注日志 MMKV在初始化失败、文件读写错误、CRC校验失败时会打印错误日志到Logcat。关注MMKV这个Tag。性能监控 在encode/decode前后打点监控耗时。如果发现某个操作特别慢可能是触发了全量重整GC。考虑是否该Value过大或写入过于频繁。5. 项目级封装构建更易用的存储组件直接使用MMKV的API虽然简单但在大型项目中散落的encode/decode调用会带来维护问题Key的管理混乱、类型不安全、无法统一进行数据迁移或加密等。因此对其进行二次封装是很有必要的。下面分享一种我在项目中常用的封装模式。5.1 封装目标与设计我们的封装要达到以下几个目标集中管理Key避免硬编码字符串散落各处。类型安全利用Kotlin的强类型特性在编译期就杜绝类型错误。提供默认值每个Key都对应一个合理的默认值。支持多实例方便按模块划分存储空间。可扩展性方便未来替换存储实现比如从MMKV换到其他库或增加统一功能如加密、迁移、监听。5.2 封装实现代码详解我们采用“接口 委托”的方式利用Kotlin的ReadWriteProperty属性委托特性。第一步定义存储接口interface IStorage { fun putInt(key: String, value: Int) fun getInt(key: String, default: Int): Int fun putString(key: String, value: String) fun getString(key: String, default: String): String fun putBoolean(key: String, value: Boolean) fun getBoolean(key: String, default: Boolean): Boolean fun putLong(key: String, value: Long) fun getLong(key: String, default: Long): Long fun putFloat(key: String, value: Float) fun getFloat(key: String, default: Float): Float fun putStringSet(key: String, value: SetString) fun getStringSet(key: String, default: SetString): SetString fun remove(key: String) fun contains(key: String): Boolean fun clear() }第二步实现基于MMKV的存储类class MMKVStorage private constructor(private val mmkv: MMKV) : IStorage { companion object { // 获取默认存储 fun default(): MMKVStorage { return MMKVStorage(MMKV.defaultMMKV()) } // 根据ID获取存储 fun withId(id: String, mode: Int MMKV.SINGLE_PROCESS_MODE, cryptKey: String? null): MMKVStorage { val kv if (cryptKey ! null) { MMKV.mmkvWithID(id, mode, cryptKey) } else { MMKV.mmkvWithID(id, mode) } return MMKVStorage(kv) } } override fun putInt(key: String, value: Int) mmkv.encode(key, value) override fun getInt(key: String, default: Int): Int mmkv.decodeInt(key, default) override fun putString(key: String, value: String) mmkv.encode(key, value) override fun getString(key: String, default: String): String mmkv.decodeString(key, default) ?: default // ... 其他类型方法的实现类似注意decodeString可能返回null override fun putStringSet(key: String, value: SetString) mmkv.encode(key, value) override fun getStringSet(key: String, default: SetString): SetString mmkv.decodeStringSet(key, default) ?: default override fun remove(key: String) mmkv.removeValueForKey(key) override fun contains(key: String): Boolean mmkv.containsKey(key) override fun clear() mmkv.clearAll() }第三步定义属性委托类这是实现类型安全和集中管理Key的核心。class PreferenceDelegateT( private val storage: IStorage, private val key: String, private val defaultValue: T, private val save: IStorage.(String, T) - Unit, private val read: IStorage.(String, T) - T ) : ReadWritePropertyAny?, T { override fun getValue(thisRef: Any?, property: KProperty*): T { return storage.read(key, defaultValue) } override fun setValue(thisRef: Any?, property: KProperty*, value: T) { storage.save(key, value) } }第四步集中定义所有配置项Keyobject AppSettings { // 获取存储实例这里用默认的也可以按模块分 private val storage: IStorage by lazy { MMKVStorage.default() } // 使用委托属性定义每一个配置项 var isFirstLaunch by PreferenceDelegate( storage, “is_first_launch”, true, save { k, v - putBoolean(k, v) }, read { k, d - getBoolean(k, d) } ) var userId by PreferenceDelegate( storage, “user_id”, “”, save { k, v - putString(k, v) }, read { k, d - getString(k, d) } ) var notificationEnabled by PreferenceDelegate( storage, “notification_enabled”, true, save { k, v - putBoolean(k, v) }, read { k, d - getBoolean(k, d) } ) var lastLoginTime by PreferenceDelegate( storage, “last_login_time”, 0L, save { k, v - putLong(k, v) }, read { k, d - getLong(k, d) } ) // 对于复杂对象可以序列化为JSON字符串存储 var userProfileJson by PreferenceDelegate( storage, “user_profile”, “”, save { k, v - putString(k, v) }, read { k, d - getString(k, d) } ) // 然后提供扩展属性来方便地访问 val userProfile: UserProfile? get() try { Gson().fromJson(userProfileJson, UserProfile::class.java) } catch (e: Exception) { null } fun saveUserProfile(profile: UserProfile) { userProfileJson Gson().toJson(profile) } }5.3 封装后的使用方式与优势使用方式变得极其简洁和安全// 读取 val isFirst AppSettings.isFirstLaunch val userId AppSettings.userId // 写入 AppSettings.isFirstLaunch false AppSettings.userId “12345” AppSettings.saveUserProfile(UserProfile(“Tom”)) // 删除某个设置如果需要 // 封装类可以增加一个删除特定key的方法这种封装带来的好处强类型AppSettings.userId是String类型不可能误赋值为Int。Key集中管理所有Key都在AppSettings对象中定义查找、修改、重构都非常方便。默认值清晰每个属性都明确了默认值。使用简单像访问普通属性一样读写持久化数据。易于测试和替换IStorage接口使得我们可以很容易地创建内存实现的MockStorage用于单元测试或者未来更换底层存储库。扩展思考 你可以进一步扩展这个封装例如增加数据变更监听在PreferenceDelegate的setValue中通知观察者。支持迁移在AppSettings的init块中编写从旧版存储如SharedPreferences迁移到新版MMKV的逻辑。按模块划分创建UserSettings、AppConfigSettings等不同对象分别对应不同的MMKV实例ID实现数据隔离。6. 常见问题排查与实战技巧即使理解了原理和封装在实际开发中还是会遇到一些具体问题。这里我整理了一个排查清单和几个实战技巧。6.1 问题排查速查表问题现象可能原因排查步骤与解决方案初始化失败1. 存储路径无权限。2. 磁盘空间已满。3. 自定义路径不存在或不可写。1. 检查MMKV.initialize()传入的Context或路径是否正确。2. 查看Logcat中MMKV的详细错误日志。3. 确保应用有必要的存储权限对于自定义外部路径。读取数据为默认值1. Key拼写错误。2. 数据类型不匹配用decodeString读encodeInt存的Key。3. 多进程下未及时同步。4. 数据已被删除或从未写入。1. 检查Key字符串是否一致注意大小写和空格。2. 统一每个Key的存取类型或封装时加强约束。3. 确认是否使用MULTI_PROCESS_MODE并尝试主动调用sync()或重新获取实例。4. 使用mmkv.containsKey(key)确认Key是否存在。写入后数据丢失1. 进程在apply异步写入完成前被杀死MMKV的mmap机制比SharedPreferences的apply更可靠但极端情况下仍有风险。2. 多进程写入冲突后写入的覆盖了先写入的需业务层加锁。3. 调用了clearAll()或removeValueForKey()。1. 对于极其重要的数据考虑在写入后调用mmkv.sync()强制同步到文件但会影响性能。2. 检查多进程写入逻辑确保对同一数据的写入有互斥锁保护。3. 审查代码逻辑。文件大小异常增长1. 存储了非常大的Value如图片Base64。2. 频繁更新和删除导致碎片过多但尚未触发重整。1.绝对不要用MMKV存放大数据。大文件应存于文件系统MMKV只存路径。2. 可以尝试手动触发重整mmkv.trim()或mmkv.clearMemoryCache()谨慎使用会清空内存缓存。通常等待自动GC即可。多进程读取到旧数据进程B持有的MMKV实例缓存了旧的文件映射未检测到文件已被进程A修改。1. 确保使用MMKV.MULTI_PROCESS_MODE。2. 在读取关键数据前调用mmkv.reload()强制重新加载文件。3. 使用进程间通信通知数据变更。6.2 实战技巧监听数据变化MMKV本身不提供数据变化监听回调但我们可以利用Kotlin的Delegates.observable或自定义委托来实现一个简易的监听。class ObservablePreferenceDelegateT( private val storage: IStorage, private val key: String, private val defaultValue: T, private val save: IStorage.(String, T) - Unit, private val read: IStorage.(String, T) - T, private val onChange: ((old: T, new: T) - Unit)? null ) : ReadWritePropertyAny?, T { private var currentValue: T storage.read(key, defaultValue) override fun getValue(thisRef: Any?, property: KProperty*): T { return currentValue } override fun setValue(thisRef: Any?, property: KProperty*, value: T) { val oldValue currentValue if (oldValue ! value) { storage.save(key, value) currentValue value onChange?.invoke(oldValue, value) } } } // 使用示例 object ObservableSettings { private val storage MMKVStorage.default() var darkMode by ObservablePreferenceDelegate( storage, “dark_mode”, false, save { k, v - putBoolean(k, v) }, read { k, d - getBoolean(k, d) }, onChange { old, new - // 当主题模式改变时通知UI更新 EventBus.post(DarkModeChangedEvent(new)) // 或者使用LiveData/Flow } ) }6.3 实战技巧数据加密与安全增强MMKV提供了基础的AES加密但密钥需要你自己管理。对于安全要求更高的场景如存储登录Token可以结合Android Keystore系统来管理加密密钥避免密钥硬编码在代码中。fun createSecureMMKV(context: Context, mmapID: String): MMKV { val alias “mmkv_key_alias” val keyStore KeyStore.getInstance(“AndroidKeyStore”) keyStore.load(null) // 尝试获取已有的密钥 val secretKeyEntry keyStore.getEntry(alias, null) as? KeyStore.SecretKeyEntry val secretKey secretKeyEntry?.secretKey if (secretKey null) { // 生成新的密钥 val keyGenerator KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, “AndroidKeyStore”) val keyGenSpec KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) // 使用GCM模式更安全 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setKeySize(256) .build() keyGenerator.init(keyGenSpec) secretKey keyGenerator.generateKey() } // 将密钥转换为字节数组注意此操作在Android P以上可能受限 // 更安全的方式是使用KeyStore的Cipher进行wrap/unwrap这里简化为直接获取 val keyBytes secretKey.encoded ?: throw IllegalStateException(“Failed to get key bytes”) // 使用密钥创建加密的MMKV实例 return MMKV.mmkvWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, keyBytes) }重要提示密钥管理是安全的核心。上述示例是一种思路实际生产环境中尤其是在Android PAPI 28及以上版本直接获取SecretKey.encoded可能返回null或受限。更安全的做法是利用KeyStore的Cipher进行加密解密操作或者使用AndroidKeyStore的KeyStore类来保护密钥本身。建议仔细阅读Android官方关于AndroidKeyStore的文档并根据目标API级别设计密钥管理方案。6.4 性能压测建议如果你担心MMKV在极端情况下的性能可以设计简单的压测。例如连续写入/读取1万次小数据对比SharedPreferences的apply和commit。在我的测试中MMKV的写入速度通常是SharedPreferences.commit的数十倍甚至上百倍与apply相比也显著更快且稳定性更高。读取速度更是内存级别的。这个测试可以让你对性能有更直观的信心。最后我个人在多个项目中用MMKV替换SharedPreferences后最深的体会是它把一件本该简单可靠的事情真的做到了简单可靠。你不再需要担心ANR担心多进程数据错乱担心偶发的数据丢失。它就像一把锋利而趁手的瑞士军刀对于移动端的轻量存储需求几乎是目前最优解。当然没有银弹它不适合存储大量结构化数据或频繁更新的日志流那是SQLite或专业时序数据库的领域。理解它的边界在正确的场景使用它才能最大化其价值。
