博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
解析SharedPreferences
阅读量:321 次
发布时间:2019-03-03

本文共 26909 字,大约阅读时间需要 89 分钟。

文章目录

简介

sp作为轻量级存储,数据最终都是以xml的形式进行存储的。

在这里插入图片描述

一、文件保存

在这里插入图片描述

SharedPreferences config = getSharedPreferences("config", Context.MODE_PRIVATE);        String value = config.getString("key", "value");

getSharedPreferences会得到SharedPreferences对象,其实调用的还是ContextImpl#getSharedPreferences()方法:

@Override    public SharedPreferences getSharedPreferences(String name, int mode) {
//mBase的类型就是ContextImpl return mBase.getSharedPreferences(name, mode); }

ContextImpl#getSharedPreferences()

@Override    public SharedPreferences getSharedPreferences(String name, int mode) {
// 目标版本<19且name为null,就把文件名直接设置为null:null.xml if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null"; } } File file; //synchronized持有对象锁, synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
//创建维护文件名和文件File的映射关系的ArrayMap
mSharedPrefsPaths = new ArrayMap<>(); } //根据name获取对应的文件File file = mSharedPrefsPaths.get(name); if (file == null) {
//取不到就创建文件 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } //根据File创建SharedPreferences return getSharedPreferences(file, mode); }
  • ①这里有一个判断,如果目标版本 < 19 且name传递为null,就直接将文件名设置为null:null.xml。
  • ②synchronized持有对象锁,创建维护文件名和文件File的映射关系的ArrayMap<name,file>—mSharedPrefsPaths;通过文件名获取对应的文件File,如果取不到就创建,并将其put进mSharedPrefsPaths中。
  • ③最后根据File创建SharedPreferences

重点关注一下SharedPreferences的创建文件的过程:

//根据文件名创建File对象	@Override    public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml"); } private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
//创建SharedPreferences文件保存目录,得到 //的是:/data/data/packageName/ mPreferencesDir = new File(getDataDir(), "shared_prefs"); } //确保应用的私有文件目录已经存在 return ensurePrivateDirExists(mPreferencesDir); } }

由此可知SP文件的存储目录是在shared_prefs下的。得到路径、文件名后就调用makeFilename()方法创建File:

private File makeFilename(File base, String name) {
//检查文件名是否合法,否则抛异常 if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name); } throw new IllegalArgumentException( "File " + name + " contains a path separator"); }

这里会对文件名进行判断,不能是“/config”等路径形式,否则会抛出异常。

总结一下:通过文件名name创建对应的File对象,并将其put到 ContextImpl的ArrayMap<name,file>缓存容器 中。

二、SP创建

在这里插入图片描述

ContextImpl#getSharedPreferences()方法最后会调用重载方法getSharedPreferences(File file,int mode):

@Override    public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp; synchronized (ContextImpl.class) {
//得到用于缓存SP的Map容器,在ContextImpl以单例形式声明 final ArrayMap
cache = getSharedPreferencesCacheLocked(); //单例容器 sp = cache.get(file); if (sp == null) {
checkMode(mode); //7.0之后文件加密相关 if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage() && !getSystemService(UserManager.class) .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
throw new IllegalStateException("SharedPreferences in credential encrypted " + "storage are not available until after user is unlocked"); } } //创建SharedPreferencesImpl,SharedPreferences只是定义了基本API的接口,真实实现类是SharedPreferencesImpl sp = new SharedPreferencesImpl(file, mode); //保存到ArrayMap
缓存容器中。 cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// MODE_MULTI_PROCESS的加载策略 sp.startReloadIfChangedUnexpectedly(); } return sp; }

先看一下如何获取到缓存SP的Map容器的,在getSharedPreferencesCacheLocked()方法:

@GuardedBy("ContextImpl.class")    private ArrayMap
getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
// 创建缓存SharedPreferencesImpl的ArrayMap容器 sSharedPrefsCache = new ArrayMap<>(); } final String packageName = getPackageName(); //根据应用包名,获取ArrayMap ArrayMap
packagePrefs = sSharedPrefsCache.get(packageName); if (packagePrefs == null) {
packagePrefs = new ArrayMap<>(); //根据包名,保存所有SharedPreferencesImpl sSharedPrefsCache.put(packageName, packagePrefs); } return packagePrefs; }

它会创建用于缓存SharedPreferencesImpl实例的ArrayMap缓存容器,然后根据包名,将所有的SharedPreferencesImpl实例都保存起来。最后返回这个SharedPreferencesImpl集合:ArrayMap<File, SharedPreferencesImpl> packagePrefs

总结一下:

  • SharedPreferences只是定义了几个API的接口,实际实现类还是SharedPreferencesImpl。所有的SP操作都是依赖SharedPreferencesImpl来完成的。
  • 每个SharedPreferencesImpl都会被缓存到ArrayMap容器中,以后再获取它,就都是从缓存容器中取
  • SP提供了加载策略MODE_MULTI_PROCESS,当目标版本 < 11就重新从文件中加载一遍数据到内存中。于是,SP不能跨进程通信

三、SP数据加载

在这里插入图片描述

SharedPreferencesImpl构造方法:

SharedPreferencesImpl(File file, int mode) {
//SharedPreferences保存文件 mFile = file; //SP的原始文件的备份文件 mBackupFile = makeBackupFile(file); //加载模式 mMode = mode; //标志位,是否正在加载 mLoaded = false; mMap = null; mThrowable = null; //开启线程,加载对应文件数据到Map容器中 startLoadFromDisk(); }

看SP如何开启线程,将对应文件中的数据加载到Map容器中:

private void startLoadFromDisk() {
synchronized (mLock) {
//加载标志位,每次需要加载时,需将其置为false //加载完后在置为true mLoaded = false; } new Thread("SharedPreferencesImpl-load") {
public void run() {
//开启独立线程加载数据 loadFromDisk(); } }.start(); }

这个标志位mLoaded在SharedPreferencesImpl的构造方法中声明,每次需要加载时需要先将其置False,加载完成后再置为True。mLoaded在多线程访问等待方面很重要,如果在UI线程操作SP数据,可能会导致UI线程等待。

随后调用startLoadFromDisk()方法,在其内部开启独立线程,通过loadFromDisk()方法异步加载数据

private void loadFromDisk() {
synchronized (mLock) {
//这个判断用来防止重复加载 if (mLoaded) {
return; } //如果备份文件存在 if (mBackupFile.exists()) {
//删除源文件 mFile.delete(); //将备份文件重命名为源文件 mBackupFile.renameTo(mFile); } } // Debugging if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission"); } //保存即将加载的数据容器 Map
map = null; StructStat stat = null; //加载过程中是否发生过异常 Throwable thrown = null; try {
stat = Os.stat(mFile.getPath()); if (mFile.canRead()) {
BufferedInputStream str = null; try {
//从文件中读取内容 str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024); //SP的文件操作都封装在XmlUtils中,得到map实例 map = (Map
) XmlUtils.readMapXml(str); } catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally {
IoUtils.closeQuietly(str); } } } catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by // ignoring. } catch (Throwable t) {
thrown = t; } synchronized (mLock) {
//表示SP文件中的数据已经从磁盘加载到内存Map中了 mLoaded = true; mThrowable = thrown; // It's important that we always signal waiters, even if we'll make // them fail with an exception. The try-finally is pretty wide, but // better safe than sorry. try {
//加载过程中未发生异常 if (thrown == null) {
//加载成功,直接赋值 if (map != null) {
mMap = map; mStatTimestamp = stat.st_mtim; mStatSize = stat.st_size; } else {
mMap = new HashMap<>(); } } // In case of a thrown exception, we retain the old map. That allows // any open editors to commit and store updates. } catch (Throwable t) {
mThrowable = t; } finally {
mLock.notifyAll(); } } }

开启独立线程加载数据的方法中,先对mLoaded进行判断,这样可以有效的避免重复加载的情况发生。然后判断备份文件是否存在,true则删除源文件,将备份文件重命名为源文件。通过BufferedInputStream读取文件内容,并借助XmlUtils将其转化为map实例(XmlUtils封装了对Xml文件的读写操作,并将数据封装进Map容器返回)。随后检查是否加载成功、是否加载异常,若无异常就直接赋值操作(mMap = map—>private Map<String,Object> mMap)。

总结一下:SharedPreferencesImpl初始化,会开启独立线程异步加载对应name的xml文件到内存中,并保存到Map容器中。后续的getxxx()操作都是直接操作的Map容器。

四、getxxx()操作

在这里插入图片描述

此时的操作,都是针对的Map容器—mMap中的数据。即根据传入的Key,到mMap中获取对应的Value。

@Override    @Nullable    public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
//重点!!! awaitLoadedLocked(); //直接从Map容器中获取Value String v = (String)mMap.get(key); return v != null ? v : defValue; } }

重点关注awaitLoadedLocked()方法,根据mLoaded判断是否加载完成,否则调用方线程等待:

@GuardedBy("mLock")    private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this // thread, since the real read will be in a different // thread and otherwise ignored by StrictMode. BlockGuard.getThreadPolicy().onReadFromDisk(); } //根据mLoaded判断,若为加载完成,调用方线程进入等待 while (!mLoaded) {
try {
mLock.wait(); } catch (InterruptedException unused) {
} } if (mThrowable != null) {
throw new IllegalStateException(mThrowable); } }

SP文件加载时,要先将mLoaded置为false,加载成功后在置为true。如果加载过程比较耗时,此时如果在UI线程操作SP数据,那么该线程就进入wait状态了。

总结一下:mLoaded的作用就是判断SP数据是否从Disk完全加载到Memory并保存到Map容器中了。如果未完成,就让调用方线程wait。待数据加载完成后,再让所有的等待线程notifyAll唤醒。由于SP存储的数据都会在内存中保留一份,故所有的getXXX()操作都是直接操作的缓存容器。

五、putXXX()操作

在这里插入图片描述

SharedPreferences config = getSharedPreferences("config", Context.MODE_PRIVATE);        SharedPreferences.Editor editor = config.edit().putString("key", "value");        editor.apply();

put操作首先要经过edit()方法获取到Editor对象:

SharedPreferencesImpl#edit()  @Override    public Editor edit() {
//mLoaded在发挥作用 synchronized (mLock) {
awaitLoadedLocked(); } //实际返回的EditorImpl return new EditorImpl(); }

返回的EditorImpl实例:

public final class EditorImpl implements Editor {
private final Object mEditorLock = new Object(); //保存修改数据的容器 //一系列添加、修改、删除数据都保存在该临时容器中 @GuardedBy("mEditorLock") private final Map
mModified = new HashMap<>(); //标志当前是否是清除操作 @GuardedBy("mEditorLock") private boolean mClear = false; //添加String类型数据 @Override public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
//添加到临时Map容器中 mModified.put(key, value); return this; } }

和SharedPreferences类似,Editor只是一个接口,定义了一些API方法,真正实现实在EditorImpl中。修改数据后,要commit或apply进行提交,这也是最重要的!

5.1 commit &&apply

在这里插入图片描述

每次.edit()都会生成一个新的EditorImpl对象,故应该尽量批量提交,避免单次提交。

@Override        public boolean commit() {
long startTime = 0; if (DEBUG) {
startTime = System.currentTimeMillis(); } //将临时容器mModified中的数据提交到SharedPreferencesImpl的Map容器中,为了方便写入文件 MemoryCommitResult mcr = commitToMemory(); //将写入文件的结果mcr作为参数,根据apply/commit决定任务run in工作线程/当前线程 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try {
mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) {
return false; } finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + " committed after " + (System.currentTimeMillis() - startTime) + " ms"); } } //通知外部监听 notifyListeners(mcr); return mcr.writeToDiskResult; }

commitToMemory()方法将临时容器mModified中的数据提交到SharedPreferencesImpl的mMap中:

private MemoryCommitResult commitToMemory() {
long memoryStateGeneration; //保存发生变化的key List
keysModified = null; //外部监听器 Set
listeners = null; Map
mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
//数据拷贝 mMap = new HashMap
(mMap); } //将mMap赋值给局部变量,后续for循环 mapToWriteToDisk = mMap; mDiskWritesInFlight++; //可以监听SP数据提交完成 boolean hasListeners = mListeners.size() > 0; if (hasListeners) {
keysModified = new ArrayList
(); //收集回调通知 listeners = new HashSet
(mListeners.keySet()); } synchronized (mEditorLock) { //确保是否真的发生变化,避免无谓的I/O boolean changesMade = false; //如果是clear操作,就直接清空数据 if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); } mClear = false; } //遍历修改后的临时容器mModified for (Map.Entry
e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); // "this" is the magic value for a removal mutation. In addition, // setting a value to "null" for a given key is specified to be // equivalent to calling remove on that key. if (v == this || v == null) { //value==null,mMap不包含该Key,可直接跳过 if (!mapToWriteToDisk.containsKey(k)) { continue; } mapToWriteToDisk.remove(k); } else { //mMap中包含该Key,Value就直接修改为最新提交的 if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); //value没变,就跳过。主要考虑changesMade标志位,确保数据是否真的发生了变化 if (existingValue != null && existingValue.equals(v)) { continue; } } //直接添加新的Key:Value mapToWriteToDisk.put(k, v); } //for循环中,数据若发生变化,changesMade就置为true。表示当前数据发生了变化 changesMade = true; if (hasListeners) { keysModified.add(k); } } //清空临时容器mModified mModified.clear(); if (changesMade) { mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }

在getXXX()、putXXX()过程中,这些操作都会添加到临时容器mModified中,mModified保存着当前的改变。遍历mModified,并与数据容器mMap做比较,就将修改提交到mMap中。mMap中保存了最新的修改Value值后,最后清空临时容器mModified。

然后commit方法又回去调用enqueueDiskWrite()方法,负责执行写入文件的任务,就是将最后一次commitToMemory的mMap数据写回到文件:

private void enqueueDiskWrite(final MemoryCommitResult mcr,                                  final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null); //执行写入文件的Runnable任务 //这里也区分commit、apply //apply会将任务扔到线程池,异步执行 final Runnable writeToDiskRunnable = new Runnable() {
@Override public void run() {
synchronized (mWritingToDiskLock) {
//写入到文件 writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) {
mDiskWritesInFlight--; } if (postWriteRunnable != null) {
postWriteRunnable.run(); } } }; //commit时,会在当前线程执行run方法 if (isFromSyncCommit) {
boolean wasEmpty = false; synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) {
//commit操作,直接在当前线程中执行 writeToDiskRunnable.run(); return; } } //apply操作,把任务扔到线程池中排队执行 QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); }

如果是commit,就直接在当前线程执行 writeToDiskRunnable.run()。如果是apply,就将任务添加到线程池中排队执行。

不管是commit还是apply,就算只改动一个条目,都会把mMap中的所有数据全部写入到文件。而且就算是多次写入同一个文件,SP也没有合而为一的意思。因此,SP的性能有点不堪入目。

5.2 apply

apply的异步提交,也不一定就是安全的。这里联系到Android四大组件的生命周期管理,ActivityThread负责调度。在它的onPause的回调中:

@Override    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,            int configChanges, PendingTransactionActions pendingActions, String reason) {
// Make sure any pending writes are now committed. if (r.isPreHoneycomb()) {
//检查异步提交的SP任务是否已完成,否则就等待直到完成 QueuedWork.waitToFinish(); } }

apply提交的任务都会被扔到线程池里排队执行(通过HandlerThread串行),如果apply的太多任务,此时判断任务队列里的任务还未执行完,就会一直等待执行完成。

5.3 SP监控

SP提供了OnSharedPreferencesChangeListener监听数据发生改变:

public interface OnSharedPreferenceChangeListener {
void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key); }

在notifyListeners()方法中调用:

private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || mcr.keysModified == null || mcr.keysModified.size() == 0) {
return; } if (Looper.myLooper() == Looper.getMainLooper()) {
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i); for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key); } } } } else {
// Run this function on the main thread. ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr)); } }

使用commit,监听回调的时机在数据落盘完成后。不一定成功,有可能会发生异常。

使用apply,是在完成数据内存替换之后,即临时容器mModified中的数据提交到容器mMap中之后。
系统采用弱引用在WeakHashMap中保存OnSharedPreferencesChangeListener对象:

@GuardedBy("mLock")    private final WeakHashMap
mListeners = new WeakHashMap
();

六、SP落盘机制

在这里插入图片描述

在SharedPreferencesImpl的构造方法中创建SP文件的备份文件:

//SP的原始文件的备份文件 mBackupFile = makeBackupFile(file);

SP的落盘机制,全都依赖于此。

static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak"); }

mBackupFile就是源文件的备份文件:…/sp文件名.xml.bak。不管是commit还是apply最终都会调用writeToFile()方法,即将我们一系列的remove、putXXX()操作后的数据落地磁盘。

@GuardedBy("mWritingToDiskLock")    private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
...... //判断源文件是否存在 boolean fileExists = mFile.exists(); ...... if (fileExists) {
boolean needsWrite = false; if (mDiskStateGeneration < mcr.memoryStateGeneration) {
//needsWrite判断要提交的数据是否真的发生了变化 if (isFromSyncCommit) {
needsWrite = true; } else {
synchronized (mLock) {
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true; } } } } //如果提交的数据没变化,就return,避免无谓的I/O操作 if (!needsWrite) {
mcr.setDiskWriteResult(false, true); return; } //判断备份文件是否存在 boolean backupFileExists = mBackupFile.exists(); if (DEBUG) {
backupExistsTime = System.currentTimeMillis(); } //备份文件不存在,落地新内容时,就将源文件备份 if (!backupFileExists) {
//源文件备份错误,直接return if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); mcr.setDiskWriteResult(false, false); return; } } else {
//备份文件存在的话,就删除源文件 mFile.delete(); } } try {
//创建mFile的输入流 FileOutputStream str = createFileOutputStream(mFile); if (DEBUG) {
outputStreamCreateTime = System.currentTimeMillis(); } if (str == null) {
mcr.setDiskWriteResult(false, false); return; } //真正写入磁盘的操作,发生在XmlUtils中 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); writeTime = System.currentTimeMillis(); //强制落盘 FileUtils.sync(str); fsyncTime = System.currentTimeMillis(); str.close(); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); if (DEBUG) {
setPermTime = System.currentTimeMillis(); } try {
final StructStat stat = Os.stat(mFile.getPath()); synchronized (mLock) {
mStatTimestamp = stat.st_mtim; //计算文件大小 mStatSize = stat.st_size; } } catch (ErrnoException e) {
// Do nothing } if (DEBUG) {
fstatTime = System.currentTimeMillis(); } // Writing was successful, delete the backup file if there is one. //写入成功,删除备份文件 mBackupFile.delete(); if (DEBUG) {
deleteTime = System.currentTimeMillis(); } mDiskStateGeneration = mcr.memoryStateGeneration; mcr.setDiskWriteResult(true, true); if (DEBUG) {
Log.d(TAG, "write: " + (existsTime - startTime) + "/" + (backupExistsTime - startTime) + "/" + (outputStreamCreateTime - startTime) + "/" + (writeTime - startTime) + "/" + (fsyncTime - startTime) + "/" + (setPermTime - startTime) + "/" + (fstatTime - startTime) + "/" + (deleteTime - startTime)); } long fsyncDuration = fsyncTime - writeTime; mSyncTimes.add((int) fsyncDuration); mNumSync++; if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": "); } //写入成功,返回 return; } catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e); } catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e); } // 写入过程发生异常,直接删除源文件 if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile); } } mcr.setDiskWriteResult(false, false); }

首先判断源文件是否存在,若存在,就通过needsWrite判断要提交的数据是否真的发生了变化。如果提交的数据没变化,就return,避免无谓的I/O操作。接着判断备份文件是否存在:① 备份文件不存在,落地新内容时,就将源文件备份(源文件备份错误,直接return) ② 备份文件存在的话,就删除源文件。

真正的写入Disk的操作发生在XmlUtils中(XmlUtils.writeMapXml),并强制落盘(FileUtils.sync(str))。写入成功,就删除备份文件,并返回。写入异常,就删除源文件。

强调一点,系统把真正写入数据的操作都封装在XmlUtils中,并强制sync落地磁盘(FileUtils.sync(str)),这个强制落盘机制也能保证数据不丢失。

关于对提交的数据是否真的发生了变化的判断,是在commitToMemory()方法中:

private MemoryCommitResult commitToMemory() {
long memoryStateGeneration; List
keysModified = null; Set
listeners = null; Map
mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) {
// We optimistically don't make a deep copy until // a memory commit comes in when we're already // writing to disk. if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently // in-flight write owns it. Clone it before // modifying it. // noinspection unchecked mMap = new HashMap
(mMap); } mapToWriteToDisk = mMap; mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) {
keysModified = new ArrayList
(); listeners = new HashSet
(mListeners.keySet()); } synchronized (mEditorLock) { boolean changesMade = false; if (mClear) { if (!mapToWriteToDisk.isEmpty()) { changesMade = true; mapToWriteToDisk.clear(); } mClear = false; } for (Map.Entry
e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); // "this" is the magic value for a removal mutation. In addition, // setting a value to "null" for a given key is specified to be // equivalent to calling remove on that key. if (v == this || v == null) { if (!mapToWriteToDisk.containsKey(k)) { continue; } mapToWriteToDisk.remove(k); } else { if (mapToWriteToDisk.containsKey(k)) { Object existingValue = mapToWriteToDisk.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mapToWriteToDisk.put(k, v); } //在for循环中,若数据发生变化,就将changesMade置为true changesMade = true; if (hasListeners) { keysModified.add(k); } } mModified.clear(); if (changesMade) { //将当前状态+1,writeToDisk()落地磁盘时,判断新状态是否 > 当前状态 mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }

上面提到了,一系列操作的数据发生在EditorImpl的临时容器mModified(Map)中,要拿着它和SharedPreferencesImpl的容器mMap作比较,来修正最后一次提交的mMap中的数据。如果数据发生了改变,

七、优化

可以在Application中重写getSharedPreferences方法来替换系统默认实现,但这都不能从根本上解决问题。还是选用MMKV吧!

public class MyApplication extends Application {
@Override public SharedPreferences getSharedPreferences(String name, int mode) {
return SharedPreferencesImpl.getSharedPreferences(name,mode); }}

总结

SP的写入操作,先把源文件备份:mFile.renameTo(mBackupFile),再写入所有数据。只有写入成功,且通过sync强制落盘后,才会将备份文件 ".bak"删除。如果写入过程中发生异常,就丢弃这份源文件。虽然丢失了这一部分数据,但是它能保证最后一次落盘的成功后的数据。由于这个BackUp机制,所以多线程的情况下,可能会丢失新写入的数据。

转载地址:http://fbbq.baihongyu.com/

你可能感兴趣的文章