概述
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
即可.