Android数据持久化方案调研-MMKV SP REALM ROOM WCDB...

7,721 阅读9分钟

官方方案对比

SharedPreference

简称SP,使用键值对的形式保存原始类型的数据,默认以XML格式的文件来存储这些数据

  • 适用场景:存储量小、简单的数据
  • 优缺点:有自己的内存级的缓存,在数据量小的时候读取较快,但是跨进程不安全,数据量大的时候加载缓慢,全量写入,容易引起ANR

SQLite

  • 适用场景:比较复杂数据类型并且较大的数据量,成千上万的级别
  • 优缺点:相比SP来说,需要在数据量较大的时候才有优势,数据的获取较慢,大量写入需要注意使用事务批处理,性能较高,如果频繁的单次插入性能不高,直接通过sql操作比较繁琐,可以考虑orm框架

File

  • 适用场景:顾名思义比较适用于文件类型的存储,如图片或者存储一些简单的文本数据或二进制数据
  • 优缺点:最基本的存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件当中的

小结

由于我们目前使用的SP方案,我们的痛点,性能差,和ANR问题,考虑换一种数据存储方案,首先我们的数据存储的场景并不是复杂数据类型,量上也不是太大,所以优先考虑的就是MMKV,为了更清楚的了解MMKV的优缺点,所以对比参考下

开源方案对比

各种方案简介

名称 MMKV Realm WCDB Room
方案 mmap nosql SQLCipher SQLite
版本 1.0.24 6.0.2 1.0.8 2.2.3
size 0.15mb 1.5mb 0.7mb 0.05mb
迁移 从SP迁移方便 从SQLite迁移方便
场景 替换SP 替换SQLite 替换SQLite 替换SQLite

注: size 为armeabi-v7a架构下打包增量大小
mmkv包含lib_c++shared库的size为0.3mb

性能对比

初始化

初始化 MMKV SP Realm Room WCDB
耗时 30ms 4ms 60ms 15ms 80ms
内存 1mb 约1mb 约2mb 1mb 2mb
cpu峰值 16% 20% 18% 15% 15%

读写速度和数据文件大小等数据对比

SP 执行次数 写入耗时 读取耗时 数据文件
字符串 200 51ms 4ms 8kb
字符串 1000 171ms 6ms 42kb
Java实体 1000 220ms 87ms 172kb
MMKV 执行次数 写入耗时 读取耗时 数据文件
字符串 200 8ms 5ms 4kb
字符串 1000 20ms 9ms 16kb
Java实体 1000 117ms 93ms 128kb
Realm 执行次数 写入耗时 读取耗时 数据文件
字符串 200 229ms 74ms 32kb
字符串 1000 606ms 122ms 288kb
Java实体 1000 760ms 130ms 576kb
Room 执行次数 写入耗时 读取耗时 数据文件
字符串 200 636ms 160ms 32kb
字符串 1000 1350ms 475ms 48kb
Java实体 1000 1298ms 432ms 68kb
wcdb 执行次数 写入耗时 读取耗时 数据文件
字符串 200 636ms 160ms 32kb
字符串 1000 1350ms 475ms 48kb
Java实体 1000 1298ms 432ms 68kb

内存消耗

内存消耗 MMKV SP
初始化 + 查询1k条 1.7mb 1.2mb
初始化 + 查询1w条 3.6mb 2.8mb
初始化 + 查询5w条 10.7mb(文件8mb) 8.5mb(文件8.6mb)

指标备注

字符串数据:含有随机数的10个字节
运行手机:华为 Mate10
执行线程:子线程
统计方式:取3次平均值
内存消耗:业务代码读取对应数据后释放本地对象后的内存增量
由于序列化反序列化会有缓存,Java实体
SP写入为apply异步方式
Realm、WCDB、Room 的插入操作均为单条插入
MMKV、SP 读取和写入Java实体对象数据时间包含实体类序列化和反序列化时间,不过由于序列化反序列化会有缓存,时间会稍偏小

测试整理

  • 初始化性能,cpu峰值和内存增长都相差无几,耗时来看,由于SP和Room都是系统原生的方案,耗时较短。
  • MMKV和SP新增了内存消耗一项,主要是因为自己有一份内存级别的缓存,需要验证下对内存的影响,从测试数据来看,这两个方案内存增长相当,可以接受。
  • MMKV使用protobuf存储,SP使用xmlprotobuf相比xml来说性能更好,体积也会减小,但是真实存储空间来看,mmkv的扩容机制(在空间不足时会申请一倍的空间)可能会导致实际使用空间变大,相比较SP的占用的存储空间就是真实的数据大小。

开源方案总结

MMKV

MMKV 通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。而SP则 MMKV 是一个跨平台的解决方案,写入性能相比sp有很大提升,适用于需要一个通用的 key-value 存储组件,有跨进程访问需求,尤其是对现在sp存储方案的不满的同学,mmkv同时支持了sp的迁移支持,基本上不用调整太多的代码,迁移事项下面会详述。

Realm

Realm作为一款移动端的NoSQL框架,官方定位就是替代SQLite等关系型数据库,跨平台,性能优秀,有可视化的查看工具。不足也比较明显,不论是集成包size还是数据文件都有点大。另外Realm要求当前Bean对象必须直接继承RealmObject,侵入性非常强。有线程限制,不能跨线程使用数据对象。另外我在写测试demo的时候发现了一个坑,如果一个线程查询出列表后,另一个线程再去增加或删除会使得本地数据文件产生备份,导致数据库文件急速增长,达到几百MB,并且不会自动清理,官方对这个也有说明:

WCDB

WCDB是一个高效、完整、易用的移动数据库框架,基于SQLCipher,支持iOS, macOS和Android

Room

Room并不是一个数据库,他是在 SQLiteo 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。并保留了灵活的接口适配层。目前WCDB已经支持接入Room:github.com/Tencent/wcd…

ObjectBox

想尝试的同学可以试试 ObjectBox,这个也是GreenDao作者的另一力作,据说性能比Realm还要好,由于本次是对老项目中的SP存储方案选型替换,暂时先不对比ObjectBox了,后续有时间了完善下相关对比记录,有几篇文章大家可以参考
notes.devlabs.bg/realm-objec…
chejdj.github.io/android/201…

综述

总结下吧,sp 和 mmkv 都是自己有一份内存级别的缓存,所以查询非常快,但是如果数据量过大,会造成内存溢出,mmkv官方也对这点有说明,不要超过100mb,其实对正常的数据来说,肯定到不了这个限制,如果30mb以上的数据,都要考虑使用数据库类型的存储了,上面测试数据对于数据库类型的存储不太公平,主要针对单次频繁插入查询的使用场景,数据库使用场景一般会多条数据通过事务批量写入,查询的时候也会查询一类数据集合。综上所述,目前采用mmkv替换sp的实现还是有很大收益的,性能和跨进程上都有优势,并且避免了sp的ANR问题,体积上也没有加大多少;如果数据库方案,建议采用Room,或者在尝试下上面说的但是没在本次测试对比的objectbox。感觉要注意的挺多,不过零零散散只整理了这么点...

MMKV迁移参考

  • 迁移官方文档:github.com/Tencent/MMK…

  • MMKV 不支持 Serializable,但是可以考虑把 Serializable 的 java对象 转换成 byte 存入MMKV

  • MMKV初始存储空间4kb,不足的时扩容最少是一倍,并且为了效率在移除key value不会减小磁盘存储空间,官方提供了kv.trim方法去缩减磁盘存储空间,不过官方建议调用时机是有大量移除操作的时候执行,一般无需考虑存储文件增长问题,但这个时机不好掌握,不过感觉可以定期清理一次

  • SP#getAll SP#registerOnSharedPreferenceChangeListener 等方法不支持,调用会抛出运行时异常,需要注意。getAll因为擦除了类型,所以不支持,感觉这是mmkv相对较弱的地方,也为以后迁移其他存储方案增加了成本

  • 一些 Android 设备(API level 19)在安装/更新 APK 时可能出错, 导致 libmmkv.so 找不到。然后就会遇到 java.lang.UnsatisfiedLinkError 之类的 crash。有个开源库 ReLinker 专门解决这个问题,不过现在还在支持19的应用应该不多了

  • 对于历史项目SP数据可能比较庞大,这样直接使用官方提供的mmkv#importFromSharedPreferences方法直接对一个文件进行迁移,会造成耗时过长的问题,目前Demo测试数据:测试机型华为Mate10,一个4MB左右的SP的文件迁移到MMKV大约耗时80ms左右,到了线上应该还会更长,所以为了避免初次使用卡住的体验问题,这里提供两个解决思路:1.在使用前统一迁移,并且给予用户一个迁移数据的提示;2.在初次使用的时候后台静默迁移。目前我们使用的是第二种思路,这种情况需要保证数据的准确性,如果有同学有兴趣可以留言探讨

参考

官方和网上各种对比测试文章