
概述
SharedPreferences是Android中的数据持久化技术中的一种. 它将Key-Value键值对储存在一个XMl文件中. 它比较适用储存小量的数据. 比如一些应用的配置信息.
使用
SharedPreferences的被设计成读写分离的模式, SharedPreferences用来读取数据, SharedPreferences.Editor则是用来写数据.
获取SharedPreferences
关于如何获取SharedPreferences, 可以通过下面这三种方法来获取:
1 2 3
| SharedPreferences sharedPreferences = Activity.getPreferences(MODE_PRIVATE); SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPreferences = context.getSharedPreferences("cr7", MODE_APPEND);
|
前面两种都是第三种的封装. 第三种的函数原型为: Context.getSharedPreferences(String name, int mode). 其中name为文件的名字, mode为操作模式. 操作模式可选有:
1 2 3 4 5
| public static final int MODE_PRIVATE = 0x0000; public static final int MODE_WORLD_READABLE = 0x0001; public static final int MODE_WORLD_WRITEABLE = 0x0002; public static final int MODE_APPEND = 0x8000; public static final int MODE_MULTI_PROCESS = 0x0004;
|
上面有些是由于安全性的问题被弃用的. 而MODE_MULTI_PROCESS用于多进程读写的模式, 但是不能保证可靠性, 有可能会出现脏读现象, 已被官方弃用.
SharedPreferences的文件储存在/data/data/shared_prefs/. Activity.getPreferences(MODE_PRIVATE)默认的文件名: 调用的Activity的名字, 比如在SpActivity中调用, 得到的文件名字为: SpActivity.xml. 至于调用PreferenceManager.getDefaultSharedPreferences(context)得到的文件名字为: 包名+ _preferences.xml.
获取完SharedPreferences后, 我们就可以调用其中的getXX来获取数据.
1
| sharedPreferences.getString("cr7", "");
|
获取SharedPreferences.Editor
获得SharedPreferences实例后, 我们可以调用它的editor()方法拿到Editor对象, 注意, 每次调用editor()方法都是新建一个Editor对象. 拿到对象后, 就可以进行写数据了.
1 2
| editor.putString("cr7", "good and best"); editor.apply();
|
要将数据写入文件中的话, 最后还得调用apply()或者commit()方法.
监听Key的对应的Value值的变化
1 2 3 4 5 6 7 8
| sharedPreferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Log.d(TAG, "onSharedPreferenceChanged: " + key); } }); sharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
|
如果我们需要监听Key对应的Value的变化的话, 可以调用sharedPreferences中的注册方法. 每当Key值对应的Value发生变化时, 都会回调这个接口. 但是不用的时候, 记得调用注销接口.
原理解析

上图为sharedPreferences的UML设计图, sharedPreferences和sharedPreferences.Editor只是一个接口, 对应的实现由SharedPreferencesImpl和SharedPreferencesImpl.EditorImpl来负责.
SharedPreferencesImpl被设计成单例的形式, 是由ContextImpl负责创建和维护. 在ContextImpl中有字段:
1
| private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
|
这个字段是负责维护SharedPreferences的. 并且为静态字段,证明同一进程中只有一个实例.
创建SharedPreferences
SharedPreferences的创建是由ContextImpl负责的, 对应实现方法为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public SharedPreferences getSharedPreferences(String name, int mode) { if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } } 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); }
|
结合注释: 首先根据传入的文件名, 先从缓存中拿, 如果不存在的话, 说明该文件还没有被创建, 因此会创建文件后添加进缓存中. 接着调用getSharedPreferences(file, mode)获取SharedPreferences实例.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public SharedPreferences getSharedPreferences(File file, int mode) { checkMode(mode); if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) { if (isCredentialProtectedStorage() && !getSystemService(StorageManager.class).isUserKeyUnlocked( UserHandle.myUserId()) && !isBuggy()) { throw new IllegalStateException("SharedPreferences in credential encrypted " + "storage are not available until after user is unlocked"); } } SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { sp.startReloadIfChangedUnexpectedly(); } return sp; }
|
在getSharedPreferences(File file, int mode)中的逻辑, 会先调用checkMode(int)来检查模式.
1 2 3 4 5 6 7 8 9 10
| private void checkMode(int mode) { if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) { if ((mode & MODE_WORLD_READABLE) != 0) { throw new SecurityException("MODE_WORLD_READABLE no longer supported"); } if ((mode & MODE_WORLD_WRITEABLE) != 0) { throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported"); } } }
|
在Android N后, 出于安全性的考虑, 开始不支持MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE.
检查完模式后, 接着从缓存中获取该文件对应的SharedPreferences, 如果存在的话, 返回. 不存在的话, 创建实例并添加进缓存.
经过上面的步骤就创建并缓存了SharedPreferences. 那么我们下面来看看SharedPreferences创建的时候内部都做了什么.
SharedPreferencesImpl
1 2 3 4 5 6 7 8
| SharedPreferencesImpl(File file, int mode) { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; mLoaded = false; mMap = null; startLoadFromDisk(); }
|
在SharedPreferencesImpl的构造方法中, 首先备份文件, 用于文件读写失败时的恢复. 最后调用startLoadFromDisk从硬盘中加载文件.
1 2 3 4 5 6 7 8 9 10
| private void startLoadFromDisk() { synchronized (mLock) { mLoaded = false; } new Thread("SharedPreferencesImpl-load") { public void run() { loadFromDisk(); } }.start(); }
|
先将加载标记mLoad设置为false表示文件还没有加载完. 然后开一条线程去加载文件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| private void loadFromDisk() { synchronized (mLock) { if (mLoaded) { return; } if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } if (mFile.exists() && !mFile.canRead()) { Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission"); } Map map = null; StructStat stat = null; try { stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16*1024); map = XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { } synchronized (mLock) { mLoaded = true; if (map != null) { mMap = map; mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } mLock.notifyAll(); } }
|
上面的方法主要做了两件事, 1: 从XML文件中读出数据, 并存放在Map中. 2: 将加载标记设置为true表示已经加载完成. 然后调用notifyAll()通知正在等待读写的线程.
利用SharedPreferences读数据
创建完SharedPreferences后, 我们就可以拿到它的实例来进行读数据, 下面通过其中的getString(String key, String defValue)来分析.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public String getString(String key, @Nullable String defValue) { synchronized (mLock) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } } private void awaitLoadedLocked() { if (!mLoaded) { BlockGuard.getThreadPolicy().onReadFromDisk(); } while (!mLoaded) { try { mLock.wait(); } catch (InterruptedException unused) { } } }
|
每次读数据时, 都会判断文件是否加载完, 没有的话, 会调用wait()挂起线程, 直到文件加载完才被唤醒.
前面说过文件的内容都是加载进了mMap中, 所以getXXX方法获取的数据都是从mMap中获取.
Editor写数据
前面分析过, SharedPreferences的读写是分离的. 要进行写数据的话, 我们需要拿到Editor对象, 这个对象可以通过SharedPreferences中的editor()方法拿到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public Editor edit() { synchronized (mLock) { awaitLoadedLocked(); } return new EditorImpl(); }
|
注意: 每次调用这个方法的方法都是新建一个实例, 所以在使用的时候,最好缓存起来, 避免多次调用生成多个对象.
下面也是通过Editor中的putString(String key, String value)为例子来分析:
1 2 3 4 5 6
| public Editor putString(String key, @Nullable String value) { synchronized (mLock) { mModified.put(key, value); return this; } }
|
写数据时, 并没有直接操作SharedPreferences中的mMap, 而是自己新建一个mModified的Map对象来记录修改的数据.
从上面可以看出, 调用putXXX方法, 数据只是存在内存中, 此时还没有写进磁盘. 需要调用commit()或者apply()方法.
提交数据commit()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public boolean commit() { long startTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } MemoryCommitResult mcr = commitToMemory(); SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null ); 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; }
|
commit()方法总体逻辑: 1: 将写的数据同步到SharedPreferences中的mMap中; 2: 将mMap写进磁盘. 3: 回到Key监听器.
commitToMemory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| private MemoryCommitResult commitToMemory() { long memoryStateGeneration; List<String> keysModified = null; Set<OnSharedPreferenceChangeListener> listeners = null; Map<String, Object> mapToWriteToDisk; synchronized (SharedPreferencesImpl.this.mLock) { if (mDiskWritesInFlight > 0) { mMap = new HashMap<String, Object>(mMap); } mapToWriteToDisk = mMap; mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList<String>(); listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (mLock) { boolean changesMade = false; if (mClear) { if (!mMap.isEmpty()) { changesMade = true; mMap.clear(); } mClear = false; } for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); if (v == this || v == null) { if (!mMap.containsKey(k)) { continue; } mMap.remove(k); } else { if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mMap.put(k, v); } changesMade = true; if (hasListeners) { keysModified.add(k); } } mModified.clear(); if (changesMade) { mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); }
|
首先判断mClear字段是否被设置, 是的话, 清除mMap中的数据. 接着将mModified的数据添加到mMap中.
enqueueDiskWrite
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final boolean isFromSyncCommit = (postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); }
|
由于传入的postWriteRunnable为null, isFromSyncCommit为true. 然后调用writeToFile(mcr, isFromSyncCommit)将数据写进磁盘.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
| private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) { long startTime = 0; long existsTime = 0; long backupExistsTime = 0; long outputStreamCreateTime = 0; long writeTime = 0; long fsyncTime = 0; long setPermTime = 0; long fstatTime = 0; long deleteTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } boolean fileExists = mFile.exists(); if (DEBUG) { existsTime = System.currentTimeMillis(); backupExistsTime = existsTime; } if (fileExists) { boolean needsWrite = false; if (mDiskStateGeneration < mcr.memoryStateGeneration) { if (isFromSyncCommit) { needsWrite = true; } else { synchronized (mLock) { if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) { needsWrite = true; } } } } if (!needsWrite) { mcr.setDiskWriteResult(false, true); return; } boolean backupFileExists = mBackupFile.exists(); if (DEBUG) { backupExistsTime = System.currentTimeMillis(); } if (!backupFileExists) { 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 { FileOutputStream str = createFileOutputStream(mFile); if (DEBUG) { outputStreamCreateTime = System.currentTimeMillis(); } if (str == null) { mcr.setDiskWriteResult(false, false); return; } 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_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { } if (DEBUG) { fstatTime = System.currentTimeMillis(); } 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(Long.valueOf(fsyncDuration).intValue()); 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); }
|
这个writeToFile方法是commit()和apply()公用的. 接下来说说commit()会走的流程: 首先将当前文件备份, 接着写入磁盘, 最后删除备份文件.
经过前面的分析, 我们可以看出, commit()方法是以同步的方式将数据写入磁盘.
提交数据apply
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public void apply() { final long startTime = System.currentTimeMillis(); final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } if (DEBUG && mcr.wasWritten) { Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration + " applied after " + (System.currentTimeMillis() - startTime) + " ms"); } } }; QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.removeFinisher(awaitCommit); } }; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); notifyListeners(mcr); }
|
apply()的逻辑跟commit()一样, 只不过, 写入磁盘的方式不一样, 下面我们只分析写入磁盘的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final boolean isFromSyncCommit = (postWriteRunnable == null); final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); }
|
由于传入的Runnable不为null, 所以isFromSyncCommit为false. 最后会调用QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit).
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void queue(Runnable work, boolean shouldDelay) { Handler handler = getHandler(); synchronized (sLock) { sWork.add(work); if (shouldDelay && sCanDelay) { handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } }
|
QueueWork封装了HandlerThread和Handler. 用来处理apply()的异步写入的请求.
在queue方法中, 发送了一条延迟消息. 延迟时间为100毫秒.
1 2 3 4 5 6 7 8 9 10 11 12 13
| private static class QueuedWorkHandler extends Handler { static final int MSG_RUN = 1; QueuedWorkHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { if (msg.what == MSG_RUN) { processPendingWork(); } } }
|
QueuedWorkHandler接受到消息时, 调用processPendingWork()处理异步写入请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| private static void processPendingWork() { long startTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } synchronized (sProcessingWork) { LinkedList<Runnable> work; synchronized (sLock) { work = (LinkedList<Runnable>) sWork.clone(); sWork.clear(); getHandler().removeMessages(QueuedWorkHandler.MSG_RUN); } if (work.size() > 0) { for (Runnable w : work) { w.run(); } if (DEBUG) { Log.d(LOG_TAG, "processing " + work.size() + " items took " + +(System.currentTimeMillis() - startTime) + " ms"); } } } }
|
在processPendingWork()中会循环调用Runnable中的run方法. 现在我们回到之前的Runnable
1 2 3 4 5 6 7 8 9 10 11 12 13
| final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
| private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) { long startTime = 0; long existsTime = 0; long backupExistsTime = 0; long outputStreamCreateTime = 0; long writeTime = 0; long fsyncTime = 0; long setPermTime = 0; long fstatTime = 0; long deleteTime = 0; if (DEBUG) { startTime = System.currentTimeMillis(); } boolean fileExists = mFile.exists(); if (DEBUG) { existsTime = System.currentTimeMillis(); backupExistsTime = existsTime; } if (fileExists) { boolean needsWrite = false; if (mDiskStateGeneration < mcr.memoryStateGeneration) { if (isFromSyncCommit) { needsWrite = true; } else { synchronized (mLock) { if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) { needsWrite = true; } } } } if (!needsWrite) { mcr.setDiskWriteResult(false, true); return; } boolean backupFileExists = mBackupFile.exists(); if (DEBUG) { backupExistsTime = System.currentTimeMillis(); } if (!backupFileExists) { 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 { FileOutputStream str = createFileOutputStream(mFile); if (DEBUG) { outputStreamCreateTime = System.currentTimeMillis(); } if (str == null) { mcr.setDiskWriteResult(false, false); return; } 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_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { } if (DEBUG) { fstatTime = System.currentTimeMillis(); } 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(Long.valueOf(fsyncDuration).intValue()); 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); }
|
apply()中将数据写入磁盘跟commit()是有区别的. 前者的写入方式是异步写入, 也就是在QueueWork所在的线程. 所以当进行多次提交时, 只会将最后一次提交的数据写入磁盘.
二级缓存

SharedPreferences内部对数据的储存是经过内存-硬盘这样的两级缓存. 每次commit或者apply的时候, 都会先写入内存, 接着再将内存写入硬盘.
commit()方法是以同步的方式提交到内存. 具体如下图:

两次commit的提交是串行化的, commit#2必须要等待commit#1写入磁盘后才能写.
与commit()方法不同. apply()是以异步的形式将数据写入磁盘, 也就是说: 写进内存是同步的, 但是内存写进磁盘是异步的, 当进行多次提交时, 后提交的会覆盖前面提交到内存的数据, 最后只有最后一次的提交才会被写进磁盘. 具体如下图:

总结
SharedPreferences适合储存一些小数据的情景. 如果储存的数据太大的话, 第一次加载时, 有可能会阻塞到主线程.
editor()每次调用都会生成一个Editor实例, 所以在封装的时候要注意, 最好缓存起来, 不然频繁调用的话, 有可能会出现内存抖动.
commit是在当前线程以同步的方法写入磁盘, 如果在主线程连续多次提交的话, 有可能阻塞主线程.
apply是先将数据同步写到内存, 然后将内存的数据异步写入磁盘, 并且只会写入最后一次提交的数据, 这样磁盘的读写次数就减少了, 所以apply会比commit高效.
SharedPreferences是不支持多进程的, 如果在多进程下读写的话, 可能出现脏读现象. 如果需要在多进程下读写数据的话, 可以借用ContentProvider来实现, 数据源设置为SharedPreferences即可.