本文共 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的映射关系的ArrayMapmSharedPrefsPaths = new ArrayMap<>(); } //根据name获取对应的文件File file = mSharedPrefsPaths.get(name); if (file == null) { //取不到就创建文件 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } //根据File创建SharedPreferences return getSharedPreferences(file, mode); }
重点关注一下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>缓存容器 中。
ContextImpl#getSharedPreferences()方法最后会调用重载方法getSharedPreferences(File file,int mode):
@Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { //得到用于缓存SP的Map容器,在ContextImpl以单例形式声明 final ArrayMapcache = 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 ArrayMapgetSharedPreferencesCacheLocked() { 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
总结一下:
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"); } //保存即将加载的数据容器 Mapmap = 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容器。此时的操作,都是针对的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()操作都是直接操作的缓存容器。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 MapmModified = 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进行提交,这也是最重要的!
每次.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 ListkeysModified = 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的性能有点不堪入目。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的太多任务,此时判断任务队列里的任务还未执行完,就会一直等待执行完成。
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 WeakHashMapmListeners = new WeakHashMap ();
在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; ListkeysModified = 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/