起因
在項(xiàng)目開(kāi)發(fā)中發(fā)現(xiàn)很多數(shù)據(jù)都是用SharedPreferences做本地保存的,操作SharedPreferences只需要建立Editor阴绢,然后這個(gè)向這個(gè)Editor對(duì)象put各種各樣的鍵值對(duì)吧享,最后調(diào)用它的commit或者apply保存信息即可甲葬,非常簡(jiǎn)單方便鸡挠。同時(shí)注釋文檔中說(shuō)明commit是同步的而apply是異步的辉饱,說(shuō)明SharedPreferences是支持多線程的,那就有些疑惑了:
- 假設(shè)要保存的數(shù)據(jù)很多拣展,apply異步線程保存彭沼,同時(shí)主線程再去讀取數(shù)據(jù),讀取數(shù)據(jù)會(huì)不會(huì)等待备埃?
- 在異步保存時(shí)退出Activity姓惑,退出應(yīng)用有影響嗎?
下面將帶著這兩個(gè)問(wèn)題去學(xué)習(xí)SharedPreferences的實(shí)現(xiàn)
分析
1.獲取SP對(duì)象
調(diào)用Activity的getSharedPreferences方法,發(fā)現(xiàn)這是父類ContextWrapper的方法按脚,如下:
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
這個(gè)方法只是再次調(diào)用mBase的相應(yīng)方法于毙,mBase是ContextImpl對(duì)象,而每一個(gè)ContextWrapper都會(huì)被綁定ContextImpl辅搬,ContextWarpper子類有Application望众、Service、Activity伞辛,所以在這三個(gè)常見(jiàn)的Context中都可以使用SP烂翰。
接著看看ContextImpl中的getSharedPreferences的實(shí)現(xiàn):
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {//用的是類鎖,不是對(duì)象鎖
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);//創(chuàng)建File對(duì)象蚤氏,name + ".xml"
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
上面的mSharedPrefsPaths是一個(gè)Map對(duì)象甘耿,它保存的是SP的名字與文件的對(duì)應(yīng)關(guān)系,如果沒(méi)有對(duì)應(yīng)的File就創(chuàng)建一個(gè)File對(duì)象竿滨,注意mSharedPrefsPaths是對(duì)象的成員佳恬,但是在操作這個(gè)對(duì)象時(shí)用的不是對(duì)象鎖而是類鎖,為什么于游?接著看最后調(diào)用的它的重載方法毁葱,如下:
/**
* Map from package name, to preference name, to cached preferences.
靜態(tài)的sSharedPrefsCache
*/
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//file -> SP 的map
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
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");
}
}
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
//獲取靜態(tài)的sSharedPrefsCache
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
在這里創(chuàng)建、返回了正兒八經(jīng)干活的SharedPreferencesImpl對(duì)象贰剥,以上的分析可以總結(jié)為:獲取SP時(shí)存在這樣的映射關(guān)系: (對(duì)象中sp name) -> (對(duì)象 file) -> (全局的 sp)的關(guān)系倾剿,這個(gè)映射關(guān)系為了確保萬(wàn)無(wú)一失在操作的過(guò)程中全部使用類鎖,究竟在什么情況會(huì)出錯(cuò)蚌成,我的單線程腦子還沒(méi)想出來(lái)前痘。。 担忧。
2. 操作SP對(duì)象
2.1 新建與get方法
首先要明確這個(gè)SP對(duì)象它是static的芹缔,任意線程只要姿勢(shì)對(duì)了都可以通過(guò)Context操作它,所以對(duì)它的操作全部都加了對(duì)象鎖瓶盛,構(gòu)造方法如下:
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
//加載數(shù)據(jù)
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
//開(kāi)啟新的線程加載數(shù)據(jù)
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
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 {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//將XML文件轉(zhuǎn)化為內(nèi)存中的鍵值對(duì)
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) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {//比較豪放最欠,任意問(wèn)題都catch住
thrown = t;
}
synchronized (mLock) {
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 {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
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.notifyAll();//通知其他線程加載完成了
}
}
}
上面的代碼中將所有保存在本地的數(shù)據(jù)都讀取到了mMap中示罗,接下來(lái)的getXXX方法都是從這個(gè)mMap中獲取值
public Map<String, ?> getAll() {
synchronized (mLock) {
awaitLoadedLocked();//等待鎖釋放
//noinspection unchecked
return new HashMap<String, Object>(mMap);
}
}
2.2 edit方法
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
直接返回了EditorImpl對(duì)象,它是SharedPreferences.Editor的實(shí)現(xiàn)類芝硬。
//保存更改的鍵值對(duì)
private final Map<String, Object> mModified = new HashMap<>();
//
@Override
public Editor putBoolean(String key, boolean value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
調(diào)用putXXX方法時(shí)數(shù)據(jù)被保存在了mModified鍵值對(duì)中蚜点,在commit或者apply的時(shí)候提交,下面先看commit方法:
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//將mModified的變化同步給mMap
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;
}
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {//此刻有其他數(shù)據(jù)正在提交
// 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);
}
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;//正在同步+1
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {//為null刪除mMap的數(shù)據(jù)
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {//更新mMap中的數(shù)據(jù)
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);
}
上面將mModified的鍵值同步給了mMap吵取,并且返回了MemoryCommitResult對(duì)象,顧名思義它是同步的結(jié)果锯厢,它的構(gòu)造方法的最后一個(gè)參數(shù)其實(shí)就是mMap對(duì)象皮官,此刻mMap中已經(jīng)同步了mModified的值,接下來(lái)就順理成章地要將mMap轉(zhuǎn)換成xml文件了实辑,看commit()中的調(diào)用的SharedPreferencesImpl.this.enqueueDiskWrite方法:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//是否是同步commit捺氢,commit方法中postWriteRunnable為null
final boolean isFromSyncCommit = (postWriteRunnable == null);
//寫文件的runnable
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {//此刻只有這一個(gè)commit
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {//只有一個(gè)提交,就在主線程寫文件了
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
接下來(lái)看apply方法:
public void apply() {
final long startTime = System.currentTimeMillis();
//和commit一樣剪撬,也調(diào)用了commitToMemory同步mModified
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//也調(diào)用了enqueueDiskWrite將數(shù)據(jù)寫到文件中
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);
}
apply的調(diào)用邏輯和commit是一樣的都是先將mModified的數(shù)據(jù)同步給mMap摄乒。
到這里開(kāi)頭的第一個(gè)問(wèn)題就有了答案:
不管是commit還是apply,最后都是將Editor的修改的數(shù)據(jù)同步賦值給SP的mMap對(duì)象之后才會(huì)做寫文件的操作残黑,所以apply時(shí)立即讀數(shù)據(jù)馍佑,讀的是內(nèi)存mMap的數(shù)據(jù),不會(huì)等待寫完了然后再讀文件梨水,要等待也只是等待同步的過(guò)程拭荤。
apply調(diào)用enqueueDiskWrite寫文件時(shí)postWriteRunnable不為null,直接跳到 enqueueDiskWrite方法的QueuedWork.queue處疫诽,將任務(wù)進(jìn)隊(duì)列了舅世,而QueuedWork是什么?結(jié)合apply的異步寫功能奇徒,很容易想到它是一個(gè)排隊(duì)執(zhí)行的異步線程雏亚,QueuedWork如下:
public class QueuedWork {
private static final Object sLock = new Object();
private static Object sProcessingWork = new Object();
private static Handler sHandler = null;
private static final LinkedList<Runnable> sWork = new LinkedList<>();
//新建HandlerThread 線程以及一個(gè)對(duì)應(yīng)的QueuedWorkHandler
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
//
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
//進(jìn)隊(duì)列方法,將work保存到sWork中摩钙,并通知使用handler去通知handlerThread執(zhí)行
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
/**
* @return True iff there is any {@link #queue async work queued}.
*/
public static boolean hasPendingWork() {
synchronized (sLock) {
return !sWork.isEmpty();
}
}
//取出sWork中的runnable挨個(gè)執(zhí)行
private static void processPendingWork() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
synchronized (sProcessingWork) {
LinkedList<Runnable> work;
synchronized (sLock) {
work = (LinkedList<Runnable>) sWork.clone();
sWork.clear();
// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
for (Runnable w : work) {
w.run();
}
if (DEBUG) {
Log.d(LOG_TAG, "processing " + work.size() + " items took " +
+(System.currentTimeMillis() - startTime) + " ms");
}
}
}
}
/**
* Trigger queued work to be processed immediately. The queued work is processed on a separate
* thread asynchronous. While doing that run and process all finishers on this thread. The
* finishers can be implemented in a way to check weather the queued work is finished.
*
* Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
* after Service command handling, etc. (so async work is never lost)
*/
public static void waitToFinish() {
long startTime = System.currentTimeMillis();
boolean hadMessages = false;
Handler handler = getHandler();
synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
// Delayed work will be processed at processPendingWork() below
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
if (DEBUG) {
hadMessages = true;
Log.d(LOG_TAG, "waiting");
}
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
try {
processPendingWork();
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}
private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;
QueuedWorkHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
processPendingWork();
}
}
}
}
QueuedWork封裝了HandlerThread罢低,queue方法將runnable保存到sWork中,并通知使用handler去通知handlerThread取sWork中的runnable執(zhí)行胖笛,最后在子線程調(diào)用processPendingWork取出sWork中的runnable挨個(gè)執(zhí)行奕短,這時(shí)退出應(yīng)用會(huì)怎么樣?
QueuedWork有一個(gè)waitToFinish方法匀钧,看這個(gè)方法名大致知道它是退出前的被調(diào)用的方法翎碑,它被調(diào)用地方如下圖:
一目了然,在Activity之斯、Service stop的時(shí)候會(huì)調(diào)用這個(gè)方法日杈。再看看這個(gè)方法的實(shí)現(xiàn)遣铝,等待HandlerThread中的上一個(gè)runnable結(jié)束后,判斷這個(gè)線程的Looper的隊(duì)列中是否有消息莉擒,如果有就接管這個(gè)事件酿炸,取sWork中所有的Runnable在主線程執(zhí)行。
所以涨冀,退出Activity填硕、退出應(yīng)用不會(huì)導(dǎo)致apply的數(shù)據(jù)丟失,它會(huì)在退出時(shí)將異步線程切換到主線程來(lái)執(zhí)行鹿鳖,等待數(shù)據(jù)都保存了才會(huì)退出扁眯。