Context.getSP()
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
...
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
...
return sp;
}
一個(gè)SP對(duì)象對(duì)應(yīng)了一個(gè)File眨唬,這些SP對(duì)象都是存在Map<File,SP>中的;
SharePreferenceImpl的構(gòu)造方法:
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
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);
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();
}
}
}
在SP構(gòu)造的時(shí)候,會(huì)開啟一個(gè)線程從磁盤讀取xml文件崭添,然后解析xml數(shù)據(jù)放入內(nèi)存的map中;這樣意味著xml上的數(shù)據(jù)都會(huì)存放在內(nèi)存中
Get操作
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
SP的get操作,如果異步加載完成,將直接從內(nèi)存Map獲取對(duì)應(yīng)的Value雾家,如果SP構(gòu)造方法中的異步加載沒(méi)有完成,假設(shè)讀取一個(gè)大的文件绍豁,勢(shì)必會(huì)造成主線程阻塞
Put操作
put操作分為兩種芯咧,同步commit
和移步apply
commit():
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 {
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
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);
}
- 注意這里enqueueDiskWrite()的兩個(gè)參數(shù):params 1 是寫入磁盤的數(shù)據(jù), params 2 為null的時(shí)候妹田,isFromSyncCommit為true 唬党,就會(huì)直接調(diào)用writeToDiskRunnable()去寫入IO鹃共,
(如果參數(shù)2不為null鬼佣,就會(huì)調(diào)用一個(gè)異步handler 去寫入IO,見下文)
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) {
}
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);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); // 寫入 IO
// 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);
}
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); // 異步 IO
}
當(dāng) enqueueDiskWrite 方法的參數(shù)2不會(huì)null的時(shí)候霜浴,就會(huì)調(diào)用QueuedWork處理
QueueWorker.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);
}
}
}
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;
}
}
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();
}
}
}
}
apply() 異步IO的實(shí)現(xiàn):HandlerThread
SP問(wèn)題:
1. SharedPreference Apply 引起的ANR問(wèn)題
剖析 SharedPreference apply 引起的 ANR 問(wèn)題
總結(jié)來(lái)看晶衷,SP 調(diào)用 apply 方法,會(huì)創(chuàng)建一個(gè)等待鎖放到 QueuedWork 中阴孟,并將真正數(shù)據(jù)持久化封裝成一個(gè)任務(wù)放到異步隊(duì)列中執(zhí)行晌纫,任務(wù)執(zhí)行結(jié)束會(huì)釋放鎖。Activity onStop 以及 Service 處理 onStop永丝,onStartCommand 時(shí)锹漱,執(zhí)行 QueuedWork.waitToFinish() 等待所有的等待鎖釋放。如果時(shí)間過(guò)長(zhǎng)會(huì)引發(fā)ANR
2. SP 無(wú)法保證類型安全
sp.edit { putInt(key, 0) } // 使用 Int 類型的數(shù)據(jù)覆蓋相同的 key
sp.getString(key, ""); // 使用相同的 key 讀取 Sting 類型的數(shù)據(jù)
此時(shí)會(huì)出現(xiàn) 類型錯(cuò)誤異常
3. get會(huì)造成阻塞慕嚷,引起ANR
詳見上文哥牍,如果在SPimpl沒(méi)有構(gòu)造完成的時(shí)候,去get喝检,調(diào)用get的線程會(huì)自旋
4. SP將所有數(shù)據(jù)都加載到了內(nèi)存中嗅辣,內(nèi)存占用問(wèn)題
SPimpl構(gòu)造的時(shí)候,將xml全部解析到map中
5. 不能跨進(jìn)程
xml文件存放在進(jìn)程私有目錄挠说,SP無(wú)法做到跨進(jìn)程