SharedPreference 初始化源碼解析
初始化
sp 內(nèi)部將數(shù)據(jù)放到 xml 文件中,加載時(shí)首先會(huì)將硬盤(pán)中文件讀取到內(nèi)存中,這樣加快了訪(fǎng)問(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)了新線(xiàn)程,主要將文件中的內(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;
}
}
如何保證線(xiàn)程安全的
通過(guò) sync 加對(duì)象鎖,內(nèi)存讀寫(xiě)都是用的同一把鎖,所以讀寫(xiě)都是線(xiàn)程安全的
數(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ì)交互的實(shí)現(xiàn)方法
本篇文章是對(duì)Android中手勢(shì)交互的實(shí)現(xiàn)進(jìn)行了詳細(xì)的分析介紹。需要的朋友參考下2013-05-05
詳解flutter中常用的container layout實(shí)例
這篇文章主要為大家介紹了詳解flutter中常用的container layout實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
Android setButtonDrawable()的兼容問(wèn)題解決辦法
這篇文章主要介紹了Android setButtonDrawable()的兼容問(wèn)題解決辦法的相關(guān)資料,需要的朋友可以參考下2017-03-03
Android studio設(shè)置指定的簽名文件教程
這篇文章主要介紹了Android studio設(shè)置指定的簽名文件教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-03-03
Android項(xiàng)目實(shí)戰(zhàn)之百度地圖地點(diǎn)簽到功能
這篇文章主要介紹了Android項(xiàng)目實(shí)戰(zhàn)之百度地圖地點(diǎn)簽到功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
使用WEB工具快速提高Android開(kāi)發(fā)效率
正所謂工欲善其事,必先利其器。學(xué)習(xí)并應(yīng)用優(yōu)秀的輪子,可以讓我們跑的更快,走的更遠(yuǎn)。這里所指的工具是廣義的,泛指能幫助我們開(kāi)發(fā)的東西,或者能提高我們效率的東西,包括:開(kāi)發(fā)工具,監(jiān)測(cè)工具,第三方代碼庫(kù)等2016-02-02
Android中使用開(kāi)源框架Citypickerview實(shí)現(xiàn)省市區(qū)三級(jí)聯(lián)動(dòng)選擇
這篇文章主要介紹了Android中使用開(kāi)源框架Citypickerview實(shí)現(xiàn)省市區(qū)三級(jí)聯(lián)動(dòng)選擇效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03

