SharedPreferences能夠說是Android中最經常使用的一種存數據到文件的方式。他的數據是以鍵值對的方式存儲在 ~/data/data/包名/shared_prefs
這個文件夾中的。java
這個存儲框架是很是輕量級的,若是咱們須要存一些小數據或者是一個小型的可序列化的Bean實體類的,使用SharedPreferences是最明智的選擇。android
在使用SharedPreferences前,咱們得先獲取到它。緩存
因爲SharedPreferences是Android內置的一個框架,因此咱們想要獲取到它很是的簡單,不須要導入任何依賴,直接寫代碼就行。下面咱們就來介紹下獲取對象的三個方式:安全
首先就是能夠說是最經常使用的方法,經過Context的getSharedPreferences()
方法去獲取到SharedPreferences對象。因爲是經過Context獲取的,因此基本上Android的全部場景咱們均可以經過這個方法獲取到。併發
public abstract SharedPreferences getSharedPreferences (String name, int mode) 複製代碼
這個方法接收兩個參數,分別是name
和mode
:app
name
:name就是咱們要存儲的SharedPreferences本地文件的名字,這個能夠自定義。可是若是使用一樣的name的話,永遠只能獲取到同一個SharedPreferences的對象。mode
:mode就是咱們要獲取的這個SharedPreferences的訪問模式,Android給咱們提供了挺多的模式的,可是因爲其他的模式或多或少存在着安全隱患(由於其餘應用也能夠直接獲取到),因此就所有都棄用了,如今就只有一個MODE_PRIVATE
模式。此外,這個方法是線程安全的。框架
Mode
的可選參數:異步
MODE_PRIVATE
:私有模式,該SharedPreferences只會被調用他的APP去使用,其餘的APP沒法獲取到這個SharedPreferences。MODE_WORLD_READABLE
ContentProvider
、BroadcastReceiver
和Service
。MODE_WORLD_WRITEABLE
MODE_MULTI_PROCESS
ContentProvider
去操做。在後面咱們會說爲啥多進程下不可靠。這個方法只能在Activity中或者經過Activity對象去使用。ide
public SharedPreferences getPreferences (int mode) 複製代碼
這個方法須要傳入一個mode
參數,這個參數和上面的context#getSharedPreferences()
的mode
參數是同樣的。其實這個方法和上面Context的那個方法是同樣的,他兩都是調用的SharedPreferences getSharedPreferences(String name, int mode)
。只不過Context的須要你去指定文件名,而這個方法你不須要手動去指定,而是會自動將當前Activity的類名做爲了文件名。函數
這個通常用在Android的設置頁面上,或者說,咱們也只有在構建設置頁面的時候纔會去使用這個。
public static SharedPreferences getDefaultSharedPreferences (Context context) 複製代碼
他承接一個context參數,並自動將當前應用的報名做爲前綴來命名文件。
若是須要往SharedPreferences中存儲數據的話,咱們並不能直接對SharedPreferences對象進行操做,由於SharedPreferences沒有提供存儲或者修改數據的接口。
若是想要對SharedPreferences存儲的數據進行修改,須要經過SharedPreferences.edit()
方法去獲取到SharedPreferences.Editor對象來進行操做。
獲取到Editor對象後,咱們就能夠調用他的putXXX()
方法進行存儲了,存儲以後必定記得經過apply()
和commit()
方法去將數據提交。
至於commit
和apply
的區別咱們後面會說。
//步驟1:建立一個SharedPreferences對象
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
//步驟2: 實例化SharedPreferences.Editor對象
SharedPreferences.Editor editor = sharedPreferences.edit();
//步驟3:將獲取過來的值放入文件
editor.putString("name", 「Tom」);
editor.putInt("age", 28);
editor.putBoolean("marrid",false);
//步驟4:提交
editor.commit();
// 刪除指定數據
editor.remove("name");
editor.commit();
// 清空數據
editor.clear();
editor.commit();
複製代碼
取值就很簡單了,構建出SharedPreferences的對象後,就直接調用SharedPreferences的getXXX()
方法就行。
SharedPreferences sharedPreferences = getSharedPreferences("data", Context .MODE_PRIVATE);
String userId = sharedPreferences.getString("name", "");
複製代碼
咱們上面說到,獲取SharedPreferences實例最經常使用的方法就是Context#getSharedPreferences()
。那咱們就從這個方法入手,看究竟是怎麼獲取到SharedPreferences實例的。
咱們先看下這個方法的實現:
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
// ...
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
}
複製代碼
能夠看到他又調用了Context的getSharedPreferences()
方法:
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
複製代碼
而後咱們就會驚喜的發現,這是一個抽象方法。我開始還想去找一個ContextWrapper
的構造的地方,看看mBase
傳入的是啥,後來找了一圈沒找到,直接上網搜索,立馬獲得答案:ContextImpl
,這個能夠說是Context
在Android中的惟一實現類,全部的操做又得通過這個類。那麼咱們就來看下這個類中的getSharedPreferences()
方法的實現:
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
// ps:這個nice很精髓😂
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
// 加了一個類鎖,保證同步
synchronized (ContextImpl.class) {
// mSharedPrefsPaths是一個保存了name和file對應關係的ArrayMap
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// 根據name從裏面找有沒有緩存的file
file = mSharedPrefsPaths.get(name);
// 若是沒有,那就調用getSharedPreferencesPath去找
if (file == null) {
// ->>> 重點1. getSharedPreferencesPath(name)
file = getSharedPreferencesPath(name);
// 並保存到mSharedPrefsPaths
mSharedPrefsPaths.put(name, file);
}
}
// 獲取到file後,再調用getSharedPreferences
return getSharedPreferences(file, mode);
}
/** * 重點1. ContextImpl # getSharedPreferencesPath(String name) * 根據PreferencesDir和name.xml去建立了這個文件 */
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
複製代碼
那咱們在看下getSharedPreferences(File file, int mode)
的實現:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
// SharedPreferences惟一實現類SharedPreferencesImpl的實例
SharedPreferencesImpl sp;
// 一樣的加類鎖
synchronized (ContextImpl.class) {
// 構造了一個File-SharedPreferencesImpl對應關係的ArrayMap
// 調用getSharedPreferencesCacheLocked方法區獲取cahce
// ->>> 重點1
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
// 從file-SharedPreferencesImpl鍵值對中根據當前file去過去SharedPreferencesImpl實例
sp = cache.get(file);
// 若是沒有,那就須要新建一個
if (sp == null) {
// 檢查mode,若是是MODE_WORLD_WRITEABLE或者MODE_MULTI_PROCESS則直接拋異常
checkMode(mode);
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對象
sp = new SharedPreferencesImpl(file, mode);
// 將對象和file的鍵值對存入cache中
cache.put(file, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
/** * 重點1. ContextImap # getSharedPreferencesCacheLocked() * 根據當前的包名,去獲取到由此應用建立的File-SharedPreferencesImpl的Map對象, * 而這個對象裏面就存放了這個應用建立的全部的SharedPreferencesImpl和File的對應關係 */
@GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
// 若是sSharedPrefsCache爲空就構造一個ArrayMap
// sSharedPrefsCache就是一個存放String-String, ArrayMap<File, SharedPreferencesImpl>的Map
// 換句話說,也就是存放包名-packagePrefs對應關係的Map
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
// 獲取包名
final String packageName = getPackageName();
// 到sSharedPrefsCache中找
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
// 若是找不到,就構建一個而後存進去
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
// 找獲得就返回
return packagePrefs;
}
複製代碼
咱們先來看下這個類的構造方法:
@UnsupportedAppUsage
SharedPreferencesImpl(File file, int mode) {
mFile = file;
// file的備份文件
mBackupFile = makeBackupFile(file);
mMode = mode;
// 從磁盤加載的標誌,當須要從磁盤加載時將其設爲true,這樣若是有其餘線程也調用了SharedPreferences的加載方法時,就會由於其爲true而直接返回也就不執行加載方法
// 保證了全局只有一個線程在加載
mLoaded = false;
// SharedPreferences中的數據
mMap = null;
// 保存的錯誤信息
mThrowable = null;
startLoadFromDisk();
}
複製代碼
初始化參數後立馬調用了startLoadFromDisk()
方法:
@UnsupportedAppUsage
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
// 開啓一個新線程來加載數據
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
複製代碼
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<String, Object> map = null;
// 文件信息,對應的是C語言stat.h中的struct stat
StructStat stat = null;
Throwable thrown = null;
try {
// 經過文件路徑去構建StructStat對象
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
// 從XML中把數據讀出來,並把數據轉化成Map類型
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>) 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) {
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();
}
}
}
複製代碼
到目前來講,就完成的SharedPreferencesImpl的構建過程。
相對來講,讀數據涉及到的方法比寫數據簡單得多,因此咱們先來看下讀數據: 咱們以getString()
爲例
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// 見2.3.2
awaitLoadedLocked();
// 從map中獲取數據
String v = (String)mMap.get(key);
// 若是獲取到數據,就返回數據,不然返回方法參數中給定的默認值
return v != null ? v : defValue;
}
}
複製代碼
@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();
}
// 若是沒有加載過,則等待
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
複製代碼
這個方法簡單點來講就是若是mLoad
不爲true也就是沒有加載完成的話,就等待加載完成。
可是光構建了對象還不夠,咱們還得能對她進行操做。咱們前面說到過,SharedPreferences並不提供修改的功能,若是你想對她進行修改,必須經過SharedPreferences.Editor
來實現。
咱們來看下SharedPreferences.edit()
:
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
// ->>> 重點1
awaitLoadedLocked();
}
// 建立了一個EditorImpl的對象,
// 可是這塊須要注意下,咱們想對SharedPreferences進行修改,就必須調用edit()方法,就會去構建一個新的EditorImpl對象
// 因此爲了不沒必要要的開銷,咱們在使用時最好一次性完成對數據的操做
return new EditorImpl();
}
/** * 重點1:SharedPreferencesImpl # awaitLoadedLocked() */
@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();
}
while (!mLoaded) {
try {
// 若是尚未加載完成,就進入等待狀態
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
複製代碼
那咱們再來看下putXXX()
方法,咱們以putString()
來舉例:
public final class EditorImpl implements Editor {
private final Object mEditorLock = new Object();
// 存數據的HashMap
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
@GuardedBy("mEditorLock")
private boolean mClear = false;
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
複製代碼
putString()
方法很簡單,直接將數據put到存數據的HashMap中去就好了。或者說,全部的putXXX()
都是這麼簡單。
可是,若是咱們想將修改提交到SharedPreferences裏面去的話,還須要調用apply()
或者commit()
方法,那咱們如今來看下這兩個方法。
@Override
public void apply() {
// 獲取當前時間
final long startTime = System.currentTimeMillis();
// 見2.2.3.4
// 構建了一個MemoryCommitResult的對象
final MemoryCommitResult mcr = commitToMemory();
// 新建一個線程,由於數據操做是很耗時的
final Runnable awaitCommit = new Runnable() {
@Override
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");
}
}
};
// 將awaitCommit添加到Queue的Word中去
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
// 執行操做,並從QueuedWord中刪除
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);
}
複製代碼
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
// 見2.2.3.4
// 構建了一個MemoryCommitResult對象
MemoryCommitResult mcr = commitToMemory();
// 將內存數據同步到文件
// 見
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");
}
}
// 通知監聽則, 並在主線程回調onSharedPreferenceChanged()方法
notifyListeners(mcr);
// 返回文件操做的結果數據
return mcr.writeToDiskResult;
}
複製代碼
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
// 當前Memory的狀態,其實也就是當須要提交數據到內存的時候,他的值就加一
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
// 存數據的Map
Map<String, Object> 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<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
// 2.2.3.5的關鍵點
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
// 若是mClear爲true,就清空mapToWriteToDisk
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
for (Map.Entry<String, Object> 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);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
複製代碼
這段代碼剛開始看的時候有點暈,可是看完後就瞬間懂了,這段代碼主要執行了一下的功能:
mMap
賦值給mapToWriteToDisk
mClear
爲true的時候,清空mapToWriteToDisk
mModified
,mModified
也就是咱們上面說到的保存本次edit的數據的HashMap
value
爲null或者this
的時候,移除對應的kMemoryCommitResult
對象private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 這個方法這塊就不講了,太長了,你們感興趣能夠看下
// 主要功能就是
// 1. 當沒有key沒有改變,則直接返回了;不然執行下一步
// 2. 將mMap所有信息寫入文件,若是寫入成功則刪除備份文件,若是寫入失敗則刪除mFile
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
// 當寫入成功後,將標誌位減1
mDiskWritesInFlight--;
}
// 此時postWriteRunnable爲null不執行該方法
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
// 若是是commit則進入
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
// 因爲commitToMemory會讓mDiskWritesInFlight+1,則wasEmpty爲true
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
// 在執行一遍上面的操做,保證將commit的內容也保存
writeToDiskRunnable.run();
return;
}
}
// 若是是apply()方法,則會將任務放入單線程的線程池中去執行
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
複製代碼
因此從這個方法咱們能夠看到:
commit()
是直接同步執行的,有數據就存入磁盤apply()
是先將awaitCommit
放入QueuedWork
,而後在單線程的線程池中去執行,執行完畢後再將awaitCommit
從QeueudWork
中移除。咱們前面說到了,SP提供了多進程訪問,雖然說沒有像World模式那樣會直接拋異常,可是官方不建議多進程下使用SP。
那麼咱們不由會好奇,多進程下訪問SP會有什麼問題呢?
探究這個問題,咱們得先回到ContextImpl#getSharedPreferences(File file, int mode)
方法:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
// ...前面的代碼省略的,若是你們想回憶下,能夠跳轉到2.1節
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
// ->>> 重點1
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
/** * 重點1 SharedPreferencesImpl # startReloadIfChangedUnexpectedly() */
void startReloadIfChangedUnexpectedly() {
synchronized (mLock) {
// TODO: wait for any pending writes to disk?
// ->>> 重點2
if (!hasFileChangedUnexpectedly()) {
return;
}
// ->>> 重點3
startLoadFromDisk();
}
}
/** * 重點2 SharedPreferencesImpl # hasFileChangedUnexpectedly() * 若是文件發生了預期以外的修改,也就是說有其餘進程在修改,就返回true,不然false */
private boolean hasFileChangedUnexpectedly() {
synchronized (mLock) {
// 若是mDiskWritesInFlight大於0,就證實是在當前進程中修改的,那就不用從新讀取
if (mDiskWritesInFlight > 0) {
// If we know we caused it, it's not unexpected.
if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
return false;
}
}
final StructStat stat;
try {
/* * Metadata operations don't usually count as a block guard * violation, but we explicitly want this one. */
BlockGuard.getThreadPolicy().onReadFromDisk();
stat = Os.stat(mFile.getPath());
} catch (ErrnoException e) {
return true;
}
synchronized (mLock) {
return !stat.st_mtim.equals(mStatTimestamp) || mStatSize != stat.st_size;
}
}
/** * 重點3 SharedPreferencesImpl # startLoadFromDisk() */
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
// ->>> 重點4,這塊代碼能夠回到2.2.3看一下
loadFromDisk();
}
}.start();
}
複製代碼
咱們能夠看到:每次獲取SharedPreferences實例的時候嘗試從磁盤中加載數據,而且是在異步線程中,所以一個線程的修改最終會反映到另外一個線程,但不能當即反映到另外一個進程,因此經過SharedPreferences沒法實現多進程同步。
loadFromDisk()
方法中咱們最須要關注的是這一段:
// 若是備份文件已經存在,那就刪除源文件,並將備份文件替換爲源文件
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
複製代碼
這塊判斷了mBackupFile
是否存在,那mBackupFile
咱們是在哪建立的呢? 整個SharedPreferencesImpl中有兩處:
makeBackupFile()
給傳入的file
構造一個mBackupFile
mBackupFile
,就會根據當前的mFile
重命名爲mBackupFile
而writeToFile()
在enqueueDiskWrite()
中被調用,這個方法太長了,我截取下關鍵信息:
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// ...
boolean fileExists = mFile.exists();
// ...
// Rename the current file so it may be used as a backup during the next read
if (fileExists) {
// ...
boolean backupFileExists = mBackupFile.exists();
// ...
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();
}
}
// Attempt to write the file, delete the backup and return true as atomically as
// possible. If any exception occurs, delete the new file; next time we will restore
// from the backup.
try {
FileOutputStream str = createFileOutputStream(mFile);
// ...
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
writeTime = System.currentTimeMillis();
FileUtils.sync(str);
fsyncTime = System.currentTimeMillis();
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
// ...
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();
// ...
複製代碼
因此咱們大體總結下這個方法的功能:
mFIle
存在而且備份文件mBackupFile
不存在,就將源文件重命名爲備份文件,若是源文件存在而且備份文件存在,就刪除源文件mFile
,並將內容寫進去mBackupFile
結合一下loadFromDisk()
和writeToFile()
兩個方法,咱們能夠推測出:當存在兩個進程,一個讀進程,一個寫進程,因爲只有在建立SharedPreferencesImpl
的時候建立了一個備份進程,此時讀進程會將源文件刪除,並將備份文件重命名爲源文件,這樣的結果就是,讀進程永遠只會看到寫以前的內容。而且因爲寫文件須要調用createFileOutputStream(mFile)
,可是這個時候因爲源文件被讀進程刪除了,因此致使寫進程的mFIle
沒有了引用,也就會建立失敗,致使修改的數據沒法更新到文件上,進而致使數據丟失。