前言
SharePreferences是Android本地化存储key-value工具。
文件目录:/data/data/<package_name>/shared_prefs目录下文件名称name + .xml
mFile对应的SP本地文件;mBackupFile备份文件,写之前会把之前的文件存储为备份,如果写入失败,后续就继续读这个备份文件,写入成功就删除备份文件,它的名字在源文件后加上.bak后缀;mLoaded表示是否把本地文件加载到内存中了;mMap内存中存放的数据;最后执行startLoadFromDisk()从文件中加载数据。直接新建一个线程调用loadFromDisk()方法。判断备份文件是否存在,如果存在则表示上次写入失败了,直接使用备份文件的数据当成新的文件,然后加载。
通过XmlUtils#readMapXml()方法获取文件流中的xml,以Map格式返回,最后存储在mMap成员变量中,最后通过mLock.notifyAll()通知其他还在等待加载完成的方法,比如读取的时候就需要等待加载完成。
写入数据通过SharePreferences#edit()方法返回一个Editor对象,通过Editor进行写,然后调用commit()(同步,不推荐使用)或者apply()(异步,一般推荐使用)方法提交,最后写入到本地。和读取一样许哟啊等待文件加载完成,然后直接新建一个EditorImpl实例返回,EditorImpl是SharedPreferencesImpl中的内部类。
插入或者修改数据会直接把值放在mModified中,删除的时候直接把对应的key的value设置成EditorImpl自己,清空数据只是加一个mClear变量标识。
apply传递Runnable对象中会等待修改完成,没有完成的可以在QueueWork中查询对应的Runnable。
参数mDiskWritesInFlight变量,当它大于0的时候标识其他写入还没有完成。
遍历EditorImpl中修改的数据,如果value值为空或者EditorImpl对象时就表示要删除这条数据,反之就更新或者插入数据。最后得到更新后的数据mapToWriteToDisk,也就是mMap内存中的数据,最后会把更新后等待写入的本地文件中的数据添加到MemoryCommitResult中供后续吸入到文件中使用。
writeToDiskRunnable写入磁盘,如果是异步调用或者如果有其他提交还没有修改完(异步通过postWriteRunnable判断,是否有其他的修改还没有完成通过mDiskWritesInFlight判断),就会用其他线程异步执行。反之就在当前线程直接执行。执行写文件方法writeToFile()。通过XmlUtils.writeMapXml()方法把更新后的数据写入到文件中,更新成功后会把结果写入,然后删除备份文件,如果更新失败会删除更新失败的文件。
总结
每个不同SP的name都对应一个不同的本地文件,每次使用时都会检查是否加载到内存中,如果没有加载就会本地区加载然后保存到内存中(没有回收机制),以后再使用就不用再加载。如果要执行读写操作,也不许阻塞等待加载完成。读取操作的性能比较好,是直接读取内存中的数据。写操作需要先更新内存中的数据,然后把更新的数据全部重新吸入本地文件。
- 同一个SP文件存放的数据不宜过大,过大会导致初次加载慢,写入的数据也慢,占用的内存也多,一个SP文件存放的数据过大可以考虑根据业务逻辑拆分成多个SP文件。如果存放的单条key-value过大,可以考虑写一个本地文件缓存,如果非常多条的key-value也可以考虑使用数据库。
- 获取SP对象是不会阻塞线程的,但是使用它时可能会阻塞,因为需要等待本地文件加载完成,所以使用时最好能够再后台线程操作。
- 写入commit是同步,apply是异步的,他们都可能造成卡顿,甚至ANR,虽然apply是异步的,某些组件的生命周期会检查有没有完成SP的apply任务,长时间没有完成也会ANR。comit方法建议后台线程中执行(子线程)。