你真的懂SharedPreferences么

1.SharedPreferences首次创建,实际类型是SharedPreferencesImpl,系统会将每个 SharedPreferences 文件对应的操作对象(实际为 SharedPreferencesImpl)进行缓存,SharedPreferencesImpl和sp文件路径file映射关系存放在Arraymap中,api28以后,之前是HashMap

2.SharedPreferencesImpl创建过程中,会开启异步线程加载对应 name 的 XML 文件内容到 Map 容器中,这里应用了锁,当文件还没读取完成,如果进行读取操作,将会进行等待,所以如果文件内容较大,会出现耗时

3.一系列 getXxx() 操作,SharedPreferences 的数据都保存在 Map 容器中,此时就是根据 Key 到该 Map 容器中查找对应的数据即可

4.putXxx() 操作中,每次通过 SharedPreferences.edit() 都会创建一个新的 EditorImpl 对象,当修改或者添加数据时会将其添加到 EditorImpl 的 mModifiled 容器中,通过 commit 或 apply 提交后会比较 mModifiled 与 mMap 容器数据,修正(commitToMemory 方法作用) mMap 中最后一次数据提交后写入文件。

5.commit 或和apply不同在于,commit 发生在当前线程,apply 发生在工作线程,但是 apply也可能造成anr,因为apply 提交的任务,都会被加入到工作线程 QueueWork 中,该任务队列以串行方式执行(只有一个工作线程),如下代码:

@Override
public void handlePauseActivity(IBinder token, boolean show, int configChanges, PendingTransactionActions pendingactions, boolean finalStateRequest, String reason){
    
    
    //... 省略
    if(!r.isPreHoneycomb()){
    
    
      //这里检查,异步提交的SharedPreferences任务是否已经完成
      //否则一直等到执行完成
      QueuedWork.waitToFinish();
    }
    //... 省略
 }

6.Context.MODE_MULTI_PROCESS 的加载模式,当应用指定的 targetSdkVersion 小于 API Level 11 时,则重新从文件中加载一遍数据到内存中,并没达到跨进程的效果

7.SharedPreferences 的写入操作,首先是将源文件备份:mFile.renameTo(mBackupFile) 再写入所有数据,只有写入成功,并且通过 sync 完成落盘后,才会将 Backup(.bak) 文件删除。如果写入过程中进程被杀,或者关机等非正常情况发生。进程再次启动后如果发现该 SharedPreferences 存在 Backup 文件,就将 Backup 文件重名为源文件,原本未完成写入的文件就直接丢弃,这样最多也就是未完成写入的数据丢失,它能保证最后一次落盘(真正落盘)成功后的数据。也正式这个 BackUp 机制,导致多进程可能会丢失新写入的数据。但也不是只有多进程场景才会发生数据丢失的情况。

8.注意 onSharedPreferenceChanged() 的回调时机在 commit() 和 apply() 有所区别:

(1)使用 commit() 提交时,onSharedPreferenceChanged() 回调时机是在数据落盘完成之后(不代表一定成功,有可能发生异常)

(2)使用 apply() 提交时,onSharedPreferenceChanged() 回调时机是在完成数据内存替换之后,既 mModified 中数据提交到 mMap 完成之后(前者是对我们一系列putXxx() 或 remove() 做保存,后者是写入文件时使用)。

9.SharedPreferences的优化可以引申出来mmkv的实现原理
MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存区域的内容,这个过程称为内存映射(memory mapping)。

扫描二维码关注公众号,回复: 12569678 查看本文章

MMAP优势
1.MMAP对文件的读写操作只需要从磁盘到用户主存的一次数据拷贝过程
2.MMAP使用逻辑内存对磁盘文件进行映射,操作内存就相当于操作文件,不需要开启线程,操作MMAP的速度和操作内存的速度一样快;
3.MMAP提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统如内存不足、进程退出等时候负责将内存回写到文件,不必担心 crash 导致数据丢失。

MMKV正式基于protobuf协议进行数据存储,存储方式为增量更新,也就是不需要每次修改数据都要重新将所有数据写入文件了。

标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力:将增量 kv 对象序列化后,直接 append 到内存末尾;这样同一个 key 会有新旧若干份数据,最新的数据在最后;那么只需在程序启动第一次打开 mmkv 时,不断用后读入的 value 替换之前的值,就可以保证数据是最新有效的。

以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。

猜你喜欢

转载自blog.csdn.net/u012124438/article/details/113773596