SharedPreference 初始化源碼解析
初始化
sp 內(nèi)部將數(shù)據(jù)放到 xml 文件中,加載時(shí)首先會(huì)將硬盤(pán)中文件讀取到內(nèi)存中,這樣加快了訪問(wèn)速度
這次從源碼開(kāi)始,看看里面具體做了什么
// 初始化 SharedPreferencesImpl(File file, int mode) { // 文件 mFile = file; //備份文件 .bak 結(jié)尾,看看什么時(shí)候排上作用,比如恢復(fù)數(shù)據(jù) mBackupFile = makeBackupFile(file); mMode = mode; mLoaded = false; mMap = null; mThrowable = null; // 從硬盤(pán)中讀取 startLoadFromDisk(); }
硬盤(pán)中讀取文件開(kāi)了新線程,主要將文件中的內(nèi)容,轉(zhuǎn)換為Map
private void loadFromDisk() { synchronized (mLock) { if (mLoaded) { return; } // 存在備份文件,刪除 file,為什么 if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } Map<String, Object> map = null; StructStat stat = null; Throwable thrown = null; stat = Os.stat(mFile.getPath()); // 讀取流 BufferedInputStream str = null; try { str = new BufferedInputStream( new FileInputStream(mFile), 16 * 1024); // 轉(zhuǎn)為 map map = (Map<String, Object>) XmlUtils.readMapXml(str); } catch (Exception e) { Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e); } finally { // 關(guān)閉流 IoUtils.closeQuietly(str); } }
流程很簡(jiǎn)單,就是讀取硬盤(pán),轉(zhuǎn)換為一個(gè) Map
apply,commit 區(qū)別
首先 apply,commit 分別是異步/同步的寫(xiě)入操作,它們都會(huì)先寫(xiě)入內(nèi)存中,也就是更新 Map,不同在于寫(xiě)入到硬盤(pán)的時(shí)機(jī)不同
- commit 先看 commit 做了什么 ,commit 方法將返回一個(gè)布爾值,表示結(jié)果
@Override public boolean commit() { // 先提交到內(nèi)存中 MemoryCommitResult mcr = commitToMemory(); // 執(zhí)行硬盤(pán)中的更新 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { // 提交異常,返回 false return false; } // 通知監(jiān)聽(tīng) notifyListeners(mcr); // 返回結(jié)果 return mcr.writeToDiskResult; }
- apply
@Override public void apply() { final long startTime = System.currentTimeMillis(); // 都是一樣的,先寫(xiě)到內(nèi)存 final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { @Override public void run() { // mcr.writtenToDiskLatch.await(); } }; // 往 sFinishers 隊(duì)列中添加,等待執(zhí)行 QueuedWork.addFinisher(awaitCommit); // 在寫(xiě)完后執(zhí)行 postWriteRunnable Runnable postWriteRunnable = new Runnable() { @Override public void run() { // 執(zhí)行 awaitCommit awaitCommit.run(); // sFinishers 隊(duì)列中移除 QueuedWork.removeFinisher(awaitCommit); } }; // 寫(xiě)入硬盤(pán) SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); }
硬盤(pán)中是如何更新的呢
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final boolean isFromSyncCommit = (postWriteRunnable == null); // 創(chuàng)建 Runnable 對(duì)象 final Runnable writeToDiskRunnable = new Runnable() { @Override public void run() { // 寫(xiě)到文件,這里的鎖是 mWritingToDiskLock 對(duì)象 synchronized (mWritingToDiskLock) { writeToFile(mcr, isFromSyncCommit); } synchronized (mLock) { mDiskWritesInFlight--; } // 執(zhí)行 postWriteRunnable, commit 這里為 null // apply 時(shí)不為i而空 if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; // Typical #commit() path with fewer allocations, doing a write on // the current thread. // 是否為同步提交 // 根據(jù) postWriteRunnable 是否為空, commit 這里為 true // apply if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (mLock) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); return; } } // 放到隊(duì)列中執(zhí)行,內(nèi)部是一個(gè) HandlerThread,按照隊(duì)列逐個(gè)執(zhí)行任務(wù) QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); }
這里用隊(duì)列來(lái)放任務(wù),應(yīng)該是要應(yīng)對(duì)多個(gè) commit 情況,這里將所有 commit 往隊(duì)列里面放,放完后就會(huì)執(zhí)行硬盤(pán)的寫(xiě),apply 也會(huì)調(diào)用到這里
public static void queue(Runnable work, boolean shouldDelay) { Handler handler = getHandler(); synchronized (sLock) { // 添加到 sWork 隊(duì)列中 sWork.add(work); // 異步 apply 走這個(gè) if (shouldDelay && sCanDelay) { handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY); } else { // 同步 commit 走這個(gè) handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN); } } }
apply 的硬盤(pán)寫(xiě)入,需要等待 Activity.onPause() 等時(shí)機(jī)才會(huì)執(zhí)行
讀取
讀取比寫(xiě)入就簡(jiǎn)單很多了
- 先查看是否從硬盤(pán)加載到了內(nèi)存,沒(méi)有就先去加載
- 從內(nèi)存中讀取
public String getString(String key, @Nullable String defValue) { synchronized (mLock) { // 檢查是否從硬盤(pán)加載到了內(nèi)存,沒(méi)有就先去加載 awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } }
如何保證線程安全的
通過(guò) sync 加對(duì)象鎖,內(nèi)存讀寫(xiě)都是用的同一把鎖,所以讀寫(xiě)都是線程安全的
數(shù)據(jù)恢復(fù)
存在備份機(jī)制
- 對(duì)文件進(jìn)行寫(xiě)入操作,寫(xiě)入成功時(shí),則將備份文件刪除
- 如果寫(xiě)入失敗,之后重新初始化時(shí),就使用備份文件恢復(fù)
SP 與 ANR
由于 Activity.onPause 會(huì)執(zhí)行 apply 的數(shù)據(jù)落盤(pán),里面是有等待鎖的,如果時(shí)間太長(zhǎng)就會(huì) ANR
小結(jié)
從 sp 的初始化,讀寫(xiě)方面簡(jiǎn)單分析了流程,sp 有數(shù)據(jù)恢復(fù)機(jī)制這是 mmkv 所欠缺的,但在多進(jìn)程環(huán)境下,sp 是不可靠的
相關(guān)鏈接
- 官方也無(wú)力回天?Android SharedPreferences的設(shè)計(jì)與實(shí)現(xiàn)
- 剖析 SharedPreference apply 引起的 ANR 問(wèn)題
以上就是SharedPreference 初始化源碼解析的詳細(xì)內(nèi)容,更多關(guān)于SharedPreference 初始化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android帶數(shù)字或紅點(diǎn)的底部導(dǎo)航攔和聯(lián)網(wǎng)等待加載動(dòng)畫(huà)示例
這篇文章主要介紹了Android帶數(shù)字或紅點(diǎn)的底部導(dǎo)航攔和聯(lián)網(wǎng)等待加載動(dòng)畫(huà)示例,具有一定的參考價(jià)值,有興趣的同學(xué)可以了解一下。2017-03-03Android權(quán)限機(jī)制帶來(lái)的一些安全問(wèn)題介紹
這篇文章主要介紹了Android權(quán)限機(jī)制帶來(lái)的一些安全問(wèn)題介紹,本文講解了權(quán)限機(jī)制的缺陷和不足、樹(shù)立權(quán)限意識(shí)、越過(guò)權(quán)限機(jī)制等內(nèi)容,需要的朋友可以參考下2015-04-04Android自定義控件實(shí)現(xiàn)時(shí)鐘效果
這篇文章主要介紹了Android自定義控件實(shí)現(xiàn)時(shí)鐘效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12Android Studio如何快速導(dǎo)入jar和.so文件
這篇文章主要介紹了Android Studio如何快速導(dǎo)入jar和.so文件的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-12-12Android實(shí)現(xiàn)新增及編輯聯(lián)系人的方法
這篇文章主要介紹了Android實(shí)現(xiàn)新增及編輯聯(lián)系人的方法,是Android應(yīng)用開(kāi)發(fā)常見(jiàn)的功能,需要的朋友可以參考下2014-07-07Android 開(kāi)發(fā)環(huán)境配置問(wèn)題
手機(jī)軟件開(kāi)發(fā)IDE,本人在同時(shí)使用 Eclipse 和 Netbeans 后,感覺(jué) Eclipse 更適合自己2012-04-04Android關(guān)鍵字persistent詳細(xì)分析
這篇文章主要介紹了Android關(guān)鍵字persistent的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04Android實(shí)現(xiàn)Recycleview懸浮粘性頭部外加右側(cè)字母導(dǎo)航
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)Recycleview懸浮粘性頭部外加右側(cè)字母導(dǎo)航,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06