聊一聊Android中的数据存储方案
我正在参加「启航计划」

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