SharedPreferences存取數(shù)據(jù)流程分析
SharedPreferencesImpl
今天研究一下SharedPreferences存取數(shù)據(jù)的實(shí)現(xiàn)兽埃,在Android中SharedPreferences是一個(gè)接口,真正的實(shí)現(xiàn)類是SharedPreferencesImpl魁兼,下面就開始分析SharedPreferencesImpl典徊,首先查看相關(guān)的屬性
private final File mFile;// 存放數(shù)據(jù)的文件
private final File mBackupFile;// 更新數(shù)據(jù)時(shí)的備份文件
private final int mMode; //sharedPreferences的模式
private final Object mLock = new Object();// 鎖
private final Object mWritingToDiskLock = new Object(); // 寫入磁盤的鎖
private Map<String, Object> mMap; // 存放的數(shù)據(jù)映射
private int mDiskWritesInFlight = 0; // 排隊(duì)寫入磁盤的任務(wù)數(shù)
private long mDiskStateGeneration; // 最后一次提交寫入磁盤的state颤专,自增的
private long mCurrentMemoryStateGeneration; // 當(dāng)前內(nèi)存數(shù)據(jù)的state, 自增的
private boolean mLoaded = false; // 是否已經(jīng)在加載文件的數(shù)據(jù)
下面在看下構(gòu)造函數(shù)
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file); //構(gòu)造出備份文件
mMode = mode;
mLoaded = false;//置為false
mMap = null;
mThrowable = null;
startLoadFromDisk(); //開始加載文件數(shù)據(jù)
}
//開個(gè)線程做加載數(shù)據(jù)的操作
private void startLoadFromDisk() {
synchronized (mLock) {
// mLoaded置為false
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
//構(gòu)造個(gè)備份文件
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
下面再看下真正加載數(shù)據(jù)的方法逮光,loadFromDisk()
private void loadFromDisk() {
synchronized (mLock) {
// 判斷是否已經(jīng)加載過(guò)了代箭,不重復(fù)加載
if (mLoaded) {
return;
}
// 如果備份文件存在,則說(shuō)明上次 創(chuàng)建/更新 操作中涕刚,寫入新的數(shù)據(jù)過(guò)程中發(fā)生了異常嗡综,此時(shí)繼續(xù)采用備份文件的數(shù)據(jù),放棄上次操作
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<String, Object> map = null;
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
// 構(gòu)造存放文件的數(shù)據(jù)流杜漠,從mFile讀數(shù)據(jù)
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
// 讀取數(shù)據(jù)生成map
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) {
...
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
// mLoaded置為true
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 {
// 如果正常加載完成极景,沒(méi)有異常
if (thrown == null) {
// 如果數(shù)據(jù)不為空
if (map != null) {
mMap = map;//把加載的數(shù)據(jù)賦值給mMap
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
// 數(shù)據(jù)為空則初始化一個(gè)mMap
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的線程
mLock.notifyAll();
}
}
}
- 從mFile中加載數(shù)據(jù)碑幅,這個(gè)過(guò)程只加載一次戴陡,如果已經(jīng)加載過(guò)了,則不再加載
- 如果加載的過(guò)程中沟涨,發(fā)現(xiàn)備份文件還存在恤批,那么就說(shuō)明上次 新建/更新 數(shù)據(jù)時(shí)發(fā)生了異常,此時(shí)mFile文件的數(shù)據(jù)得不到保障裹赴,所以繼續(xù)采用備份文件的數(shù)據(jù)喜庞,放棄上次的操作
- 如果mFile里面沒(méi)有數(shù)據(jù),則重新初始化mMap棋返,如果有數(shù)據(jù)延都,則把讀取出來(lái)的數(shù)據(jù)賦值給mMap
- 數(shù)據(jù)讀取成功后,喚醒等待加載數(shù)據(jù)的線程
因?yàn)镾haredPreferences里面只有讀取數(shù)據(jù)的方法睛竣,下面看下getInt()
@Override
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();//判斷數(shù)據(jù)是否加載完成晰房,沒(méi)有加載完成,則阻塞等待
Integer v = (Integer)mMap.get(key);//從mMap中去數(shù)據(jù)
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);
}
}
- 首先判斷數(shù)據(jù)是否加載完成,如果沒(méi)有加載完成殊者,則阻塞當(dāng)前線程与境。加載完成后會(huì)被加載數(shù)據(jù)線程喚醒
- 數(shù)據(jù)加載完成后,從mMap中去數(shù)據(jù)
- 如果mMap中有數(shù)據(jù)則返回猖吴,沒(méi)有的話返回默認(rèn)值
其他的讀取數(shù)據(jù)的方法也是類似的,還有另外一個(gè)方法需要注意下
@Override
public Map<String, ?> getAll() {
synchronized (mLock) {
awaitLoadedLocked();
//noinspection unchecked
return new HashMap<String, Object>(mMap);
}
}
調(diào)用這個(gè)方法會(huì)返回當(dāng)前shardePreferences的所有數(shù)據(jù)摔刁,但是一定不要更改里面的內(nèi)容,因?yàn)槿绻牧藘?nèi)容海蔽,那么SharedPreferences的數(shù)據(jù)一致性就得不到保證了共屈。
下面再分析下ShardePreferences的新增,更新和刪除的方法党窜,這些方法就需要EditorImpl 這個(gè)類來(lái)做
EditorImpl
private final Object mEditorLock = new Object();// 鎖
private final Map<String, Object> mModified = new HashMap<>(); // 更改的數(shù)據(jù)
private boolean mClear = false; //是否清空數(shù)據(jù)
下面看下putString()拗引,其他的putXXX()類似
@Override
public Editor putString(String key, @Nullable String value) {
// 加鎖,保證線程安全刑然,數(shù)據(jù)一致性
synchronized (mEditorLock) {
mModified.put(key, value);// 把值放入mModified
return this;
}
}
- 添加或者更新數(shù)據(jù)時(shí)寺擂,都會(huì)加鎖暇务,保證線程安全泼掠,數(shù)據(jù)一致性。然后會(huì)把數(shù)據(jù)放入mModified
添加完數(shù)據(jù)后還需要更新到文件和內(nèi)存中垦细,一般會(huì)調(diào)用commit()和apply()下面先看看apply()
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
// 首先更新內(nèi)存中的數(shù)據(jù)择镇,
final MemoryCommitResult mcr = commitToMemory();
// 這是等待提交到磁盤的任務(wù)
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
// 如果寫入磁盤任務(wù)未完成,則一直等待writtenToDiskLatch是CountDownLatch
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
// 把a(bǔ)waitCommit 添加QueuedWork的finisher的任務(wù)隊(duì)列
QueuedWork.addFinisher(awaitCommit);
// 磁盤任務(wù)寫完之后需要執(zhí)行任務(wù)
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
// 執(zhí)行awaitCommit.run()
awaitCommit.run();
// 把a(bǔ)waitCommit 移除finisher隊(duì)列
QueuedWork.removeFinisher(awaitCommit);
}
};
// 調(diào)用寫入磁盤的任務(wù)
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);
}
上面的代碼中引入了MemoryCommitResult括改,MemoryCommitResult就是用來(lái)記錄更新的數(shù)據(jù)提交到內(nèi)存中的結(jié)果腻豌,比較簡(jiǎn)單,會(huì)放在下面再看
- 首先把數(shù)據(jù)更新到內(nèi)存中
- 構(gòu)造兩個(gè)任務(wù)嘱能,awaitCommit和postWriteRunnable
- 調(diào)用enqueueDiskWrite()吝梅,傳入postWriteRunnable
繼續(xù)追蹤enqueueDiskWrite
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// commit()方法傳入的postWriteRunnable為null,而apply()傳入的不為null
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 寫入文件
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
// 運(yùn)行postWriteRunnable
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
// 如果isFromSyncCommit為true
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
// 如果提交的寫入磁盤數(shù)據(jù)的任務(wù)數(shù)為1惹骂,那么就直接執(zhí)行苏携,不再交給QueuedWork
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
// 默認(rèn)交給QueuedWork執(zhí)行
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
- 給isFromSyncCommit賦值,commit()方法中postWriteRunnable对粪,及isFromSyncCommit為true右冻,而apply()為false
- 構(gòu)建一個(gè)將數(shù)據(jù)寫入磁盤的任務(wù)writeToDiskRunnable
- isFromSyncCommit為true,并且寫入磁盤的任務(wù)數(shù)為1著拭,那么就會(huì)直接在當(dāng)前線程執(zhí)行纱扭,否則提交給QueuedWork,放在子線程中執(zhí)行
上面有個(gè)關(guān)鍵的方法writeToFile()
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
...
boolean fileExists = mFile.exists();
...
if (fileExists) {
boolean needsWrite = false;
// Only need to write if the disk state is older than this commit
//只有當(dāng)前磁盤的state小于內(nèi)存的state儡遮,才會(huì)執(zhí)行寫入磁盤的任務(wù)
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
// 如果不是同步寫入的話乳蛾,那么只有當(dāng)前內(nèi)存state等于mcr.memoryStateGeneration才會(huì)執(zhí)行寫入
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
// 如果不需要寫入,則返回
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
boolean backupFileExists = mBackupFile.exists();
....
// 如果備份文件不存在
if (!backupFileExists) {
// 把mFile重命名為備份文件名,如果執(zhí)行失敗肃叶,則返回
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false, false);
return;
}
} else {
// 如果備份文件已經(jīng)存在了忆首,則把mFile文件刪除
mFile.delete();
}
}
// 嘗試寫入數(shù)據(jù)到mFile,如果寫入成功則把備份文件刪除被环;如果寫入的過(guò)程中發(fā)生了異常糙及,則把新文件刪除,下次會(huì)從備份文件讀取數(shù)據(jù)筛欢。這里就跟讀取數(shù)據(jù)的時(shí)候關(guān)聯(lián)上了
try {
FileOutputStream str = createFileOutputStream(mFile);
// 如果數(shù)據(jù)流為null
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
// 把mcr.mapToWriteToDisk數(shù)據(jù)寫入文件浸锨,此時(shí)所有的數(shù)據(jù)包括更新/新增的數(shù)據(jù)都在mcr.mapToWriteToDisk里面,因?yàn)樵赾ommitToMemory處理版姑,下面會(huì)分析commitToMemory
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
}
// 寫入數(shù)據(jù)成功柱搜,刪除備份文件
mBackupFile.delete();
// 更新磁盤的mDiskStateGeneration為mcr.memoryStateGeneration
mDiskStateGeneration = mcr.memoryStateGeneration;
...
mcr.setDiskWriteResult(true, true);
mNumSync++;
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// 如果寫入數(shù)據(jù)的過(guò)程中發(fā)生了異常,那么就刪除mFile文件剥险,下次加載時(shí)會(huì)直接讀取備份文件
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false, false);
}
上面是分析了寫入磁盤緩存的方法聪蘸,下面再看下寫入內(nèi)存緩存的方法commitToMemory()
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
// 只會(huì)在調(diào)用了commit和apply方法時(shí),才會(huì)執(zhí)行內(nèi)存緩存表制,把數(shù)據(jù)更新到內(nèi)存
if (mDiskWritesInFlight > 0) {
// 如果當(dāng)前正在向磁盤寫入mMap健爬,那么禁止修改mMap
// 對(duì)當(dāng)前的mMap進(jìn)行clone,下面就只會(huì)操作這個(gè)mMap,因?yàn)閔ashmap不是線程安全么介,SharedPreference最重要的必須保證的就是數(shù)據(jù)一致性
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;//把mMap賦值給mapToWriteToDisk
mDiskWritesInFlight++;// 寫入磁盤的任務(wù)數(shù) 加1
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
// 如果調(diào)用了clear方法娜遵,clear標(biāo)志就會(huì)為true
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
// 清楚數(shù)據(jù),此時(shí)清楚的只是mMap里面的數(shù)據(jù)壤短,并沒(méi)有清除mModified设拟,即更新后的數(shù)據(jù)沒(méi)有清除
mapToWriteToDisk.clear();
}
mClear = false;
}
// 遍歷修改的數(shù)據(jù)
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// 如果v == null 或者v== this,這里this就是EditorImpl對(duì)象,就代表需要移除當(dāng)前k
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;
}
}
// 把k久脯,v寫入mapToWriteToDisk
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
// 修改后的數(shù)據(jù)集清空纳胧,此時(shí)所有的數(shù)據(jù),原來(lái)的數(shù)據(jù)和更新后的數(shù)據(jù)全部在mapToWriteToDisk里面
mModified.clear();
// 如果有更新
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
// 更新memoryStateGeneration為mCurrentMemoryStateGeneration
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
只有在調(diào)用了commit或者apply方法時(shí)帘撰,才會(huì)調(diào)用這個(gè)方法跑慕。這個(gè)方法比較簡(jiǎn)單,就是把mModified中的數(shù)據(jù)和原來(lái)的數(shù)據(jù)mMap做添加骡和,更新相赁,刪除的操作。得到最新的數(shù)據(jù)mapToWriteToDisk慰于,更新內(nèi)存狀態(tài)钮科,最終構(gòu)造成MemoryCommitResult返回
上面是apply方法,下面再看commit()婆赠,跟apply基本一樣
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
MemoryCommitResult mcr = commitToMemory();//更新內(nèi)存的映射
// 執(zhí)行硬盤緩存的任務(wù)绵脯,傳入null佳励,表示可以在當(dāng)前線程執(zhí)行寫入磁盤的操作
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
// 這里是關(guān)鍵,這里會(huì)阻塞當(dāng)前線程 直到磁盤緩存任務(wù)執(zhí)行完成
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;
}
分析完apply方法后commit()就很容易了蛆挫,commit跟apply的區(qū)別就在與commit允許在當(dāng)前線程執(zhí)行寫入磁盤的任務(wù)赃承,并且不管寫入磁盤是否在當(dāng)前任務(wù)執(zhí)行,commit都會(huì)阻塞當(dāng)前線程悴侵,等待磁盤更新完成瞧剖。
MemoryCommitResult
這個(gè)類比較簡(jiǎn)單,先看相關(guān)的屬性
final long memoryStateGeneration;// 當(dāng)前的內(nèi)存state
@Nullable final List<String> keysModified;//做了改變的值的列表
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
final Map<String, Object> mapToWriteToDisk;// 存放此次內(nèi)存更新后的數(shù)據(jù)
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);//用來(lái)等待磁盤寫入完成
只有一個(gè)關(guān)鍵方法
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();// writtenToDiskLatch計(jì)數(shù)減一,喚醒等待此任務(wù)的線程
}
上面的方法是在設(shè)置最終磁盤更新任務(wù)的結(jié)果可免,并且將writtenToDiskLatch計(jì)數(shù)減一抓于,這樣等待磁盤更新任務(wù)的線程會(huì)被喚醒,繼續(xù)執(zhí)行浇借。