前言
分析了MMKV的源碼解析后,我們來(lái)看看Android中常用的鍵值對(duì)組件SharedPreferences的實(shí)現(xiàn)税肪。究竟源碼中出現(xiàn)了什么問(wèn)題芙扎,導(dǎo)致了SharedPreferences的卡頓和ANR呢涌献?
如果有什么問(wèn)題胚宦,請(qǐng)到本文http://www.reibang.com/p/ca1a2129523b下進(jìn)行討論
正文
關(guān)于SharedPreferences的用法,這里就不多贅述了,如果不懂用法的枢劝,隨意找一篇看一下就好了井联。我們一般都是通過(guò)context獲取SharePreferences。
SharedPreferences sharedPreferences = getSharedPreferences("test", Context.MODE_PRIVATE);
我們就從這個(gè)方法看看究竟做了什么您旁。
獲取SharedPreferences 實(shí)例
文件:/frameworks/base/core/java/android/content/ContextWrapper.java
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
這里面的mBase實(shí)際上是ContextImpl烙常,這個(gè)邏輯的解析可以在資源管理或者ActivityThread的初始化兩篇文章中了解到。
文件:/frameworks/base/core/java/android/app/ContextImpl.java
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
- 1.在每一個(gè)ContextWrapper中都會(huì)緩存一個(gè)mSharedPrefsPaths鹤盒,這個(gè)ArrayMap緩存了SharedPreferences的名字為鍵蚕脏,file文件對(duì)象為值。如果發(fā)現(xiàn)mSharedPrefsPaths沒(méi)有緩存侦锯,則會(huì)通過(guò)getSharedPreferencesPath創(chuàng)建一個(gè)file文件出來(lái)驼鞭。
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
能看到實(shí)際上這個(gè)文件就是應(yīng)用目錄下的一個(gè)xml文件
data/shared_prefs/+ sp的名字 + .xml
- 2.getSharedPreferences通過(guò)file和mode獲取SharedPreferences實(shí)例。
getSharedPreferences
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
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;
}
}
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;
}
- 1.先調(diào)用getSharedPreferencesCacheLocked 獲取緩存好的SharedPreferencesImpl實(shí)例尺碰。如果找不到實(shí)例挣棕,則檢查設(shè)置的mode是否合法,并且實(shí)例化一個(gè)新的SharedPreferencesImpl對(duì)象葱蝗,并保存在cache中穴张,同時(shí)返回sp對(duì)象细燎。
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);
}
return packagePrefs;
}
能看到在ContextImpl的靜態(tài)變量sSharedPrefsCache中两曼,根據(jù)包名緩存了一個(gè)以File和SharedPreferencesImpl為鍵值對(duì)的ArrayMap。當(dāng)前ContextWrapper是從這個(gè)靜態(tài)變量中檢查是否緩存了對(duì)應(yīng)的SharedPreferencesImpl對(duì)象玻驻。
- 2.如果mode是MODE_MULTI_PROCESS 多進(jìn)程模式悼凑,通過(guò)api低于3.0則調(diào)用sp的startReloadIfChangedUnexpectedly方法。這里比較特殊就不展開(kāi)討論了璧瞬。
SharedPreferencesImpl 實(shí)例化
文件:/frameworks/base/core/java/android/app/SharedPreferencesImpl.java
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
在這個(gè)過(guò)程中有兩個(gè)很重要的過(guò)程:
- 1.makeBackupFile 根據(jù)當(dāng)前的xml的file 獲取一個(gè)備份的file,就是原來(lái)file的路徑后加一個(gè).bak
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
- 2.startLoadFromDisk 開(kāi)始從磁盤中加載數(shù)據(jù)户辫。
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
能看到此時(shí)是新增了一個(gè)線程調(diào)用loadFromDisk進(jìn)行磁盤的讀取操作。
loadFromDisk
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
...
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);
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) {
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();
}
}
}
這個(gè)方法分為三部分:
- 1.如果mBackupFile存在嗤锉,說(shuō)明有備份文件渔欢,則把構(gòu)造函數(shù)傳遞進(jìn)來(lái)的mFile刪除,并把mBackupFile移動(dòng)到為mFile的path瘟忱。
- 2.先讀取當(dāng)前路徑下文件的權(quán)限奥额,讀取file中xml中所有的存儲(chǔ)的鍵值對(duì)數(shù)據(jù),保存在一個(gè)臨時(shí)的HashMap中访诱。
- 3.如果發(fā)現(xiàn)沒(méi)有任何的異常垫挨,則把臨時(shí)的map賦值給全局的mMap中,并記錄文件下的大小触菜,以及上次一次修改的時(shí)間九榔。
記住這里面有一個(gè)mLock全局對(duì)象十分重要。當(dāng)沒(méi)有初始化讀取文件中緩存的數(shù)據(jù)之前,就是通過(guò)該對(duì)象進(jìn)行阻塞哲泊。
SharedPreferences 增刪查改
SharedPreferences增加鍵值對(duì)
提一下SharedPreferences是如何進(jìn)行增刪查改的剩蟀。當(dāng)獲取到SharedPreferences對(duì)象后會(huì)調(diào)用edit方法,實(shí)例化一個(gè)SharedPreferences.Editor對(duì)象切威。
@GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
@Override
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
能看到這這個(gè)過(guò)程中喻旷,實(shí)際上會(huì)進(jìn)行一次mLock的阻塞,知道讀取磁盤的Thread工作完成后牢屋,才能實(shí)例化一個(gè)新的EditorImpl對(duì)象且预,進(jìn)行增加鍵值對(duì)的操作。
我們就以增加一個(gè)String為例子
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;
}
}
能看到這個(gè)過(guò)程中能看到本質(zhì)上就是把鍵值對(duì)烙无,暫時(shí)存到mModified一個(gè)map中锋谐。
SharedPreferences刪除鍵值對(duì)
@Override
public Editor remove(String key) {
synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
這里面也很簡(jiǎn)單,把當(dāng)前的Key對(duì)應(yīng)鍵值設(shè)置為Editor截酷,并沒(méi)有像常見(jiàn)設(shè)置為null涮拗。
SharedPreferences 查詢鍵值對(duì)
查詢鍵值對(duì),邏輯和增加刪除的不一致迂苛。
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
查詢鍵值對(duì)則會(huì)通過(guò)從xml中獲取的mMap緩存數(shù)據(jù)中進(jìn)行查詢三热。
SharedPreferences 同步數(shù)據(jù)
當(dāng)editor操作完成后,就會(huì)進(jìn)行數(shù)據(jù)的同步三幻。SharedPreferences同步數(shù)據(jù)到磁盤有兩種就漾,一種是commit同步,另一種是apply異步同步念搬。
SharedPreferences commit同步到磁盤中
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
...
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
在這個(gè)過(guò)程中抑堡,可以分為三步:
- 1.commitToMemory 把剛才緩存在Editor的HashMap生成一個(gè)內(nèi)存提交對(duì)象MemoryCommitResult。
- 2.調(diào)用enqueueDiskWrite的enqueueDiskWrite朗徊,把這個(gè)提交對(duì)象提交到磁盤中首妖,并且調(diào)用writtenToDiskLatch進(jìn)行等待。
- 3.完成后調(diào)用notifyListeners通知監(jiān)聽(tīng)已經(jīng)完成爷恳。
先來(lái)看看MemoryCommitResult 對(duì)象中承載了什么數(shù)據(jù)有缆。
MemoryCommitResult
private static class MemoryCommitResult {
final long memoryStateGeneration;
@Nullable final List<String> keysModified;
@Nullable final Set<OnSharedPreferenceChangeListener> listeners;
final Map<String, Object> mapToWriteToDisk;
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
@Nullable Set<OnSharedPreferenceChangeListener> listeners,
Map<String, Object> mapToWriteToDisk) {
this.memoryStateGeneration = memoryStateGeneration;
this.keysModified = keysModified;
this.listeners = listeners;
this.mapToWriteToDisk = mapToWriteToDisk;
}
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
writtenToDiskLatch.countDown();
}
}
能看到這個(gè)對(duì)象中有一個(gè)關(guān)鍵的對(duì)象mapToWriteToDisk,這個(gè)散列表將會(huì)持有SharePreferenceImpl.Editor用于提交到磁盤的臨時(shí)散列表温亲。
另外棚壁,這個(gè)過(guò)程中,還有writtenToDiskLatch進(jìn)行線程工作完成的計(jì)數(shù)铸豁。每當(dāng)一個(gè)線程完成工作后灌曙,將會(huì)調(diào)用writtenToDiskLatch的計(jì)數(shù)減一,實(shí)現(xiàn)阻塞放開(kāi),最后在apply或者commit的末尾通知監(jiān)聽(tīng)者已經(jīng)完成了一次操作节芥。
我們暫時(shí)放一放commit的后續(xù)流程在刺,來(lái)看看apply異步同步磁盤方法中有多少和commit相似的邏輯逆害。
apply 異步同步磁盤
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
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);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
能看到大體上的邏輯和commit很相似。一樣通過(guò)commitToMemory生成一個(gè)MemoryCommitResult對(duì)象蚣驼。同樣是通過(guò)enqueueDiskWrite把寫(xiě)入磁盤的事務(wù)放入事件隊(duì)列中魄幕。
唯一不同的是多了兩個(gè)Runnable,一個(gè)是awaitCommit颖杏,另一個(gè)是postWriteRunnable纯陨。awaitCommit會(huì)進(jìn)行MemoryCommitResult的阻塞等待,會(huì)添加到QueuedWork中留储,并在postWriteRunnable中執(zhí)行awaitCommit的run方法翼抠。postWriteRunnable傳遞給enqueueDiskWrite。
commitToMemory
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) {
// 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++;
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) {
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);
}
1.在這段邏輯中mDiskWritesInFlight這個(gè)計(jì)數(shù)器十分重要获讳,如果mDiskWritesInFlight這個(gè)計(jì)數(shù)大于0阴颖,說(shuō)明有其他線程在異步的進(jìn)行commit處理,由于HashMap本身不是一個(gè)線程安全的集合丐膝,因此會(huì)對(duì)全局的mMap進(jìn)行一次拷貝量愧,讓其他線程可以正常的查詢數(shù)據(jù)。
2.判斷此時(shí)mModified中的value是不是null或者Editor對(duì)象帅矗,是則說(shuō)明鍵對(duì)應(yīng)的值已經(jīng)設(shè)置為null偎肃。在這里以Editor為value判空只是一個(gè)避免多線程的修改而處理的魔數(shù)。
3.最后把mModified中的數(shù)據(jù)拷貝到mapToWriteToDisk浑此。一旦出現(xiàn)了數(shù)據(jù)的變化累颂,mCurrentMemoryStateGeneration的計(jì)數(shù)就會(huì)加1.最后生成MemoryCommitResult返回。
enqueueDiskWrite 推入SP的寫(xiě)入磁盤隊(duì)列
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
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) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
1.writeToDiskRunnable 這個(gè)runnable能看到實(shí)際上是真正的調(diào)用writeToFile寫(xiě)入到磁盤中尤勋,每一次寫(xiě)入完畢就會(huì)減少mDiskWritesInFlight的計(jì)數(shù)喘落,說(shuō)明一個(gè)線程已經(jīng)工作了,最后再執(zhí)行postWriteRunnable的run方法最冰。
2.isFromSyncCommit這個(gè)判斷的是否進(jìn)行同步處理的標(biāo)志位,就是通過(guò)postWriteRunnable是否為null判斷的稀火。如果為空說(shuō)明是commit方法進(jìn)行處理暖哨。此時(shí)會(huì)判斷mDiskWritesInFlight是否為1,為1說(shuō)明只有一個(gè)線程在執(zhí)行凰狞,那就可以直接執(zhí)行的writeToDiskRunnable的方法直接寫(xiě)入磁盤篇裁。但是mDiskWritesInFlight大于1說(shuō)明有其他線程正在準(zhǔn)備提交,那么還是和apply一樣需要放到QueuedWork中排隊(duì)執(zhí)行赡若。
在SP中所有的異步操作都會(huì)進(jìn)入到QueuedWork中進(jìn)行排隊(duì)操作达布,我們來(lái)看看
QueuedWork是怎么設(shè)計(jì)的。
QueuedWork
文件:/frameworks/base/core/java/android/app/QueuedWork.java
我們首先來(lái)看看addFinish是做了什么
/** Finishers {@link #addFinisher added} and not yet {@link #removeFinisher removed} */
@GuardedBy("sLock")
private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
/** Lock for this class */
private static final Object sLock = new Object();
public static void addFinisher(Runnable finisher) {
synchronized (sLock) {
sFinishers.add(finisher);
}
}
/**
* Remove a previously {@link #addFinisher added} finisher-runnable.
*
* @param finisher The runnable to remove.
*/
public static void removeFinisher(Runnable finisher) {
synchronized (sLock) {
sFinishers.remove(finisher);
}
}
在apply方法中逾冬,會(huì)把a(bǔ)waitCommit這個(gè)runnable對(duì)象存放到一個(gè)靜態(tài)集合sFinishers黍聂。
QueuedWork queue
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);
}
}
}
這個(gè)方法中躺苦,先獲取Queuework內(nèi)部的保存的靜態(tài)Handler對(duì)象,接著根據(jù)shouldDelay標(biāo)志位是否需要延時(shí)产还,而決定在handler發(fā)送MSG_RUN消息的時(shí)候有沒(méi)有100毫秒的延時(shí)匹厘。
QueuedWork getHandler
/** {@link #getHandler() Lazily} created handler */
@GuardedBy("sLock")
private static Handler sHandler = null;
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;
}
}
能看到在QueueWork對(duì)象中,保存著一個(gè)單例設(shè)計(jì)的sHandler對(duì)象脐区。這個(gè)Handler是基于handlerThread中的線程生成的Looper愈诚。也就說(shuō),所有的入隊(duì)操作最后都會(huì)切換到handlerThread這個(gè)名為queued-work-looper的線程中牛隅。
QueuedWorkHandler
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();
}
}
}
在QueuedWorkHandler中只接受一種MSG_RUN消息的炕柔,并執(zhí)行processPendingWork的方法。
processPendingWork 執(zhí)行等待執(zhí)行的任務(wù)
private static void processPendingWork() {
long startTime = 0;
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();
}
}
}
}
在這過(guò)程中媒佣,實(shí)際上十分簡(jiǎn)單汗唱,就是把之前通過(guò)queue方法加入的runnable,從sWork只能夠取出丈攒,并且移除掉所有的MSG_RUN的消息哩罪,清掉sWork的緩存,并執(zhí)行所有的queue的Runnable對(duì)象巡验。
知道怎么執(zhí)行之后际插,我們回頭看看設(shè)置進(jìn)來(lái)的runnable對(duì)象writeToDiskRunnable。
writeToDiskRunnable 執(zhí)行流程
文件:/frameworks/base/core/java/android/app/SharedPreferencesImpl.java
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
當(dāng)執(zhí)行完writeToFile寫(xiě)入磁盤方法后显设,減少mDiskWritesInFlight的計(jì)數(shù)已旧,同時(shí)調(diào)用postWriteRunnable方法此再。這個(gè)方法實(shí)際上就是awaitCommit的runnable,實(shí)際上就是調(diào)用了mcr中的writtenToDiskLatch的阻塞await方法。
writeToFile
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
long startTime = 0;
long existsTime = 0;
long backupExistsTime = 0;
long outputStreamCreateTime = 0;
long writeTime = 0;
long fsyncTime = 0;
long setPermTime = 0;
long fstatTime = 0;
long deleteTime = 0;
boolean fileExists = mFile.exists();
// Rename the current file so it may be used as a backup during the next read
if (fileExists) {
boolean needsWrite = false;
// Only need to write if the disk state is older than this commit
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
}
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
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
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
mDiskStateGeneration = mcr.memoryStateGeneration;
mcr.setDiskWriteResult(true, true);
long fsyncDuration = fsyncTime - writeTime;
mSyncTimes.add((int) fsyncDuration);
mNumSync++;
return;
} catch (XmlPullParserException e) {
...
} catch (IOException e) {
...
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
...
}
}
mcr.setDiskWriteResult(false, false);
}
這里面的可以分為如下幾個(gè)步驟:
判斷到mFile责球,需要緩存的文件存在則執(zhí)行如下:
- 1.如果是commit的方式或者全局異步寫(xiě)入次數(shù)mDiskStateGeneration和mcr需要提交的memoryStateGeneration一致,此時(shí)needsWrite就為true秉氧。這么做有什么好處呢址儒?有一個(gè)全局的SP操作計(jì)數(shù),就能知道當(dāng)前有多少線程進(jìn)行了SP的操作允悦,那么就沒(méi)有必要膝擂,每一次都寫(xiě)入到磁盤,只需要寫(xiě)入最后一次的內(nèi)存提交即可隙弛。如果發(fā)現(xiàn)需要提交的數(shù)據(jù)不是最后一次架馋,則會(huì)調(diào)用mcr的setDiskWriteResult結(jié)束當(dāng)前線程的等待。
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
- 2.如果備份文件不存在全闷,則會(huì)把當(dāng)前的mFIle嘗試著移動(dòng)路徑為備份文件mBackFile叉寂。如果連mFile的重命名失敗就直接返回了。如果備份文件存在总珠,則刪除掉原來(lái)的mFile文件屏鳍。
if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
在sp第一次初始化時(shí)候勘纯,會(huì)把嘗試著備份文件轉(zhuǎn)化為存儲(chǔ)文件。如果是第一次創(chuàng)建sp對(duì)應(yīng)的存儲(chǔ)文件孕蝉,那么備份文件必定不存在屡律。在writeToFile這個(gè)方法中,就會(huì)把當(dāng)前的file重命名為備份的file. 如果存在備份文件降淮,說(shuō)明之前已經(jīng)存過(guò)東西了超埋,并從文件中讀取鍵值對(duì)到mMap全局變量中,可以直接刪除掉整個(gè)File文件佳鳖,其實(shí)就是相當(dāng)于把file的存儲(chǔ)文件給清空了霍殴。
- 3.把mMap的數(shù)據(jù)通過(guò)io寫(xiě)入到XML mFile文件中。
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
writeTime = System.currentTimeMillis();
FileUtils.sync(str);
fsyncTime = System.currentTimeMillis();
str.close();
- 4.如果寫(xiě)入操作成功了系吩,那么備份文件也不需要存在了来庭,直接刪除了。并調(diào)用mcr的setDiskWriteResult穿挨,告訴阻塞的對(duì)象已經(jīng)完成了io的操作月弛。
到這里,大致上就完成了SP的讀寫(xiě)流程了科盛。
關(guān)于SP的思考與總結(jié)
總結(jié)
我們先來(lái)看那么幾個(gè)時(shí)序圖帽衙。
當(dāng)SP啟動(dòng)的時(shí)候,就會(huì)就會(huì)開(kāi)啟一個(gè)線程從磁盤中讀取贞绵,但是如果此時(shí)遇到了SP操作就會(huì)如下執(zhí)行:
十分簡(jiǎn)單的SP的初始化流程厉萝。
SP的備份機(jī)制
我們?cè)偃タ纯碨P的備份機(jī)制。在SP的備份機(jī)制中榨崩,實(shí)際上是由一個(gè).bak后綴的文件進(jìn)行備份谴垫。
當(dāng)loadFromDisk的時(shí)候,會(huì)生成一個(gè)線程讀取Xml的數(shù)據(jù)到mMap的緩存中母蛛。能看到翩剪,第一次讀取文件的時(shí)候,發(fā)現(xiàn)備份文件存在會(huì)進(jìn)行如下處理:
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
把mFile的文件給刪除掉溯祸,把mBackupFile重新命名為mFile(xml的緩存文件名)肢专。那么這樣做就能把上一次保存在mBackupFile中完好的數(shù)據(jù)繼承到mFile中。
而mBackupFile一般來(lái)說(shuō)是實(shí)在writeToFile的時(shí)候附帶的副產(chǎn)物:
if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
假設(shè)焦辅,如果我們上一次以及之前所有的磁盤讀寫(xiě)都成功了,那么備份文件就會(huì)和writeToFile小結(jié)中說(shuō)的一樣椿胯,每一次讀寫(xiě)完畢會(huì)刪除筷登。
backupFile都不存在,就會(huì)把mFile重命名路徑為mBackupFile哩盲,在每一次讀寫(xiě)之前進(jìn)行了一次備份前方。
從全局來(lái)看如果backupFile存在狈醉,說(shuō)明了之前的讀寫(xiě)出現(xiàn)了問(wèn)題。此時(shí)可以分為兩種情況:
1.初始化的時(shí)候發(fā)現(xiàn)backupFile存在惠险,那么此時(shí)backupFile已經(jīng)重新刷新了原來(lái)的mFile對(duì)象苗傅,也就說(shuō)把之前數(shù)據(jù)出錯(cuò)了mFile回退到最后編輯的backupFile數(shù)據(jù)狀態(tài),并且保存到mMap中班巩。從之后就不會(huì)存在backupFile這個(gè)備份文件了渣慕。
2.如果在讀寫(xiě)過(guò)程中出現(xiàn)了異常,讀寫(xiě)操作無(wú)法完成抱慌,則會(huì)生成一個(gè)備份文件backupFile逊桦。這個(gè)時(shí)候就會(huì)把之前那個(gè)mFile存儲(chǔ)出錯(cuò)的數(shù)據(jù)文件,直接刪除抑进。因?yàn)樽x寫(xiě)過(guò)程中被異常退出了强经,很有可能讀寫(xiě)的信息有誤,造成下一次啟動(dòng)無(wú)法正常讀取寺渗。干脆直接刪掉mFile匿情,重新建立一個(gè)新的mFile文件寫(xiě)入其中。如果這一次的成功了信殊,就刪掉備份文件炬称。
換句話說(shuō),SP的備份機(jī)制鸡号,實(shí)際上備份的是上一次正常讀寫(xiě)成功磁盤存儲(chǔ)機(jī)制转砖。
SP跨進(jìn)程的mode在其中的作用
在整個(gè)流程中mode正在起作用的位置在writeToFile中,寫(xiě)如磁盤操作結(jié)束后的下面一行代碼中:
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
文件:/frameworks/base/core/java/android/app/ContextImpl.java
static void setFilePermissionsFromMode(String name, int mode,
int extraPermissions) {
int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR
|FileUtils.S_IRGRP|FileUtils.S_IWGRP
|extraPermissions;
if ((mode&MODE_WORLD_READABLE) != 0) {
perms |= FileUtils.S_IROTH;
}
if ((mode&MODE_WORLD_WRITEABLE) != 0) {
perms |= FileUtils.S_IWOTH;
}
FileUtils.setPermissions(name, perms, -1, -1);
}
public static int setPermissions(String path, int mode, int uid, int gid) {
try {
Os.chmod(path, mode);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chmod(" + path + "): " + e);
return e.errno;
}
if (uid >= 0 || gid >= 0) {
try {
Os.chown(path, uid, gid);
} catch (ErrnoException e) {
Slog.w(TAG, "Failed to chown(" + path + "): " + e);
return e.errno;
}
}
return 0;
}
//用戶
public static final int S_IRWXU = 00700;
public static final int S_IRUSR = 00400;
public static final int S_IWUSR = 00200;
public static final int S_IXUSR = 00100;
//組
public static final int S_IRWXG = 00070;
public static final int S_IRGRP = 00040;
public static final int S_IWGRP = 00020;
public static final int S_IXGRP = 00010;
//其他人
public static final int S_IRWXO = 00007;
public static final int S_IROTH = 00004;
public static final int S_IWOTH = 00002;
public static final int S_IXOTH = 00001;
其實(shí)可以看到這個(gè)過(guò)程中就是通過(guò)chmod方法設(shè)置了文件的在系統(tǒng)中的權(quán)限。從名稱就能知道鲸伴,這個(gè)方法每一次讀寫(xiě)之后都會(huì)默認(rèn)給用戶和組都能夠進(jìn)行讀寫(xiě)府蔗。
在這個(gè)方法會(huì)判斷從上面?zhèn)飨聛?lái)的mode,是否打開(kāi)了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE汞窗。
public static final int MODE_WORLD_WRITEABLE = 0x0002;
public static final int MODE_WORLD_READABLE = 0x0001;
我們來(lái)看看SP設(shè)置多進(jìn)程讀寫(xiě)時(shí)候的標(biāo)志位:
public static final int MODE_MULTI_PROCESS = 0x0004;
換算到二進(jìn)制就是100和010姓赤,以及100和001相與都是0.連這里都沒(méi)有進(jìn)行處理的話,說(shuō)明SP在Android 9.0根本沒(méi)有進(jìn)行多進(jìn)程讀寫(xiě)文件的互斥處理.
我們看看MODE_MULTI_PROCESS的注釋:
MODE_MULTI_PROCESS does not work reliably in
* some versions of Android, and furthermore does not provide any
* mechanism for reconciling concurrent modifications across
* processes. Applications should not attempt to use it. Instead,
* they should use an explicit cross-process data management
* approach such as {@link android.content.ContentProvider ContentProvider}.
這里面已經(jīng)說(shuō)明了仲吏,在某些Android版本中SP將不會(huì)提供跨進(jìn)程讀寫(xiě)文件的保護(hù)不铆,如果有需求,請(qǐng)使用ContentProvider裹唆。
最后上一副流程圖進(jìn)行總結(jié)整個(gè)流程:
思考
能看到實(shí)際上我們通過(guò)Context獲取sp對(duì)象誓斥,在這個(gè)過(guò)程中,我們完全可以復(fù)寫(xiě)Context中g(shù)etSharePreferences的方法许帐,進(jìn)而返回自己定義的SharePreferences劳坑。當(dāng)然我們需要自己實(shí)現(xiàn)SharePreferences以及SharePreferences.Editor.
同理我們可以看到MMKV中的實(shí)現(xiàn):
public class MMKV implements SharedPreferences, SharedPreferences.Editor
那么,實(shí)際上我們這么無(wú)縫遷移MMKV到SP中:
override fun getSharedPreferences(name: String?, mode: Int): SharedPreferences {
val mmkv = MMKV.mmkvWithID(name,mode)
if(mmkv.getBoolean("hasTransport",false)){
var originPrefences = super.getSharedPreferences(name, mode)
mmkv.importFromSharedPreferences(originPrefences)
originPrefences.edit().clear().apply()
mmkv.encode("hasTransport",true)
}
return mmkv
}
只需要在Application成畦,Activity距芬,CP下復(fù)寫(xiě)該方法為如上涝开,就能在上層使用了SP的方式,實(shí)際上底層卻是調(diào)用了mmkv的方法框仔。
了解到如何無(wú)縫使用MMKV之后舀武,我們?cè)賮?lái)聊一下在MMKV一文中和大家提過(guò)SP的幾個(gè)問(wèn)題:
- 1.跨進(jìn)程不安全
- 2.加載緩慢
- 3.全量寫(xiě)入
- 4.卡頓
SP跨進(jìn)程不安全
對(duì)于第一個(gè)問(wèn)題,在上一節(jié)我們聊過(guò)了SP的實(shí)現(xiàn)离斩。實(shí)際上它并沒(méi)有對(duì)多進(jìn)程讀寫(xiě)進(jìn)行保護(hù)
SP加載緩慢
對(duì)于這個(gè)問(wèn)題银舱,其實(shí)就是指初始化的時(shí)候SPImpl的時(shí)候,新建立了一個(gè)線程進(jìn)行讀取Xml的數(shù)據(jù):
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
然而在SP每一次操作都必須等待這個(gè)線程讀取完磁盤才能進(jìn)行下一步的操作捐腿。很多開(kāi)發(fā)者估計(jì)都是把SP操作放到ui線程中進(jìn)行的吧纵朋。如果開(kāi)發(fā)者不注意,保存的XML數(shù)據(jù)過(guò)于龐大茄袖,就會(huì)造成ui卡頓甚至ANR操软。
為了讓該線程更加快速的處理,Android系統(tǒng)應(yīng)該要把SP讀取磁盤使用一個(gè)緩存線程池進(jìn)行處理宪祥,線程可以立即執(zhí)行
private static volatile ExecutorService sCachedThreadPool = Executors.newCachedThreadPool();
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
sCachedThreadPool.execute(new Runnable() {
@Override
public void run() {
loadFromDisk();
}
});
}
通過(guò)這種方式聂薪,就能讓在線程池中立即獲取還存活的線程進(jìn)行直接的處理磁盤讀取任務(wù)。
當(dāng)然蝗羊,還有一點(diǎn)需要注意由于磁盤讀取的和MMKV不一樣藏澳,MMKV是直接通過(guò)共享內(nèi)存的方式直接把內(nèi)存文件映射到虛擬內(nèi)存中,應(yīng)用可以直接訪問(wèn)耀找。而SP的讀取則是通過(guò)普通的io讀寫(xiě)翔悠,這樣需要經(jīng)過(guò)一次進(jìn)入內(nèi)核一次,就會(huì)造成速度上比mmap要慢上不少野芒。
兩者都應(yīng)該注意寫(xiě)如數(shù)據(jù)的大行畛睢:
MMKV雖然有trim方法,但是并沒(méi)有幫你監(jiān)控虛擬內(nèi)存的情況狞悲,這也是MMKV可以后續(xù)優(yōu)化的地方撮抓,如果不注意數(shù)據(jù)的大小一味的存儲(chǔ),會(huì)造成虛擬內(nèi)存爆炸導(dǎo)致應(yīng)用異常摇锋。
SP由于是一口氣從磁盤中讀取所有的數(shù)據(jù)丹拯,數(shù)據(jù)過(guò)于龐大就會(huì)造成SP初始化十分慢,導(dǎo)致后續(xù)操作產(chǎn)生ANR荸恕。
全量寫(xiě)入
這個(gè)問(wèn)題可以很容易的看到乖酬,在writeToFile方法中,是把所有從Xml緩存文件解析到的數(shù)據(jù)統(tǒng)統(tǒng)保存會(huì)緩存文件中融求,這樣就會(huì)造成了寫(xiě)入十分緩慢剑刑。而反觀MMKV,由于它本身就是支持append模式在后面映射內(nèi)存末尾繼續(xù)添加鍵值對(duì)双肤,這樣寫(xiě)入速度比起SP快的不是一星半點(diǎn)施掏。
當(dāng)然也是因?yàn)檫@種機(jī)制的問(wèn)題,SP和MMKV的recover模式從根本的策略上不同茅糜。SP由于是全量讀寫(xiě)七芭,這樣就能完成的保存一份備份文件。而MMKV一般是內(nèi)存末尾追加模式以及多進(jìn)程讀寫(xiě)保護(hù)的策略蔑赘,雖然讀寫(xiě)很快狸驳,但是這也造成了MMKV很難對(duì)全文件進(jìn)行一次備份處理,只能不斷的保證最后一次讀寫(xiě)正常缩赛,并嘗試讀取緩存文件中完好數(shù)據(jù)盡可能恢復(fù)完好的數(shù)據(jù)耙箍。
接下來(lái)比較Android 7.0低版本中SP的實(shí)現(xiàn),我們翻翻以前老版本的SP的源碼酥馍。我們看看Android 7.0的源碼:
private void writeToFile(MemoryCommitResult mcr) {
if (mFile.exists()) {
if (!mcr.changesMade) {
mcr.setDiskWriteResult(true);
return;
}
if (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
mcr.setDiskWriteResult(false);
return;
}
} else {
mFile.delete();
}
}
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
mBackupFile.delete();
mcr.setDiskWriteResult(true);
return;
} catch (XmlPullParserException e) {
} catch (IOException e) {
}
mcr.setDiskWriteResult(false);
}
能看到在Android 7.0中并沒(méi)有像Android 9.0一樣辩昆,對(duì)apply異步寫(xiě)入進(jìn)行一次needWrites的標(biāo)志位判斷,避免多次寫(xiě)入磁盤旨袒。在Android 7.0中只要有一個(gè)apply的操作汁针,就會(huì)進(jìn)行一次磁盤的讀寫(xiě),這樣就會(huì)造成io的上繁忙砚尽,性能大大的降低施无。
在我看來(lái),Android 9.0的優(yōu)化方案也不是最好的必孤。
在SP中有一個(gè)參數(shù)mDiskWritesInFlight對(duì)apply或者commit的同步操作進(jìn)行計(jì)數(shù)猾骡。我們完全可以做成把多個(gè)apply合并成一個(gè)apply操作,只需要判斷到mDiskWritesInFlight小于等于0敷搪,說(shuō)明SP其他的操作經(jīng)完成了兴想,可以進(jìn)行SharedPreferencesImpl.this.enqueueDiskWrite操作,這樣的結(jié)果也不會(huì)發(fā)生變化购啄,因?yàn)樵谶@之前一直在操作內(nèi)存襟企。
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
boolean hasDiskWritesInFlight = false;
synchronized (SharedPreferencesImpl.this) {
hasDiskWritesInFlight = mDiskWritesInFlight > 0;
}
if (!hasDiskWritesInFlight) {
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.add(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
}
notifyListeners(mcr);
}
能通過(guò)這種方式減少apply的操作,減少Q(mào)ueueWork中遍歷的任務(wù)隊(duì)列狮含。
卡頓
那么除了上文我說(shuō)過(guò)的顽悼,因?yàn)镾P讀取XML緩存文件過(guò)大使得初始化時(shí)間太長(zhǎng)而導(dǎo)致ANR之外。其實(shí)在AcitivtyThread中有什么一段代碼几迄,不知道你們有沒(méi)有在我的Activity啟動(dòng)流程系列文章中有沒(méi)有發(fā)現(xiàn)在onPause以及onStop生命周期一文中有這么一段代碼:
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
int configChanges, PendingTransactionActions pendingActions, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
if (userLeaving) {
performUserLeavingActivity(r);
}
r.activity.mConfigChangeFlags |= configChanges;
performPauseActivity(r, finished, reason, pendingActions);
// Make sure any pending writes are now committed.
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
mSomeActivitiesChanged = true;
}
}
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
reason);
updateVisibility(r, show);
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
stopInfo.setActivity(r);
stopInfo.setState(r.state);
stopInfo.setPersistentState(r.persistentState);
pendingActions.setStopInfo(stopInfo);
mSomeActivitiesChanged = true;
}
能看到在低于Android api 11會(huì)在onPause時(shí)候調(diào)用QueuedWork的waitToFinish蔚龙。大于11則會(huì)在onStop調(diào)用QueuedWork.waitToFinish.這兩個(gè)方法都會(huì)對(duì)QueuedWork的中的任務(wù)執(zhí)行進(jìn)行等待,直到執(zhí)行完畢映胁。
QueuedWork waitToFinish
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);
...
}
// 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);
}
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
synchronized (sLock) {
long waitTime = System.currentTimeMillis() - startTime;
if (waitTime > 0 || hadMessages) {
mWaitTimes.add(Long.valueOf(waitTime).intValue());
mNumWaits++;
....
}
}
}
能看到木羹,在這個(gè)過(guò)程中實(shí)際上就是上文我提到過(guò)的addFinisher的方法。這個(gè)方法實(shí)際上就是在apply異步同步到磁盤的Runnable對(duì)象awaitCommit。
從全局的設(shè)計(jì)來(lái)看坑填,一個(gè)Finisher會(huì)對(duì)應(yīng)一個(gè)執(zhí)行磁盤寫(xiě)入的方法抛人。所以在waitToFinish這個(gè)方法實(shí)際上就是檢查還有多少個(gè)Finisher沒(méi)有被銷毀,那么就有多少任務(wù)還沒(méi)有執(zhí)行完成脐瑰。
在processPendingWork執(zhí)行完成之前妖枚,都需要調(diào)用finisher的run方法,對(duì)mcr中的CountDownLatch進(jìn)行等待阻塞苍在。
換句話說(shuō)绝页,當(dāng)我們的SP寫(xiě)入耗時(shí)過(guò)大,就會(huì)造成Activity 暫停時(shí)候卡住寂恬,從而導(dǎo)致AMS服務(wù)那邊的倒計(jì)時(shí)超時(shí)爆了ANR续誉。而這種情況可能很會(huì)見(jiàn)的不少,因?yàn)镾P本身就全量寫(xiě)入初肉。
這四個(gè)缺點(diǎn)就是平時(shí)開(kāi)發(fā)中遇到酷鸦,并且通過(guò)源碼分析后,發(fā)現(xiàn)系統(tǒng)實(shí)現(xiàn)不合理的地方朴译。而MMKV都能對(duì)這四個(gè)問(wèn)題有很好的彌補(bǔ)以及提升井佑。
后話
關(guān)于存儲(chǔ)優(yōu)化的第一部分就完成了,不過(guò)關(guān)于存儲(chǔ)還有很多可以聊聊眠寿,比如數(shù)據(jù)庫(kù)等等躬翁。不過(guò)到這里我們先點(diǎn)到為止,后續(xù)我們繼續(xù)View的繪制流程源碼解析盯拱。
從這一篇文章來(lái)看盒发,源碼本身雖然經(jīng)過(guò)千錘百煉,不過(guò)還是有不少設(shè)計(jì)不是很好的地方狡逢。SP除了這四個(gè)性能的問(wèn)題之外宁舰,還有一些代碼設(shè)計(jì)層面上我個(gè)人覺(jué)得不夠好的地方,比如緩存對(duì)象為什么一定要用SharedPreferencesImpl奢浑,而不用SharedPreferences接口蛮艰,這樣系統(tǒng)就能緩存我們自定義的SP了。
不過(guò)這些都是后話了雀彼。