SharedPreferences原理淺析
1.概述
SharedPreferences是用來訪問和修改偏好preference數(shù)據(jù)的接口否纬,可以通過Context.getSharedPreferences()方法返回SharedPreferences择膝。
對于任意一組偏好設(shè)置數(shù)據(jù)奋构,只有一個共享的SharedPreferences實例。
修改preferences必須通過一個Editor對象來確保存儲數(shù)據(jù)的一致性以及控制數(shù)據(jù)何時存儲链蕊。
SharedPreferences是一個接口请祖,里面定義了很多數(shù)據(jù)存儲與獲取的接口您机。
public interface SharedPreferences {
/*
* 定義了一個回調(diào)接口穿肄,當(dāng)SharedPreference被修改時,觸發(fā)該方法
*/
public interface OnSharedPreferenceChangeListener {
void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
}
/*
* 用來修改SharedPreference里面values的接口
* 所有在editor中的修改都是批量修改往产,只有調(diào)用了commit()或者apply()方法之后被碗,修改才生效
*/
public interface Editor {
Editor putString(String key, @Nullable String value);
Editor putInt(String key, int value);
Editor remove(String key);
Editor clear();
boolean commit();
void apply();
}
String getString(String key, @Nullable String defValue);
int getInt(String key, int defValue);
Editor edit();
void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
}
我們可以通過Context.getSharedPreferences()方法返回一個SharedPreferences實現(xiàn)某宪,該實現(xiàn)是SharedPreferencesImpl類仿村。接下來將來分析SharedPreferencesImpl實現(xiàn)類。
2.源碼分析
2.1 ContextImpl.getSharedPreferences()
public SharedPreferences getSharedPreferences(String name, int mode) {
// 在Android 4.4以前兴喂,如果name為null的話蔼囊,則會把它當(dāng)成null.xml來處理
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// 根據(jù)name查找對應(yīng)的File文件是否存在焚志,不存在,則根據(jù)name創(chuàng)建一個File文件畏鼓,并把該File文件保存到一個Map集合中酱酬,以備后續(xù)使用
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);//見2.2
}
在ContextImpl中定義了兩個與SharedPreference相關(guān)的ArrayMap,它們分別緩存<preference name,File>和<package name,<File,SharedPreferencesImpl>>云矫。它們的定義如下:
/**
* Map from package name, to preference name, to cached preferences.
*/
@GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
/**
* Map from preference name to generated path.
*/
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;
我們在查找一個SharedPreferences時膳沽,首先需要根據(jù)SharedPreferences的name在mSharedPrefsPaths中查找到對應(yīng)的File文件,然后根據(jù)當(dāng)前應(yīng)用包名package name让禀,在sSharedPrefsCache中查找當(dāng)前應(yīng)用包名對應(yīng)的ArrayMap<File, SharedPreferencesImpl>挑社,最后根據(jù)File文件,查找對應(yīng)的SharedPreferencesImpl類巡揍。查找過程大致如下:
preference name ——> File
package name ——> ArrayMap<File痛阻,SharedPreferencesImpl>———> SharedPreferencesImpl
2.2 ContextImpl.getSharedPreferences()
public SharedPreferences getSharedPreferences(File file, int mode) {
checkMode(mode);// 檢查文件模式
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();//根據(jù)當(dāng)前應(yīng)用包名,獲取對應(yīng)的ArrayMap<File, SharedPreferencesImpl>
sp = cache.get(file);//根據(jù)文件腮敌,獲取對應(yīng)的SharedPreferencesImpl
if (sp == null) {
sp = new SharedPreferencesImpl(file, mode);//創(chuàng)建一個新的ShardPreferenceImpl對象阱当,見2.3
cache.put(file, sp);//存入緩存中
return sp;
}
}
// 如果是多進程模式,或者是Android 3.0以前糜工,則檢測是否有其他進程在后臺修改SharedPreferences
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;
}
SharedPreference的創(chuàng)建模式mode有以下幾種:
- MODE_PRIVATE:默認(rèn)模式弊添,該模式下創(chuàng)建的文件只能被當(dāng)前應(yīng)用或者與該應(yīng)用具有相同SharedUserID的應(yīng)用訪問。
- MODE_WORLD_READABLE:允許其他應(yīng)用讀取這個模式創(chuàng)建的文件捌木。在Android N上使用該模式將拋出SecurityException異常表箭。
- MODE_WORLD_WRITEABLE:允許其他應(yīng)用寫入這個模式創(chuàng)建的文件。在Android N上使用該模式將拋出SecurityException異常钮莲。
- MODE_APPEND:如果文件已經(jīng)存在了免钻,則在文件的尾部添加數(shù)據(jù)。
- MODE_MULTI_PROCESS:SharedPreference加載標(biāo)志崔拥,當(dāng)設(shè)置了該標(biāo)志极舔,則在磁盤上的文件將會被檢查是否修改了,盡管SharedPreference實例已經(jīng)在該進程中被加載了链瓦。
在checkMode()方法中拆魏,主要是檢查是否在Android N上使用了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模式,如果使用了慈俯,則拋出異常渤刃。
在獲取SharedPreference時,不是每次都會重新創(chuàng)建一個新的SharedPreference實例贴膘,而是先從緩存中卖子,查找是否存在對應(yīng)的SharedPreference實例,如果有相應(yīng)的實例刑峡,則直接返回洋闽。如果不存在玄柠,則創(chuàng)建一個新的SharedPreference實例,并把它保存在緩存中诫舅,以備下次使用羽利。
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();//獲取當(dāng)前應(yīng)用的包名
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);//獲取當(dāng)前應(yīng)用包名對應(yīng)的ArrayMap<File, SharedPreferencesImpl>
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);//保存到緩存中
}
return packagePrefs;
}
由于ContextImpl是應(yīng)用的執(zhí)行環(huán)境,每一個應(yīng)用里面可以包含有多個SharedPreference文件刊懈。因此这弧,為了更好的定位SharedPreference文件,首先根據(jù)應(yīng)用包名進行篩選虚汛,得到ArrayMap<File, SharedPreferencesImpl>当宴,然后再通過SharedPreference文件名進行篩選,得到SharedPreferencesImpl泽疆。
可以看到户矢,SharedPreferencesImpl只會被創(chuàng)建一次,之后會被保存在緩存中殉疼,后續(xù)的獲取操作都是從緩存中獲取SharedPreferencesImpl實例對象梯浪。
2.3 SharedPreferencesImpl()
SharedPreferencesImpl(File file, int mode) {
mFile = file;//保存SharedPreference對應(yīng)的xml文件
mBackupFile = makeBackupFile(file);//構(gòu)建備份文件,以.bak后綴結(jié)尾
mMode = mode;//保存創(chuàng)建模式
mLoaded = false;//初始化mLoaded為false
mMap = null;//初始化Map<String, Object>為空
startLoadFromDisk();//開始從磁盤中加載數(shù)據(jù)瓢娜,見2.4
}
在SharedPreferencesImpl的構(gòu)造函數(shù)中挂洛,主要是初始化了一些重要成員變量,并開始從磁盤中加載數(shù)據(jù)到內(nèi)存中眠砾。
2.4 SharedPreferencesImpl.startLoadFromDisk()
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
//創(chuàng)建一個線程來執(zhí)行從磁盤中加載數(shù)據(jù)
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();// 見2.5
}
}.start();
}
該方法主要是創(chuàng)建一個子線程虏劲,然后在子線程中執(zhí)行具體的加載操作。
2.5 SharedPreferencesImpl.loadFromDisk()
private void loadFromDisk() {
synchronized (SharedPreferencesImpl.this) {
// 如果已經(jīng)被加載了褒颈,則直接返回
if (mLoaded) {
return;
}
//如果備份文件已經(jīng)存在了柒巫,則刪除當(dāng)前文件,用備份文件來代替
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
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);//讀取文件內(nèi)容到Map集合中谷丸,Map集合是一個Map<String, Object>的集合
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
/* ignore */
}
synchronized (SharedPreferencesImpl.this) {
mLoaded = true;//將mLoaded置為空堡掏,讓等待加載完成的線程被喚醒
if (map != null) {
mMap = map;//保存加載內(nèi)容到mMap中
mStatTimestamp = stat.st_mtime;//記錄時間戳
mStatSize = stat.st_size;//記錄文件大小
} else {
mMap = new HashMap<>();
}
notifyAll();//喚醒被阻塞的線程
}
}
可以看到,在loadFromDisk()方法中刨疼,主要的工作是將磁盤中的文件內(nèi)容讀取到內(nèi)存中泉唁,然后再喚醒阻塞等待的線程,告訴他們數(shù)據(jù)已經(jīng)讀取到內(nèi)存中了揩慕。這里用到了典型的wait()/notifyAll()機制亭畜,來同步線程之間的交互。那在什么時候會調(diào)用wait()呢迎卤?在我們獲取SharedPreferencesImpl來存儲數(shù)據(jù)和獲取數(shù)據(jù)時拴鸵,都會調(diào)用到wait()。
SharedPreferences數(shù)據(jù)的存儲需要借助Editor來實現(xiàn),通過Editor操作后宝踪,再通過commit()或apply()方法將修改同步到磁盤中侨糟。commit()方法是有返回結(jié)果的碍扔,來表示修改是否成功了瘩燥。而apply()方法是沒有返回結(jié)果,它只是提交一個寫入磁盤的請求不同,然后由子線程去執(zhí)行厉膀。
Editor是通過SharedPreferencesImpl的edit()方法來創(chuàng)建的。
2.6 SharedPreferencesImpl.edit()
public Editor edit() {
synchronized (this) {//同步方法二拐,保證每次只有一個線程執(zhí)行加載操作
awaitLoadedLocked();//等待SharedPreferencesImpl加載到內(nèi)存中服鹅,見2.7
}
return new EditorImpl();//創(chuàng)建一個新的Editor實現(xiàn)。
}
在創(chuàng)建Editor之前百新,需要等待SharedPreferencesImpl加載到內(nèi)存中企软,然后才會創(chuàng)建一個Editor實現(xiàn)類EditorImpl。
2.7 SharedPreferencesImpl.awaitLoadedLocked()
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();
}
// 如果mLoad為false饭望,則一直循環(huán)等待下去
while (!mLoaded) {
try {
wait();//阻塞等待
} catch (InterruptedException unused) {
}
}
}
在awaitLoadedLocked()方法中仗哨,主要是通過mLoaded變量來控制循環(huán)阻塞等待,該變量是在SharedPreferencesImpl的loadFromDisk()方法中铅辞,被置為true了厌漂,并通過notifyAll()方法喚醒等待的線程。
因為從磁盤中加載SharedPreference數(shù)據(jù)到內(nèi)存中是一個耗時操作斟珊,因此需要在子線程中執(zhí)行加載操作苇倡,當(dāng)子線程加載完成后,需要給主線程發(fā)送一個通知囤踩,喚醒被阻塞等待的操作旨椒。
在開始創(chuàng)建SharedPreferencesImpl時,就會從磁盤中加載xml文件到內(nèi)存中堵漱,加載完成后钩乍,將mLoaded置為true,并喚醒正在等待的線程怔锌。因為在通過Context.getSharedPreferences()獲取到SharedPreferencesImpl時寥粹,此時有可能數(shù)據(jù)并未全部都從磁盤加載到內(nèi)存中,因此需要在操作SharedPreferencesImpl之前埃元,等待數(shù)據(jù)從磁盤加載到內(nèi)存中涝涤。awaitLoadedLocked()操作就是用來完成等待數(shù)據(jù)從磁盤加載到內(nèi)存中,該方法返回后岛杀,可以確保所有的數(shù)據(jù)都加載到內(nèi)存中了阔拳,后續(xù)的所有操作都是針對內(nèi)存中數(shù)據(jù)進行操作了。
EditorImpl實現(xiàn)了Editor接口,獲取到EditorImpl之后糊肠,就可以通過Editor對SharedPreference中的數(shù)據(jù)進行修改了辨宠。Editor的putXXX方法對數(shù)據(jù)的修改都只是在內(nèi)存中對數(shù)據(jù)進行修改,只有調(diào)用了commit()或apply()方法之后货裹,才會真正同步修改到磁盤中嗤形。
2.8 EditorImpl.putString()
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
EditorImpl的putXXX方法,主要是將數(shù)據(jù)保存在一個Map中弧圆,這些數(shù)據(jù)是存儲在內(nèi)存中赋兵,只有調(diào)用了commit()或apply()方法之后,才會同步到磁盤中搔预。
private final Map<String, Object> mModified = Maps.newHashMap();//暫時保存需要寫入磁盤的數(shù)據(jù)
2.9 EditorImpl.commit()
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();//構(gòu)建需要寫入磁盤的數(shù)據(jù)霹期,見2.10
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay*/);//見2.11
try {
mcr.writtenToDiskLatch.await();//等待寫入完成
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr);//通知等待者,寫入已經(jīng)完成了
return mcr.writeToDiskResult;//返回寫入是否成功
}
在將內(nèi)存中保存的數(shù)據(jù)寫入到磁盤時拯田,需要借助MemoryCommitResult類历造,構(gòu)建寫入磁盤的數(shù)據(jù)。然后將這個寫入操作由子線程來執(zhí)行船庇,并等待子線執(zhí)行完成吭产。當(dāng)子線程寫入完成后或者發(fā)生了異常,通知等待者寫入完成了溢十,并把寫入結(jié)果返回給調(diào)用者垮刹。
2.10 EditorImpl.commitToMemory()
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();//創(chuàng)建MemoryCommitResult實例
synchronized (SharedPreferencesImpl.this) {
// 在準(zhǔn)備將內(nèi)存中的數(shù)據(jù)寫入到磁盤時,如果已經(jīng)正在執(zhí)行寫入操作张弛,則先采用深拷貝荒典,復(fù)制mMap中的數(shù)據(jù)
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);//深拷貝mMap中的數(shù)據(jù)
}
mcr.mapToWriteToDisk = mMap;//將該Map賦值給需要寫入磁盤的Map。
mDiskWritesInFlight++;//更新正在執(zhí)行寫入磁盤的操作次數(shù)
boolean hasListeners = mListeners.size() > 0;//返回已經(jīng)注冊的OnSharedPreferenceChangeListener數(shù)量
if (hasListeners) {
mcr.keysModified = new ArrayList<String>();
mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (this) {
boolean changesMade = false;
//是否需要清除內(nèi)容
if (mClear) {
if (!mMap.isEmpty()) {
changesMade = true;
mMap.clear();
}
mClear = false;
}
// 循環(huán)將mModified集合中的數(shù)據(jù)拷貝到mMap中
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// 如果需要修改的value為null吞鸭,則移除該key對應(yīng)的entry
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
// 如果已經(jīng)存在相同的值寺董,則直接返回,否則將該<k,v>添加到mMap集合中
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);//添加該<k刻剥,v>集合到Map中
}
changesMade = true;//發(fā)生了改變
if (hasListeners) {
mcr.keysModified.add(k);//更新被修改的key集合
}
}
mModified.clear();//清空mModified Map集合
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
mcr.memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return mcr;
}
可以看到commitToMemory()方法主要完成以下幾件事情:
- 創(chuàng)建一個MemoryCommitResult對象遮咖,該對象封裝了一些寫入磁盤的狀態(tài);
- 對mMap集合做一個深拷貝造虏,并把它保存在MemoryCommitResult的mapToWriteToDisk變量中御吞;
- 如果注冊了OnSharedPreferenceChangeListener監(jiān)聽者,則創(chuàng)建一個ArrayList列表漓藕,來保存被修改的key列表陶珠;
- 如果設(shè)置了清除標(biāo)志位mClear,則先清空mMap集合享钞;
- 將EditorImpl中mModified集合中的數(shù)據(jù)拷貝到mMap集合中揍诽,如果key對應(yīng)value已經(jīng)存在了,則跳過拷貝。如果key對應(yīng)的value為null暑脆,則刪除該key對應(yīng)的Entry渠啤。
- 清空mModified集合,并返回創(chuàng)建的MemoryCommitResult對象添吗。
將mModified集合中的數(shù)據(jù)拷貝到mMap集合中沥曹,具有緩存的作用,如果應(yīng)用再次馬上查詢SharedPreference時根资,則可以先從mMap集合中直接返回結(jié)果架专,而不用從磁盤中讀取同窘。
2.11 EditorImpl.enqueueDiskWrite()
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);//寫入到磁盤文件中玄帕,見2.12
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;//寫入完成后,更新正在執(zhí)行寫入操作數(shù)
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
if (isFromSyncCommit) {//如果是同步寫入操作
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
wasEmpty = mDiskWritesInFlight == 1;//在創(chuàng)建MemoryCommitResult時想邦,如果之前的寫入操作都完成了的話裤纹,則mDiskWritesInFlight的值為1
}
if (wasEmpty) {
writeToDiskRunnable.run();//執(zhí)行run()方法,同步調(diào)用
return;
}
}
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);//如果之前還有寫入操作丧没,則將該寫入操作放入工作隊列中鹰椒,等待執(zhí)行。
}
在enqueueDiskWrite()方法中呕童,首先根據(jù)參數(shù)postWriteRunnable是否為null來判斷是否同步寫入操作漆际。接著創(chuàng)建一個寫入磁盤的Runnable,在該Runnable中執(zhí)行具體的寫入磁盤文件的操作夺饲。如果是同步寫入操作奸汇,并且當(dāng)前沒有寫入操作,則直接調(diào)用writeToDiskRunnable的run()方法往声,在當(dāng)前線程中執(zhí)行磁盤寫入操作擂找。如果是同步寫入操作,并且當(dāng)前有正在執(zhí)行的寫入操作浩销,則將該writeToDiskRunnable放入工作隊列中贯涎,等待線程隨后執(zhí)行。
QueuedWork.singleThreadExecutor()方法返回的是一個單個線程的執(zhí)行器Executor慢洋,里面有一個無界的隊列來保存執(zhí)行任務(wù)塘雳。這樣的話,可以保證任務(wù)是順序的執(zhí)行普筹,并且保證每次只有一個任務(wù)執(zhí)行败明。
public static ExecutorService singleThreadExecutor() {
synchronized (QueuedWork.class) {
if (sSingleThreadExecutor == null) {
// TODO: can we give this single thread a thread name?
sSingleThreadExecutor = Executors.newSingleThreadExecutor();
}
return sSingleThreadExecutor;
}
}
2.12 SharedPreferencesImpl.writeToFile()
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
if (mFile.exists()) {// 如果文件已經(jīng)存在了
boolean needsWrite = false;
// 只有磁盤上文件狀態(tài)比當(dāng)前文件更舊時,才執(zhí)行更新操作
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {//同步寫入操作
needsWrite = true;
} else {
synchronized (this) {
// No need to persist intermediate states. Just wait for the latest state to
// be persisted.
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
// 不需要立即寫入斑芜,則在MemoryCommitResult中記錄該結(jié)果肩刃,然后直接返回
if (!needsWrite) {
mcr.setDiskWriteResult(true);//記錄寫入成功了,喚醒等待寫入結(jié)果的線程
return;
}
if (!mBackupFile.exists()) {//如果備份文件不存在
if (!mFile.renameTo(mBackupFile)) {//將新文件重命名為備份文件
mcr.setDiskWriteResult(false);
return;
}
} else {
mFile.delete();//如果備份文件已經(jīng)存在了,則刪除mFile文件
}
}
// 當(dāng)嘗試寫入文件時盈包,刪除備份文件沸呐,并返回true。如果在寫入過程中發(fā)生了異常呢燥,則刪除新的文件崭添,下一次從備份文件中恢復(fù)。
try {
FileOutputStream str = createFileOutputStream(mFile);//從File中獲取FileOutputStream
if (str == null) {//獲取失敗叛氨,則取消寫入操作
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);//借助XmlUtils工具呼渣,將MemoryCommitResult中保存的Map數(shù)據(jù),寫入到FileOutputStream中寞埠。
FileUtils.sync(str);//執(zhí)行FileOutputStream的flush操作屁置,同步到磁盤中
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);//設(shè)置文件的訪問模式
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;//更新時間戳
mStatSize = stat.st_size;//更新文件大小
}
} catch (ErrnoException e) {
// Do nothing
}
//寫入成功了,刪除備份文件
mBackupFile.delete();
mDiskStateGeneration = mcr.memoryStateGeneration;//更新磁盤文件狀態(tài)
mcr.setDiskWriteResult(true);//記錄寫入成功了仁连,喚醒等待寫入結(jié)果的線程
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);
}
在writeToFile()方法中蓝角,首先將當(dāng)前文件重命名為備份文件,然后從當(dāng)前文件中獲取文件輸出流饭冬,并將MemoryCommitResult中保存的Map數(shù)據(jù)使鹅,寫入到文件輸出流中。如果寫入成功昌抠,則刪除備份文件患朱,返回true。如果寫入失敗炊苫,則刪除當(dāng)前文件裁厅,下一次從備份文件中恢復(fù)過來。
通知調(diào)用者是否寫入成功是通過setDiskWriteResult()方法來完成的劝评,在該方法中姐直,通過MemoryCommitResult的writeToDiskResult變量來保存寫入結(jié)果,寫入成功為true蒋畜,寫入失敗為false声畏。不管寫入成功還是失敗,都會讓writtenToDiskLatch閉鎖計數(shù)減1姻成,喚醒在閉鎖上等待的線程插龄。
public void setDiskWriteResult(boolean result) {
writeToDiskResult = result;//保存是否寫入磁盤成功的結(jié)果
writtenToDiskLatch.countDown();//減少閉鎖計數(shù),喚醒在閉鎖上等待的操作
}
既然有通過閉鎖計數(shù)減1科展,喚醒等待線程的操作均牢,就應(yīng)該也有等待閉鎖計算計數(shù)為0的地方。這個地方才睹,在調(diào)用commit()方法時候徘跪,會調(diào)用MemoryCommitResult上的閉鎖writtenToDiskLatch的await()方法甘邀。
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
調(diào)用者獲取到寫入完成通知后,接著通知那些監(jiān)聽SharedPreference變化的監(jiān)聽者垮庐。具體是通過EditorImpl的notifyListeners()方法完成的松邪。
2.13 EditorImpl.notifyListeners()
private void notifyListeners(final MemoryCommitResult mcr) {
// 如果監(jiān)聽者為空,或者沒有修改過SharedPreference的內(nèi)容哨查,則直接返回
if (mcr.listeners == null || mcr.keysModified == null ||
mcr.keysModified.size() == 0) {
return;
}
//如果當(dāng)前線程是主線程,則把SharedPreference中的所有key修改通知給所有的監(jiān)聽者
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 {
// 如果是在子線程中逗抑,則讓該通知操作發(fā)生在主線程中
ActivityThread.sMainThreadHandler.post(new Runnable() {
public void run() {
notifyListeners(mcr);
}
});
}
}
在通知SharedPreference變化時,首先判斷監(jiān)聽者是否空寒亥,或者SharedPreference是否發(fā)生了變化邮府。然后在應(yīng)用主線程中將SharedPreference中的所有key修改通知給所有的監(jiān)聽者。
通過2.9~2.13的過程溉奕,描述了commit()的流程:
- 首先構(gòu)建一個寫入磁盤的輔助對象MemoryCommitResult褂傀,把mModified集合中的數(shù)據(jù)拷貝到mMap中,并把它保存到MemoryCommitResult的mapToWriteToDisk變量中腐宋;
- 如果當(dāng)前沒有寫入操作紊服,則直接在當(dāng)前線程中執(zhí)行寫入操作檀轨;否則胸竞,封裝寫入操作到單線程任務(wù)隊列中,等待在其他線程中隨后執(zhí)行寫入操作参萄;
- 寫入操作主要是將MemoryCommitResult中的mapToWriteToDisk集合內(nèi)容寫入到磁盤文件中卫枝,寫入完成后,再通過setDiskWriteResult()方法返回結(jié)果讹挎,并喚醒等待結(jié)果的線程校赤;
- 等待寫入結(jié)果的線程被喚醒之后,通過notifyListeners()方法筒溃,在主線程中將SharedPreference中修改的key通知給監(jiān)聽者马篮;
- 返回寫入結(jié)果給調(diào)用者;
2.14 EditorImpl.apply()
public void apply() {
final MemoryCommitResult mcr = commitToMemory();//構(gòu)建需要寫入磁盤的數(shù)據(jù)怜奖,見2.10
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();//等待寫入完成
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.add(awaitCommit);//添加到等待結(jié)束任務(wù)隊列
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(awaitCommit);//從等待結(jié)束任務(wù)隊列中移除
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);//見2.11
// 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);//通知監(jiān)聽者
}
可以看到apply()與commit()的主要區(qū)別是傳遞給enqueueDiskWrite()方法的第二個參數(shù)不同浑测。在commit()方法中,postWriteRunnable為null歪玲,因此執(zhí)行的同步寫入操作迁央,而在apply()方法中,postWriteRunnable不為null滥崩,因此apply()中的所有寫入操作都是在單線程的Executor中執(zhí)行岖圈。
在寫入操作完成后,會執(zhí)行postWriteRunnable里面的run()方法钙皮,在該run()方法中蜂科,又執(zhí)行awaitCommit里面的run()方法顽决,在該run()方法中,主要是等待寫入操作完成导匣。由于postWriteRunnable是在寫入操作完成后執(zhí)行的擎值,因此該等待操作立即返回。
因為apply()方法的寫入操作逐抑,都是在單線程的Executor中執(zhí)行的鸠儿,不能確切知道什么時候執(zhí)行完成。那么如果想等待異步操作完成后立即返回厕氨,該如何做呢进每?在QueuedWork中,有一個等待執(zhí)行結(jié)束的任務(wù)隊列命斧,在執(zhí)行任務(wù)之前田晚,先將任務(wù)添加到任務(wù)隊列中,等待任務(wù)執(zhí)行完成后国葬,則再從任務(wù)隊列中移除該任務(wù)贤徒。
public class QueuedWork {
private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
new ConcurrentLinkedQueue<Runnable>();
/*
* 添加一個任務(wù)到等待結(jié)束任務(wù)隊列中
*/
public static void add(Runnable finisher) {
sPendingWorkFinishers.add(finisher);
}
/*
* 從等待結(jié)束任務(wù)隊列中移除該任務(wù)
*/
public static void remove(Runnable finisher) {
sPendingWorkFinishers.remove(finisher);
}
/*
* 等待異步操作完成,如果異步操作沒有汇四,則一直循環(huán)等待接奈。
*/
public static void waitToFinish() {
Runnable toFinish;
while ((toFinish = sPendingWorkFinishers.poll()) != null) {
toFinish.run();
}
}
}
從QueuedWork中,可以看到通孽,如果需要等待異步操作完成序宦,只需在任務(wù)執(zhí)行前先通過QueuedWork.add()方法將任務(wù)添加到等待結(jié)束的任務(wù)隊列中,然后調(diào)用QueuedWork.waitToFinish()方法等待異步操作執(zhí)行完成背苦,異步操作執(zhí)行完成后互捌,會調(diào)用QueuedWork.remove()方法,從等待結(jié)束任務(wù)隊列中移除該任務(wù)行剂。
可以看到在apply()方法中秕噪,在執(zhí)行異步寫入操作之前,通過QueuedWork.add()方法厚宰,將任務(wù)添加到了等待結(jié)束的任務(wù)隊列中腌巾,當(dāng)執(zhí)行完寫入操作后,再通過QueuedWork.remove()方法移除在結(jié)束等待任務(wù)隊列中的任務(wù)固阁。
前面主要是介紹了數(shù)據(jù)的存儲過程壤躲,主要是借助Editor類的putXXX()方法來保存數(shù)據(jù),并最后通過commit()或apply()方法將內(nèi)存中的數(shù)據(jù)同步到磁盤中备燃。
接下來看看SharedPreference是如何獲取數(shù)據(jù)的碉克?
2.15 SharedPreferencesImpl.getString()
public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();//等待SharedPreference加載到內(nèi)存中,見2.7
String v = (String)mMap.get(key);//直接從mMap中獲取值
return v != null ? v : defValue;//如果值不存在并齐,則返回默認(rèn)的值
}
}
可以看到漏麦,獲取數(shù)據(jù)的過程比較簡單客税,首先是等待SharedPreference加載到內(nèi)存中,加載完成后撕贞,直接從mMap集合查看對應(yīng)key的value是否存在更耻。如果存在,則直接返回捏膨,如果不存在秧均,則返回默認(rèn)值。
在commitToMemory()方法中号涯,我們可以看到目胡,在寫入磁盤之前,其實已經(jīng)將數(shù)據(jù)先從mModified集合拷貝到mMap集合中链快。這樣做的一個目的是誉己,當(dāng)一個線程執(zhí)行putXXX()操作后,另外一個線程就可以通過getXXX()立即獲得相關(guān)的值域蜗,因為這些數(shù)據(jù)都是保存在內(nèi)存中巨双,可以立即返回,而不用等待數(shù)據(jù)寫入到磁盤后霉祸,再從磁盤中獲取數(shù)據(jù)筑累。這是因為磁盤操作是一個耗時的操作,所以通過mMap集合在內(nèi)存中緩存結(jié)果脉执。
3.小結(jié)
SharedPreference主要用來保存一些簡單的值疼阔,例如int、String半夷、Boolean等類型。
SharedPreference數(shù)據(jù)的存儲必須通過Editor類的putXXX()方法進行保存迅细,然后通過Editor的commit()和apply()方法將數(shù)據(jù)同步到磁盤中巫橄。
SharedPreference數(shù)據(jù)的獲取可以直接通過SharedPreference的getXXX()方法進行獲取。
SharedPreference的數(shù)據(jù)本質(zhì)上是保存在一個xml文件中茵典,這個xml文件存放在/data/data/應(yīng)用包名/shared_prefs/目錄下湘换。
如果不需要數(shù)據(jù)寫入磁盤的結(jié)果,則可以使用apply()方法進行磁盤寫入统阿,該方法是在子線程中執(zhí)行彩倚。如果需要磁盤寫入結(jié)果,則可以使用commit()方法進行磁盤寫入操作扶平。