官方方案对比
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使用xml
。protobuf
相比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.在初次使用的时候后台静默迁移。目前我们使用的是第二种思路,这种情况需要保证数据的准确性,如果有同学有兴趣可以留言探讨
参考
官方和网上各种对比测试文章