Android 的数据存储

1. 目录

  • SharedPreferences、DataStore、mmkv的介绍
  • SharedPreferences 的缺点
  • DataStore 的优点
  • mmkv 的优点
  • SharedPreferences、DataStore、mmkv 的性能对比

2.SharedPreferences、DataStore、mmkv的介绍

SharePreferences

一种轻量级的数据存储方式,它是以key-value的形式保存在 data/data//shared_prefs 下的xml文件中

Jetpack DataStore

一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。

MMKV

是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Win32 / POSIX 平台,一并开源。

3.SharedPreferences 的缺点

  • 通过 getXXX() 方法获取数据,可能会导致主线程阻塞
  • commit() 方法同步提交导致ANR
  • apply() 方法虽然是异步的,可能会发生 ANR、无法获取到操作成功或者失败的结果
  • SharedPreference 不能保证类型安全
  • SharedPreference 加载的数据会一直留在内存中,浪费内存

getXXX 方法

getXXX() 方法都是同步的,在主线程调用 get 方法,必须等待 SP 加载完毕,也有可能导致ANR。

commit 方法

commit 提交是同步的,直到磁盘操作成功后才会完成,所以当数据量比较大时,使用commit很可能引起ANR

apply 方法

apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。

而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。

但是apply同样会引起ANR的问题

为了保证异步任务及时完成,当生命周期处于 handleStopService() 、 handlePauseActivity() 、 handleStopActivity() 的时候会调用 QueuedWork.waitToFinish() 会等待写入任务执行完毕。所以当数据量比较大时,apply也会造成ANR。

SharedPreference 不能保证类型安全

调用 getXXX() 方法的时候,可能会出现 ClassCastException 异常,因为使用相同的 key 进行操作的时候,putXXX 方法可以使用不同类型的数据覆盖掉相同的 key。

val key = "jetpack"
val sp = getSharedPreferences("ByteCode", Context.MODE_PRIVATE) // 异步加载 SP 文件内容

sp.edit { putInt(key, 0) } // 使用 Int 类型的数据覆盖相同的 key
sp.getString(key, ""); // 使用相同的 key 读取 Sting 类型的数据

使用 Int 类型的数据覆盖掉相同的 key,然后使用相同的 key 读取 Sting 类型的数据,编译正常,但是运行会出现以下异常。

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

SharedPreference 加载的数据会一直留在内存中,浪费内存

通过 getSharedPreferences() 方法加载的数据,最后会将数据存储在静态的成员变量中

通过静态的 ArrayMap 缓存每一个 SP 文件,而每个 SP 文件内容通过 Map 缓存键值对数据,这样数据会一直留在内存中,浪费内存。

4.DataStore 的优点

Preferences DataStore 主要用来替换 SharedPreferences,Preferences DataStore 解决了 SharedPreferences 带来的所有问题。

Preferences DataStore 相比于 SharedPreferences 优点

  • DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
  • 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
  • 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
  • 可以监听到操作成功或者失败结果

另外 Jetpack DataStore 提供了 Proto DataStore 方式,用于存储类的对象(typed objects ),通过 protocol buffers 将对象序列化存储在本地,protocol buffers 现在已经应用的非常广泛,无论是微信还是阿里等等大厂都在使用,我们在部分场景中也使用了 protocol buffers,在后续的文章会详细的分析。

5.mmkv 的优点

  • 内存准备
    通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。
  • 数据组织
    数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。
  • 写入优化
    考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。
  • 空间增长
    使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

6.SharedPreferences、DataStore、mmkv 的性能对比

640.png
  1. 测试项目地址:Hoo
  2. 测试过程:单个文件1000次的读写和查询


    640 (1).png
640 (2).png

参考地址

  • 这是一篇你应该了解的Android数据存储优化
  • SharedPreferences替换:MMKV集成与原理

你可能感兴趣的:(Android 的数据存储)