MMKV、SharedPreference 和 DataStore
2.2 MMKV
为啥有用户层 和 内核层
主要是为了安全,如果没有内核层,一个程序把系统给弄挂了,其他的也都会不能用
2.2.1 首先说一下 SharedPreference 原理
getSharedPreferences的时候其实就是先从缓存中拿 这个xml的File,如果缓存没有,直接去new一个File,并且读取。然后通过传统的I/O 去读取xml,利用xml解析器解析这个xml,存到Map中
- 读数据:读取的时候从map中读的,
- 写数据:commit 是同步的写入,先写入内存,然后全量写入到xml中 ,apply 是异步的写入xml,通过一个QueuedWork写的
- 不丢全量数据(说的是全量数据,只会丢一次的数据,比如写的时候,啥进程。)
他的缺点有很多
- FileoutStream ,读取xml传统I/O,比如写,先从用户内存,写到内核空间,然后再写到为文件中,相当于两次copy,SP第一次加载数据时需要全量加载,当数据量大时可能会阻塞UI线程造成卡顿
- 序列化 用的xml,使用DOM解析,
- 不能差量更新,只能更新全部的xml内容
- 拿不到返回结果,虽然commit可以,但是commit会阻塞线程,apply 拿不到
- 虽然apply 是开辟了线程,但是没有返回值
- sp不能跨进程,我记得7.0以前是可以的,7.0之后mode必须是priate了
- 容易造成ANR字节的公众号,commit 需要等 xml读入完毕, apply的问题,apply 会放入一个QueuedWork里面取执行,开始执行会加锁,执行完毕会释放锁,但是Activity onStop 以及 Service 处理 onStop,onStartCommand 时,执行 QueuedWork.waitToFinish() 等待所有的等待锁释放,头条的做法是,hook系统ActivityThread的H的Handler,在这个Handler里面加了个 CallBcak ,Handler 的 dispatchMessage 中会先处理 callback,可以在这里监听Activity的onStop的时候清除这个QueuedWork
java
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
2.2.2 MMKV(Memory Mapping Key Value)优点
- mmap,是Linux 为咱们提供的方式,同时还有sendFile,数据拷贝相当于拷贝了一次,当写的时候,从用户空间到内核空间,然而内核空间利用mmap映射到文件,相当于直接操作文件的,相当于直接写内存,MMAP使用逻辑内存对磁盘文件进行映射,App只管往里面写数据,由操作系统负责将内存,不必担心crash 导致数据丢失
- 不需要开辟线程,操作内存就相当于操作文件,不需要开启线程,操作MMAP的速度和操作内存的速度一样快
- 效率更高。MMKV 使用protobuf进行序列化和反序列化,是二进制数据,不容易阅读,protobuf是可变编码(和H264的哥伦布差不多),其他的是定长编码,定长编码:比如 int 占 4个字节,你是1也占4个字节,但是protobuf 只是占用1位就可以了,一个字节8位,他是通过 一些简单的 & 做到的。然后做移动。
- 可以支持增量更新,只是在protobuf追加在数据的最后面,当数量达到申请的大小时候,开始去除重复的key,扩容的时候,也是double现在的size 跟 Hashmap一样,会重新计算,看是否能撑得下,然后再固定大小,也是很耗时的。
- 多进程
2.2.3 mmmap 缺点
- 因为 MMap 需要提供一度长度的内存块,其映射区的长度默认是一页,即 4kb,当存储的文件内容较少时可能会造成空间的浪费
- 没有提供注册value变化监听的,(自己封装的时候,做了一层)
- 增量更新,并没有判断value 是否相同就直接追加(自己封装的时候,做了一层)23-11-20号已经修改,因为扩容的原因,所以网上写的存大字符串会比其他的慢的原因
- clearAll的时候,会恢复到4k,(23-11-20号,保持文件磁盘空间不变)
- 没有像Hashmap一样可以初始化大小,默认就是4kb(23-11-20号已经增加)
- 丢数组,断电关机------磁盘里的文件就会以这种写了一半的、不完整的形式被保留,此时就会损坏(自己做了恢复。已启动的时候,同时存入DataStore中一个数据,如果拿到不一样,就证明丢失了,此时同步数据)
2.3 google新出来的 DataStore
DataStore 是 Android Jetpack 的一部分。Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象,官方建议如果当前在使用 SharedPreferences 存储数据,请考虑迁移到 DataStore
- Preferences DataStore 以键值对的形式存储在本地和 SharedPreferences 类似,此实现不需要预定义的架构,也不确保类型安全。Preferences DataStore 只支持 Int , Long , Boolean , Float , String 键值对数据,适合存储简单、小型的数据,并且不支持局部更新,如果修改了其中一个值,整个文件内容将会被重新序列化。
- Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。Proto DataStore 使用了二进制编码压缩,体积更小,速度比 XML 更快
主要优点
- 内部使用kotlin协程,避免阻塞线程
- 可以使用 protoccol buffers 存储,更小
- 可以在编译期间提醒类型错误,sp 直接getInt ,如果里面是String,就会报错