先給出結(jié)論茉继,如果不想跟隨源碼分析的寄狼,可以根據(jù)結(jié)論,對(duì)SharedPreferences有個(gè)大概的了解喻圃。
結(jié)論:
1.SharedPreferences 線程是安全的,內(nèi)部由大量synchronized
代碼塊實(shí)現(xiàn)同步
2.SharedPreferences 進(jìn)程是不安全的(雖然官方提供了 ),但官方文檔也給出了如下說(shuō)明:MODE_MULTI_PROCESS
加載模式粪滤,該加載模式在API級(jí)別11中添加斧拍,在API級(jí)別23中被廢棄。該SharedPreference loading標(biāo)志:設(shè)置后杖小,即使已在此過(guò)程中加載了共享首選項(xiàng)實(shí)例肆汹,也會(huì)檢查磁盤(pán)上的文件是否已修改。在應(yīng)用程序具有多個(gè)進(jìn)程的情況下予权,有時(shí)需要此行為昂勉,所有進(jìn)程都寫(xiě)入相同的SharedPreferences文件。但是扫腺,通常在進(jìn)程之間存在更好的通信形式岗照。
此常量在API級(jí)別23中已棄用
.MODE_MULTI_PROCESS在某些Android版本中無(wú)法可靠地工作笆环,并且不提供任何協(xié)調(diào)跨進(jìn)程的并發(fā)修改的機(jī)制攒至。應(yīng)用程序不應(yīng)嘗試使用它。相反躁劣,他們應(yīng)該使用明確的跨流程數(shù)據(jù)管理方法迫吐,例如ContentProvider。
3.首次getSharedPreferences
會(huì)把文件從磁盤(pán)加載到內(nèi)存(用Map進(jìn)行保存)账忘,之后調(diào)用getSharedPreferences
獲取數(shù)據(jù)志膀,即從內(nèi)存中直接獲取,也就不會(huì)由于大量的synchronized
而影響性能
4.apply
:同步修改內(nèi)存中的數(shù)據(jù)闪萄,然后異步回寫(xiě)到磁盤(pán)且是將其放入單線程任務(wù)隊(duì)列中執(zhí)行
commit
:同步修改內(nèi)存中的數(shù)據(jù)梧却,且同步回寫(xiě)到磁盤(pán),因此會(huì)阻塞當(dāng)前線程
建議:
1.不要使用多進(jìn)程操作SharedPreferences 败去,小概率會(huì)出現(xiàn)數(shù)據(jù)丟失(文件被刪除)
2.不要存儲(chǔ)大量的數(shù)據(jù)放航,從磁盤(pán)加載SharedPreferences 文件是進(jìn)行I/O操作的,并且由于文件結(jié)構(gòu)是xml格式的圆裕,加載解析都比較耗時(shí)广鳍,雖然在此過(guò)程中是異步的,但如果數(shù)據(jù)過(guò)大吓妆,首次調(diào)用getSharedPreferences
后赊时,然后馬上執(zhí)行getXX()
或者putXX()
由于需要等待加載文件結(jié)束,可能會(huì)阻塞線程(如果在UI線程可能會(huì)引發(fā)ANR)行拢。并且SharedPreferences 中的數(shù)據(jù)會(huì)一次性從磁盤(pán)加載到內(nèi)存祖秒,.apply
、commit
都是會(huì)將數(shù)據(jù)寫(xiě)到磁盤(pán)的,SharedPreferences 文件不應(yīng)過(guò)大會(huì)影響性能
3.盡量使用apply更新數(shù)據(jù)竭缝,因?yàn)樗钱惒綄?xiě)入磁盤(pán)的房维,不會(huì)阻塞線程
將您的首選項(xiàng)更改從此編輯器提交回它正在編輯的SharedPreferences對(duì)象。這將自動(dòng)執(zhí)行請(qǐng)求的修改抬纸,替換SharedPreferences中當(dāng)前的內(nèi)容咙俩。
注意,當(dāng)兩個(gè)編輯器同時(shí)修改首選項(xiàng)時(shí)湿故,最后一個(gè)調(diào)用apply的編輯器將獲勝阿趁。
與同步地將其首選項(xiàng)寫(xiě)到持久存儲(chǔ)的commit()不同,apply()將其更改立即提交到內(nèi)存中的SharedPreferences坛猪,但是啟動(dòng)到磁盤(pán)的異步提交脖阵,并且不會(huì)通知您任何失敗。如果這個(gè)SharedPreferences上的另一個(gè)編輯器在apply()仍然未完成時(shí)執(zhí)行常規(guī)提交()砚哆,那么commit()將阻塞独撇,直到所有異步提交和提交本身都完成為止。
由于SharedPreferences實(shí)例是流程中的單例實(shí)例躁锁,如果已經(jīng)忽略了返回值纷铣,那么可以用apply()替換commit()的任何實(shí)例。
您不需要擔(dān)心Android組件的生命周期以及它們與向磁盤(pán)寫(xiě)入apply()的交互战转。該框架確保在切換狀態(tài)之前完成來(lái)自apply()的在途磁盤(pán)寫(xiě)操作搜立。
源碼分析:
我們通過(guò)調(diào)用getSharedPreferences
獲取SharedPreferences對(duì)象時(shí),最終會(huì)調(diào)用ContextImpl.getSharedPreferences(File file, int mode)
方法:
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//獲取緩存中所有SharedPreferences對(duì)象集合
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
//緩存中是否已存在我們需要的SharedPreferences
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage()
&& !getSystemService(UserManager.class)
.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
throw new IllegalStateException("SharedPreferences in credential encrypted "
+ "storage are not available until after user is unlocked");
}
}
//如果緩存中不存在槐秧,則通過(guò)創(chuàng)建SharedPreferencesImpl對(duì)象啄踊,由其加載文件
sp = new SharedPreferencesImpl(file, mode);
//將SharedPreferences對(duì)象存入緩存集合中,以便下次直接從內(nèi)存中獲取
cache.put(file, sp);
return sp;
}
}
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;
}
先從緩存中獲取刁标,如果未命中則創(chuàng)建SharedPreferencesImpl對(duì)象颠通,并保存到緩存中,多次調(diào)用getSharedPreferences
并不會(huì)多次創(chuàng)建對(duì)象膀懈,由于整個(gè)SharedPreferences獲取過(guò)程中都放在synchronized
代碼塊中顿锰,因此在多線程下創(chuàng)建對(duì)象是安全的
SharedPreferencesImpl
創(chuàng)建過(guò)程:
SharedPreferencesImpl(File file, int mode) {
//需要加載的SharedPreferences文件對(duì)象
mFile = file;
//備份文件對(duì)象,寫(xiě)入失敗時(shí)會(huì)通過(guò)其進(jìn)行數(shù)據(jù)恢復(fù)
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
//數(shù)據(jù)緩存集合启搂,用于存儲(chǔ)SharedPreferences文件中的數(shù)據(jù)
mMap = null;
mThrowable = null;
//從磁盤(pán)加載數(shù)據(jù)
startLoadFromDisk();
}
下面我們看看從磁盤(pán)加載數(shù)據(jù)過(guò)程:
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
//從磁盤(pán)加載數(shù)據(jù)
loadFromDisk();
}
}.start();
}
另起一個(gè)線程加載SharedPreferences文件
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
···省略代碼···
str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
map = (Map<String, Object>) XmlUtils.readMapXml(str);
···省略代碼···
synchronized (mLock) {
mLoaded = true;
mThrowable = thrown;
···省略代碼···
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
...
mLock.notifyAll();
}
}
從上面方法可知從磁盤(pán)加載SharedPreferences文件分別做了一下幾步處理:
1.如果由備份文件硼控,把原文件刪除,然后修改備份文件名稱(chēng)胳赌,使其成為原文件牢撼,達(dá)到文件恢復(fù)效果(備份文件名以.bak結(jié)尾)
2.加載文件并解析成Map
3.標(biāo)記加載完成
4.將Map賦值給mMap(即數(shù)據(jù)緩存集合,用于存儲(chǔ)SharedPreferences文件中的數(shù)據(jù))
5.通過(guò)notifyAll
喚起所有等待加載的線程
現(xiàn)在SharedPreferences對(duì)象已經(jīng)初始化成功了疑苫,下面我們看一下對(duì)其數(shù)據(jù)操作的流程:
getXX()
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
//等待SharedPreferences加載完成
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
通過(guò)synchronized
代碼塊熏版,可見(jiàn)獲取數(shù)據(jù)是線程安全的纷责,僅僅是從緩存中獲取數(shù)據(jù),同步不會(huì)影響性能纳决。而awaitLoadedLocked()
即是等待SharedPreferences文件從磁盤(pán)加載到內(nèi)存完畢后再執(zhí)行下面的獲取數(shù)據(jù)的方法碰逸,因此如果在首次調(diào)用getSharedPreferences
時(shí)乡小,馬上調(diào)用getXX()
阔加,會(huì)阻塞線程
接著看看awaitLoadedLocked()
里面做了如何處理:
private void awaitLoadedLocked() {
if (!mLoaded) {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
可見(jiàn),除了首次加載文件時(shí)满钟,mLoaded = false
會(huì)卡在此處等待胜榔,當(dāng)文件加載完畢后會(huì)重置加載狀態(tài)即mLoaded = true
,則不會(huì)等待直接獲取數(shù)據(jù)
putXX()
修改數(shù)據(jù)時(shí)首先得獲取Editor對(duì)象(SharedPreferencesImpl.java)
@Override
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
public final class EditorImpl implements Editor {
private final Object mEditorLock = new Object();
@GuardedBy("mEditorLock")
private final Map<String, Object> mModified = new HashMap<>();
@GuardedBy("mEditorLock")
private boolean mClear = false;
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
···代碼省略···
}
可見(jiàn)我們調(diào)用putXX()
僅僅是將數(shù)據(jù)存到mModified
中湃番,直到我們調(diào)用apply
或commit
時(shí)才真正將數(shù)據(jù)同步到緩存并寫(xiě)入磁盤(pán)
apply()
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
//同步數(shù)據(jù)到緩存
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//將寫(xiě)入磁盤(pán)任務(wù)加入隊(duì)列
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);
}
下面看看同步緩存數(shù)據(jù)流程:
private MemoryCommitResult commitToMemory() {
···代碼省略···
synchronized (SharedPreferencesImpl.this.mLock) {
···代碼省略···
//緩存數(shù)據(jù)集合
mapToWriteToDisk = mMap;
···代碼省略···
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
//同步緩存數(shù)據(jù)
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
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;
}
}
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,mapToWriteToDisk);
}
然后我們瞅瞅?qū)懭氪疟P(pán)SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
//如果是commit則postWriteRunnable == null即isFromSyncCommit = true
//如果是apply則isFromSyncCommit = false
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//寫(xiě)入磁盤(pán)
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// commit直接在當(dāng)前線程直接同步寫(xiě)入磁盤(pán)
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//apply加入任務(wù)隊(duì)列執(zhí)行寫(xiě)入磁盤(pán)runnable
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
//sCanDelay用于紀(jì)錄當(dāng)前線程是否處于寫(xiě)入磁盤(pán)任務(wù)狀態(tài)
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
//首次創(chuàng)建一個(gè)線程
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
//創(chuàng)建一個(gè)handler用于接收queue(Runnable work, boolean shouldDelay)發(fā)送的消息夭织,然后執(zhí)行寫(xiě)入磁盤(pán)runnable
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
commit()
@Override
public boolean commit() {
long startTime = 0;
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */);
try {
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;
}
由此可見(jiàn),不論commit/apply都回調(diào)用SharedPreferencesImpl.this.enqueueDiskWrite
然后同步/異步執(zhí)行寫(xiě)入磁盤(pán)操作