我正在参加「掘金·启航计划」
Android数据存储方案
1.SharedPreferences前世今生
Sp的主要缺点
- SP主要缺点
- SP用内存层用HashMap保存,磁盘层则是用的XML文件保存。每次更改都需要将整个HashMap序列化为XML格式的报文然后整个写入文件。
- SP读写文件不是类型安全的,且没有发出错误信号的机制,缺少事务性API
- commit() / apply()操作可能会造成ANR问题
- 其较慢原因:
- 1、不能增量写入
- 2、序列化比较耗时
SP可能造成ANR
-
commit() / apply()操作可能会造成ANR问题
- commit()是同步提交,会在UI主线程中直接执行IO操作,当写入操作耗时比较长时就会导致UI线程被阻塞,导致ANR;
- apply()虽是异步提交,但异步写入磁盘时,如果执行了Activity / Service中的 onStop()方法,一样会同步等待SP写入完毕,等待时间过长时也会引起ANR。
-
分析一下SharedPreferences源码中apply()
SharedPreferencesImpl#apply(),主要是将记录的数据同步写到Map集合中,再开启子线程将数据写入磁盘;
SharedPreferencesImpl#enqueueDiskWrite(),会将runnable写入队列,在run方法中写数据到磁盘;QueuedWork#queue(),将runnable添加到sWork(LinkedList链表)中,通过handler发送处理队列消息MSG_RUN;
-
看一下ActivityThread源码中的handlePauseActivity()、handleStopActivity()
ActivityThread#handlePauseActivity()/handleStopActivity(),Activity在pause和stop时会调用对应方法
QueuedWork.waitToFinish(),是等待QueuedWork所有任务处理完的逻辑
QueuedWork#waitToFinish(),会通过handler查询MSG_RUN消息是否有,如果有则会waiting等待
-
handlePauseActivity()时会一直等待 apply() 方法将数据保存成功,否则会一直等待,从而阻塞主线程造成 ANR
2.MMKV的存储分析
MMKV不足之处
- 读取相对较慢:
- SP在加载时已经将value反序列化存在HashMap中,读取的时候索引到之后就能直接引用。而MMKV每次读取时都需要重新解码,除时间上的消耗之外,还需每次创建新的对象。
- 要引入so, 增加包体积:
- 引入MMKV需要增加的体积还是不少的。
- 文件只增不减:
- MMKV的扩容策略是比较激进的,且扩容之后不会主动trim size。
- 没有类型信息,不支持getAll:
- MMKV的存储用类似于Protobuf的编码方式,只存储key和value本身,没有存类型信息。因没有记录类型信息,MMKV无法自动反序列化,也就无法实现getAll接口
3.DataStore
DataStore介绍
- DataStore包含两种实现方式:
- Preferences DataStore仅使用键存储和访问值数据。不需要预定义,且不提供类型安全性。
- Proto DataStore将数据存储为自定义数据类型的实例。要求使用协议缓冲区(protobuf – PB协议)定义架构,但它提供类型安全性。
- DataStore初始化遇到的坑
- 不能将DataStore初始化代码写到Activity里面,否则重复进入Activity并使用Preferences DataStore时,会创建一个同名的.preferences_pb文件。
- SingleProcessDataStore#check(!activeFiles.contains(it)),该方法会检查如果判断到activeFiles里已经有该文件,直接抛异常。
- 思考一个问题
- 以事务方式处理更新数据,如何保证事务有四大特性(原子性、一致性、 隔离性、持久性)?
DataStore优缺点
- DataStore优势是异步Api
- DataStore 的主要优势之一是异步API,本身并未提供同步API调用,实际上可能不一定始终能将周围的代码更改为异步代码
- 使用阻塞式协程消除异步差异
- 使用 runBlocking() 从 DataStore 同步读取数据。runBlocking()会运行一个新的协程并阻塞当前线程直到内部逻辑完成,所以尽量避免在UI线程调用。
- 频繁使用阻塞式协程会有问题吗
- 要注意的一点是,不用在初始读取时调用runBlocking,会阻塞当前执行的线程,因为初始读取会有较多的IO操作,耗时较长。
- 推荐的做法则是先异步读取到内存后,后续有需要可直接从内存中拿,而非运行同步代码阻塞式获取。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END