聊一聊Android中的数据存储方案

ea6387ef0345515755e13bb711bb3543--809802570.jpg
我正在参加「掘金·启航计划」

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操作,耗时较长。
    • 推荐的做法则是先异步读取到内存后,后续有需要可直接从内存中拿,而非运行同步代码阻塞式获取。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY1m01U3' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片