Android应用:SharedPreferences、MMKV和DataStore怎么选

2023-12-13 04:24:34

一、前言

对于 Android 轻量级存储方案,SharedPreferences、MMKV和DataStore都是用来进行键值对存储的,那么在项目中该如何选用呢?

二、SharedPreferences

是 Android 中简单易用的轻量级存储方案,用来保存相关信息。

1.本质:

键值对(key-value)的方式保存数据到 xml 文件

文件路径为:/sdcard/data/data/应用程序包名/shared_prefs

适合存储一些简单的数据,提供了多种数据类型的存储,包括:int、long、string、Boolean、float、Set<String>

读取数据:通过解析 xml 文件,得到指定 key 对应的 value

不支持多进程,虽然有基于 ContentProvider 封装的实现,但是性能低下,经常导致 ANR

2.源码分析

Context.getSharedPreferences(name, mode)

SharedPreferences 是个接口,其真正实现类是 SharedPreferencesImpl。

每当调用 SharedPreferencesImpl 的构造器的时候,都会开始调用 startLoadFromDisk 方法,然后在该方法中开启一个子线程加载 xml 文件中的内容,最后将 xml 中的内容全部加载到 mMap中

当 xml 中数据过大时,将xml一次性加载到内存中,就会导致内存占用过高,可能出现OOM

SharedPreferencesImpl.getString()

????????在获取之前使用了synchronized同步锁,如果?sp 还没加载完毕,主线程会一直阻塞在那里,直到加载 sp 的子线程加载完成? ? ?

????????当数据很大时,可能会导致卡顿。

Editor.commit/apply()

每次调用 edit 方法都会创建一个 Editor 对象,造成额外的内存占用

调用频繁的场景,在多次 put 之后再统一进行?commit/apply,也就是一次更新多个键值对,只进行一次 IO 操作

commit/apply 引发的ANR问题

  • commit 是同步地提交到硬件磁盘,如果在主线程中提交会阻塞线程,影响后续的操作,可能导致 ANR
  • apply 是将修改数据提交到内存,然后异步提交到硬件磁盘,没有返回值,异步操作不会阻塞调用的线程,但是如果写入任务比较耗时,会阻塞住主线程

3.卡顿原理

commit 造成的卡顿原理

commit真正的实现类是SharedPreferencesImpl,在这个类中的commit方法中。

通过enqueueDiskWrite进行写入任务,然后阻塞调用commit的线程,让调用线程处于等待状态,等写入任务完成之后就唤起调用commit的线程,如果实在主线程中执行的commit,就会阻塞主线程,如果写入任务很多,比较耗时,阻塞时间过长,可能导致ANR。

所以不要在主线程执行写入文件的操作

apply造成的卡顿

????????在apply方法中,通过enqueueDiskWrite进行写入任务,但是它最后一个参数不为空,是一个postWriteRunnable回调,因此这里是一个异步任务,这个异步任务的执行是在QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit)中

QueuedWork:

????????QueuedWork是Android系统提供的一个执行异步任务的工具类

内部的实现逻辑的就是创建一个HandlerThread作为工作线程,然后QueuedWorkHandler和这个HandlerThread进行管理,每当有任务就添加进来,在这个异步线程中执行,线程名为:”queued-work-looper“

? ? ? ?每次调用queue方法,都忘sWork 添加一个任务,它是一个LinkedList,这个队列中数据最终在queued-work-looper 线程中依次得到执行

????????apply写入操作是在异步线程中执行,不会导致主线程卡顿

????????但是如果异步任务执行时间过长,当ActvityThread执行了handleStopActivity()、handleServiceArgs()、handlePauseActivity() 等方法的时候都会调用QueuedWork.waitToFinish(),而此方法中会在异步任务执行完成前一直阻塞主线程,造成卡顿

? ? ? ? 也就是说虽然apply写入操作是在异步线程中执行,但是如果要销毁的时候就会等待异步线程执行完成,导致卡顿

SharedPreferences 获取数据造成的卡顿

????????数据加载是异步执行的,开启了一个子线程去执行,但是在get数据的时候,会调用awaitLoadedLocked 这个方法,当数据没有加载完,就让调用的线程处于等待中,导致阻塞

4.总结

  • 不支持多进程
  • 会导致卡顿、ANR
  • 同步比较耗时
  • 异步无法回调

三、MMKV

MMKV是微信开源出来的,起初是为了在文字显示之前先把它记录到磁盘,如果文字显示失败导致程序崩溃,就可以将它从磁盘里恢复

1.原理:

????????基于内存映射文件的方式实现的

????????使用mmap方式进行内存映射的key-value组件,底层使用protobuf实现,通过将文件映射到内存中,从而使得读取和写入操作都可以在内存中进行,避免了频繁的IO操作,写入性能是极高的

2.优点

  • 极高的同步写入磁盘的性能,稳定性强
  • 支持多进程

3.缺点

  • 丢数据,如果程序崩溃,或者断电关机等异常情况磁盘里的文件就会写入不完整的形式保留

四、DataStore

DataStore 被创造出来的目标就是替代 SharedPreferences

  • 异步操作:提供异步的读写操作,避免阻塞主线程;
  • 类型安全:支持使用协议缓冲区(Protocol Buffers)来定义数据模型。在编译时进行检测;
  • 支持多种数据类型:支持存储不同类型的数据,包括原始类型、对象或自定义类;
  • 数据一致性:提供一致性和安全性保证,保证在多个写入操作中的数据一致性;
  • 流式数据访问:支持使用流(Flow)来访问数据,使得可以轻松地观察数据的变化并进行相应的更新。

文章来源:https://blog.csdn.net/weixin_42277946/article/details/134726479
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。