SharedPreferences原理淺析
1.概述
SharedPreferences是用來訪問和修改偏好preference數(shù)據(jù)的接口,可以通過Context.getSharedPreferences()方法返回SharedPreferences。
對(duì)于任意一組偏好設(shè)置數(shù)據(jù),只有一個(gè)共享的SharedPreferences實(shí)例。
修改preferences必須通過一個(gè)Editor對(duì)象來確保存儲(chǔ)數(shù)據(jù)的一致性以及控制數(shù)據(jù)何時(shí)存儲(chǔ)。
SharedPreferences是一個(gè)接口,里面定義了很多數(shù)據(jù)存儲(chǔ)與獲取的接口。
public interface SharedPreferences {
/*
* 定義了一個(gè)回調(diào)接口,當(dāng)SharedPreference被修改時(shí),觸發(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()方法返回一個(gè)SharedPreferences實(shí)現(xiàn),該實(shí)現(xiàn)是SharedPreferencesImpl類。接下來將來分析SharedPreferencesImpl實(shí)現(xiàn)類。
2.源碼分析
2.1 ContextImpl.getSharedPreferences()
public SharedPreferences getSharedPreferences(String name, int mode) {
// 在Android 4.4以前,如果name為null的話,則會(huì)把它當(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查找對(duì)應(yīng)的File文件是否存在,不存在,則根據(jù)name創(chuàng)建一個(gè)File文件,并把該File文件保存到一個(gè)Map集合中,以備后續(xù)使用
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);//見2.2
}
在ContextImpl中定義了兩個(gè)與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;
我們?cè)诓檎乙粋€(gè)SharedPreferences時(shí),首先需要根據(jù)SharedPreferences的name在mSharedPrefsPaths中查找到對(duì)應(yīng)的File文件,然后根據(jù)當(dāng)前應(yīng)用包名package name,在sSharedPrefsCache中查找當(dāng)前應(yīng)用包名對(duì)應(yīng)的ArrayMap<File, SharedPreferencesImpl>,最后根據(jù)File文件,查找對(duì)應(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)用包名,獲取對(duì)應(yīng)的ArrayMap<File, SharedPreferencesImpl>
sp = cache.get(file);//根據(jù)文件,獲取對(duì)應(yīng)的SharedPreferencesImpl
if (sp == null) {
sp = new SharedPreferencesImpl(file, mode);//創(chuàng)建一個(gè)新的ShardPreferenceImpl對(duì)象,見2.3
cache.put(file, sp);//存入緩存中
return sp;
}
}
// 如果是多進(jìn)程模式,或者是Android 3.0以前,則檢測(cè)是否有其他進(jìn)程在后臺(tái)修改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)用讀取這個(gè)模式創(chuàng)建的文件。在Android N上使用該模式將拋出SecurityException異常。
- MODE_WORLD_WRITEABLE:允許其他應(yīng)用寫入這個(gè)模式創(chuàng)建的文件。在Android N上使用該模式將拋出SecurityException異常。
- MODE_APPEND:如果文件已經(jīng)存在了,則在文件的尾部添加數(shù)據(jù)。
- MODE_MULTI_PROCESS:SharedPreference加載標(biāo)志,當(dāng)設(shè)置了該標(biāo)志,則在磁盤上的文件將會(huì)被檢查是否修改了,盡管SharedPreference實(shí)例已經(jīng)在該進(jìn)程中被加載了。
在checkMode()方法中,主要是檢查是否在Android N上使用了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE模式,如果使用了,則拋出異常。
在獲取SharedPreference時(shí),不是每次都會(huì)重新創(chuàng)建一個(gè)新的SharedPreference實(shí)例,而是先從緩存中,查找是否存在對(duì)應(yīng)的SharedPreference實(shí)例,如果有相應(yīng)的實(shí)例,則直接返回。如果不存在,則創(chuàng)建一個(gè)新的SharedPreference實(shí)例,并把它保存在緩存中,以備下次使用。
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)用包名對(duì)應(yīng)的ArrayMap<File, SharedPreferencesImpl>
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);//保存到緩存中
}
return packagePrefs;
}
由于ContextImpl是應(yīng)用的執(zhí)行環(huán)境,每一個(gè)應(yīng)用里面可以包含有多個(gè)SharedPreference文件。因此,為了更好的定位SharedPreference文件,首先根據(jù)應(yīng)用包名進(jìn)行篩選,得到ArrayMap<File, SharedPreferencesImpl>,然后再通過SharedPreference文件名進(jìn)行篩選,得到SharedPreferencesImpl。
可以看到,SharedPreferencesImpl只會(huì)被創(chuàng)建一次,之后會(huì)被保存在緩存中,后續(xù)的獲取操作都是從緩存中獲取SharedPreferencesImpl實(shí)例對(duì)象。
2.3 SharedPreferencesImpl()
SharedPreferencesImpl(File file, int mode) {
mFile = file;//保存SharedPreference對(duì)應(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)建一個(gè)線程來執(zhí)行從磁盤中加載數(shù)據(jù)
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();// 見2.5
}
}.start();
}
該方法主要是創(chuàng)建一個(gè)子線程,然后在子線程中執(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集合是一個(gè)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;//記錄時(shí)間戳
mStatSize = stat.st_size;//記錄文件大小
} else {
mMap = new HashMap<>();
}
notifyAll();//喚醒被阻塞的線程
}
}
可以看到,在loadFromDisk()方法中,主要的工作是將磁盤中的文件內(nèi)容讀取到內(nèi)存中,然后再喚醒阻塞等待的線程,告訴他們數(shù)據(jù)已經(jīng)讀取到內(nèi)存中了。這里用到了典型的wait()/notifyAll()機(jī)制,來同步線程之間的交互。那在什么時(shí)候會(huì)調(diào)用wait()呢?在我們獲取SharedPreferencesImpl來存儲(chǔ)數(shù)據(jù)和獲取數(shù)據(jù)時(shí),都會(huì)調(diào)用到wait()。
SharedPreferences數(shù)據(jù)的存儲(chǔ)需要借助Editor來實(shí)現(xiàn),通過Editor操作后,再通過commit()或apply()方法將修改同步到磁盤中。commit()方法是有返回結(jié)果的,來表示修改是否成功了。而apply()方法是沒有返回結(jié)果,它只是提交一個(gè)寫入磁盤的請(qǐng)求,然后由子線程去執(zhí)行。
Editor是通過SharedPreferencesImpl的edit()方法來創(chuàng)建的。
2.6 SharedPreferencesImpl.edit()
public Editor edit() {
synchronized (this) {//同步方法,保證每次只有一個(gè)線程執(zhí)行加載操作
awaitLoadedLocked();//等待SharedPreferencesImpl加載到內(nèi)存中,見2.7
}
return new EditorImpl();//創(chuàng)建一個(gè)新的Editor實(shí)現(xiàn)。
}
在創(chuàng)建Editor之前,需要等待SharedPreferencesImpl加載到內(nèi)存中,然后才會(huì)創(chuàng)建一個(gè)Editor實(shí)現(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()方法喚醒等待的線程。
因?yàn)閺拇疟P中加載SharedPreference數(shù)據(jù)到內(nèi)存中是一個(gè)耗時(shí)操作,因此需要在子線程中執(zhí)行加載操作,當(dāng)子線程加載完成后,需要給主線程發(fā)送一個(gè)通知,喚醒被阻塞等待的操作。
在開始創(chuàng)建SharedPreferencesImpl時(shí),就會(huì)從磁盤中加載xml文件到內(nèi)存中,加載完成后,將mLoaded置為true,并喚醒正在等待的線程。因?yàn)樵谕ㄟ^Context.getSharedPreferences()獲取到SharedPreferencesImpl時(shí),此時(shí)有可能數(shù)據(jù)并未全部都從磁盤加載到內(nèi)存中,因此需要在操作SharedPreferencesImpl之前,等待數(shù)據(jù)從磁盤加載到內(nèi)存中。awaitLoadedLocked()操作就是用來完成等待數(shù)據(jù)從磁盤加載到內(nèi)存中,該方法返回后,可以確保所有的數(shù)據(jù)都加載到內(nèi)存中了,后續(xù)的所有操作都是針對(duì)內(nèi)存中數(shù)據(jù)進(jìn)行操作了。
EditorImpl實(shí)現(xiàn)了Editor接口,獲取到EditorImpl之后,就可以通過Editor對(duì)SharedPreference中的數(shù)據(jù)進(jìn)行修改了。Editor的putXXX方法對(duì)數(shù)據(jù)的修改都只是在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行修改,只有調(diào)用了commit()或apply()方法之后,才會(huì)真正同步修改到磁盤中。
2.8 EditorImpl.putString()
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
EditorImpl的putXXX方法,主要是將數(shù)據(jù)保存在一個(gè)Map中,這些數(shù)據(jù)是存儲(chǔ)在內(nèi)存中,只有調(diào)用了commit()或apply()方法之后,才會(huì)同步到磁盤中。
private final Map<String, Object> mModified = Maps.newHashMap();//暫時(shí)保存需要寫入磁盤的數(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ù)寫入到磁盤時(shí),需要借助MemoryCommitResult類,構(gòu)建寫入磁盤的數(shù)據(jù)。然后將這個(gè)寫入操作由子線程來執(zhí)行,并等待子線執(zhí)行完成。當(dāng)子線程寫入完成后或者發(fā)生了異常,通知等待者寫入完成了,并把寫入結(jié)果返回給調(diào)用者。
2.10 EditorImpl.commitToMemory()
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();//創(chuàng)建MemoryCommitResult實(shí)例
synchronized (SharedPreferencesImpl.this) {
// 在準(zhǔn)備將內(nèi)存中的數(shù)據(jù)寫入到磁盤時(shí),如果已經(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)注冊(cè)的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對(duì)應(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)建一個(gè)MemoryCommitResult對(duì)象,該對(duì)象封裝了一些寫入磁盤的狀態(tài);
- 對(duì)mMap集合做一個(gè)深拷貝,并把它保存在MemoryCommitResult的mapToWriteToDisk變量中;
- 如果注冊(cè)了OnSharedPreferenceChangeListener監(jiān)聽者,則創(chuàng)建一個(gè)ArrayList列表,來保存被修改的key列表;
- 如果設(shè)置了清除標(biāo)志位mClear,則先清空mMap集合;
- 將EditorImpl中mModified集合中的數(shù)據(jù)拷貝到mMap集合中,如果key對(duì)應(yīng)value已經(jīng)存在了,則跳過拷貝。如果key對(duì)應(yīng)的value為null,則刪除該key對(duì)應(yīng)的Entry。
- 清空mModified集合,并返回創(chuàng)建的MemoryCommitResult對(duì)象。
將mModified集合中的數(shù)據(jù)拷貝到mMap集合中,具有緩存的作用,如果應(yīng)用再次馬上查詢SharedPreference時(shí),則可以先從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時(shí),如果之前的寫入操作都完成了的話,則mDiskWritesInFlight的值為1
}
if (wasEmpty) {
writeToDiskRunnable.run();//執(zhí)行run()方法,同步調(diào)用
return;
}
}
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);//如果之前還有寫入操作,則將該寫入操作放入工作隊(duì)列中,等待執(zhí)行。
}
在enqueueDiskWrite()方法中,首先根據(jù)參數(shù)postWriteRunnable是否為null來判斷是否同步寫入操作。接著創(chuàng)建一個(gè)寫入磁盤的Runnable,在該Runnable中執(zhí)行具體的寫入磁盤文件的操作。如果是同步寫入操作,并且當(dāng)前沒有寫入操作,則直接調(diào)用writeToDiskRunnable的run()方法,在當(dāng)前線程中執(zhí)行磁盤寫入操作。如果是同步寫入操作,并且當(dāng)前有正在執(zhí)行的寫入操作,則將該writeToDiskRunnable放入工作隊(duì)列中,等待線程隨后執(zhí)行。
QueuedWork.singleThreadExecutor()方法返回的是一個(gè)單個(gè)線程的執(zhí)行器Executor,里面有一個(gè)無界的隊(duì)列來保存執(zhí)行任務(wù)。這樣的話,可以保證任務(wù)是順序的執(zhí)行,并且保證每次只有一個(gè)任務(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)前文件更舊時(shí),才執(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)嘗試寫入文件時(shí),刪除備份文件,并返回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;//更新時(shí)間戳
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。不管寫入成功還是失敗,都會(huì)讓writtenToDiskLatch閉鎖計(jì)數(shù)減1,喚醒在閉鎖上等待的線程。
public void setDiskWriteResult(boolean result) {
writeToDiskResult = result;//保存是否寫入磁盤成功的結(jié)果
writtenToDiskLatch.countDown();//減少閉鎖計(jì)數(shù),喚醒在閉鎖上等待的操作
}
既然有通過閉鎖計(jì)數(shù)減1,喚醒等待線程的操作,就應(yīng)該也有等待閉鎖計(jì)算計(jì)數(shù)為0的地方。這個(gè)地方,在調(diào)用commit()方法時(shí)候,會(huì)調(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變化時(shí),首先判斷監(jiān)聽者是否空,或者SharedPreference是否發(fā)生了變化。然后在應(yīng)用主線程中將SharedPreference中的所有key修改通知給所有的監(jiān)聽者。
通過2.9~2.13的過程,描述了commit()的流程:
- 首先構(gòu)建一個(gè)寫入磁盤的輔助對(duì)象MemoryCommitResult,把mModified集合中的數(shù)據(jù)拷貝到mMap中,并把它保存到MemoryCommitResult的mapToWriteToDisk變量中;
- 如果當(dāng)前沒有寫入操作,則直接在當(dāng)前線程中執(zhí)行寫入操作;否則,封裝寫入操作到單線程任務(wù)隊(duì)列中,等待在其他線程中隨后執(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ù)隊(duì)列
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(awaitCommit);//從等待結(jié)束任務(wù)隊(duì)列中移除
}
};
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()方法的第二個(gè)參數(shù)不同。在commit()方法中,postWriteRunnable為null,因此執(zhí)行的同步寫入操作,而在apply()方法中,postWriteRunnable不為null,因此apply()中的所有寫入操作都是在單線程的Executor中執(zhí)行。
在寫入操作完成后,會(huì)執(zhí)行postWriteRunnable里面的run()方法,在該run()方法中,又執(zhí)行awaitCommit里面的run()方法,在該run()方法中,主要是等待寫入操作完成。由于postWriteRunnable是在寫入操作完成后執(zhí)行的,因此該等待操作立即返回。
因?yàn)閍pply()方法的寫入操作,都是在單線程的Executor中執(zhí)行的,不能確切知道什么時(shí)候執(zhí)行完成。那么如果想等待異步操作完成后立即返回,該如何做呢?在QueuedWork中,有一個(gè)等待執(zhí)行結(jié)束的任務(wù)隊(duì)列,在執(zhí)行任務(wù)之前,先將任務(wù)添加到任務(wù)隊(duì)列中,等待任務(wù)執(zhí)行完成后,則再從任務(wù)隊(duì)列中移除該任務(wù)。
public class QueuedWork {
private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers =
new ConcurrentLinkedQueue<Runnable>();
/*
* 添加一個(gè)任務(wù)到等待結(jié)束任務(wù)隊(duì)列中
*/
public static void add(Runnable finisher) {
sPendingWorkFinishers.add(finisher);
}
/*
* 從等待結(jié)束任務(wù)隊(duì)列中移除該任務(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ù)隊(duì)列中,然后調(diào)用QueuedWork.waitToFinish()方法等待異步操作執(zhí)行完成,異步操作執(zhí)行完成后,會(huì)調(diào)用QueuedWork.remove()方法,從等待結(jié)束任務(wù)隊(duì)列中移除該任務(wù)。
可以看到在apply()方法中,在執(zhí)行異步寫入操作之前,通過QueuedWork.add()方法,將任務(wù)添加到了等待結(jié)束的任務(wù)隊(duì)列中,當(dāng)執(zhí)行完寫入操作后,再通過QueuedWork.remove()方法移除在結(jié)束等待任務(wù)隊(duì)列中的任務(wù)。
前面主要是介紹了數(shù)據(jù)的存儲(chǔ)過程,主要是借助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ù)的過程比較簡(jiǎn)單,首先是等待SharedPreference加載到內(nèi)存中,加載完成后,直接從mMap集合查看對(duì)應(yīng)key的value是否存在。如果存在,則直接返回,如果不存在,則返回默認(rèn)值。
在commitToMemory()方法中,我們可以看到,在寫入磁盤之前,其實(shí)已經(jīng)將數(shù)據(jù)先從mModified集合拷貝到mMap集合中。這樣做的一個(gè)目的是,當(dāng)一個(gè)線程執(zhí)行putXXX()操作后,另外一個(gè)線程就可以通過getXXX()立即獲得相關(guān)的值,因?yàn)檫@些數(shù)據(jù)都是保存在內(nèi)存中,可以立即返回,而不用等待數(shù)據(jù)寫入到磁盤后,再從磁盤中獲取數(shù)據(jù)。這是因?yàn)榇疟P操作是一個(gè)耗時(shí)的操作,所以通過mMap集合在內(nèi)存中緩存結(jié)果。
3.小結(jié)
SharedPreference主要用來保存一些簡(jiǎn)單的值,例如int、String、Boolean等類型。
SharedPreference數(shù)據(jù)的存儲(chǔ)必須通過Editor類的putXXX()方法進(jìn)行保存,然后通過Editor的commit()和apply()方法將數(shù)據(jù)同步到磁盤中。
SharedPreference數(shù)據(jù)的獲取可以直接通過SharedPreference的getXXX()方法進(jìn)行獲取。
SharedPreference的數(shù)據(jù)本質(zhì)上是保存在一個(gè)xml文件中,這個(gè)xml文件存放在/data/data/應(yīng)用包名/shared_prefs/目錄下。
如果不需要數(shù)據(jù)寫入磁盤的結(jié)果,則可以使用apply()方法進(jìn)行磁盤寫入,該方法是在子線程中執(zhí)行。如果需要磁盤寫入結(jié)果,則可以使用commit()方法進(jìn)行磁盤寫入操作。