總體認(rèn)識(shí)
圖片來(lái)自掘金文章,我就懶得再畫(huà)一次了哈婚脱。
SharedPreferences是個(gè)接口叨叙,SharedPreferenceImpl是其實(shí)現(xiàn)。
而Editor是SharedPreferences的內(nèi)部類填硕,也是一個(gè)接口。EditorImpl是其實(shí)現(xiàn)鹿鳖,它是SharedPreferenceImpl的內(nèi)部類扁眯。
看看它們都提供了什么方法:
可以看到它支持5種基本數(shù)據(jù)類型的存取:boolean , int, float, long, String,和一個(gè)Set集合:Set<String>翅帜。
基本用法
private void simpleSP(Context context) {
SharedPreferences sp = context.getSharedPreferences("leonxtp", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("my_key", "leonxtp");
// editor.commit();
editor.apply();
String leonxtp = sp.getString("my_key", "");
Log.i("SP", leonxtp);
}
SharedPreferences是怎么存的姻檀?
磁盤(pán)上:
數(shù)據(jù)以xml形式存儲(chǔ)在/data/data/項(xiàng)目包名/shared_prefs/sp_name.xml
圖片來(lái)自簡(jiǎn)書(shū)文章
內(nèi)存中:
SharedPreferenceImpl.java:
final class SharedPreferencesImpl implements SharedPreferences {
@GuardedBy("ContextImpl.class")
private ArrayMap<String, File> mSharedPrefsPaths;
@GuardedBy("ContextImpl.class")
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
@GuardedBy("mLock")
private Map<String, Object> mMap;
public final class EditorImpl implements Editor {
@GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
}
}
他們的存儲(chǔ)關(guān)系是:
-
從文件的角度來(lái)說(shuō),
一個(gè)包名對(duì)應(yīng)一個(gè)存儲(chǔ)目錄涝滴,里面有多個(gè)xml文件施敢,一個(gè)xml文件對(duì)應(yīng)一個(gè)map周荐,里面多個(gè)key-value
-
從內(nèi)存的角度來(lái)說(shuō),
sSharedPrefsCache
中保存的是app中所有的xml文件對(duì)應(yīng)的操作它的SharedPreferencesImpl
的對(duì)象僵娃,據(jù)查源碼概作,它內(nèi)部只保存了一個(gè)鍵值對(duì),key就是app的包名默怨。mSharedPrefsPaths
保存的是文件名-文件列表讯榕,mMap
保存的是某個(gè)xml文件對(duì)應(yīng)的內(nèi)容。mModified
中保存的是修改過(guò)的內(nèi)容匙睹,它會(huì)在commit和apply之后愚屁,保存到mMap
中,并且同步/異步寫(xiě)進(jìn)xml文件里痕檬。Android這樣設(shè)計(jì)的目的就是避免每次讀寫(xiě)都去操作文件霎槐,帶來(lái)很大的性能消耗。
OK梦谜,總體上的認(rèn)知就到這吧丘跌,開(kāi)始上源碼
源碼分析
我們通過(guò)context來(lái)獲取,而ContextImpl是它的實(shí)現(xiàn)唁桩,這個(gè)過(guò)程是怎么完成的闭树,可以看我之前的文章。
我們直接來(lái)到ContextImpl.java:
@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.
// 這里谷歌開(kāi)發(fā)人員寫(xiě)了一段感覺(jué)很自?shī)首詷?lè)的注釋荒澡,我們可以看到报辱,它允許null值哦
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);
}
先看內(nèi)存中有沒(méi)有這個(gè)xml文件的對(duì)象,沒(méi)有就創(chuàng)建单山,加入到 mSharedPrefsPaths
中碍现。創(chuàng)建的代碼如下:
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
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");
}
很簡(jiǎn)單,其中g(shù)etPreferencesDir()獲取到目錄就是/data/data/我們的程序包名/shared_prefs/米奸。
有了文件之后昼接,就嘗試去取出SharedPreferenceImpl對(duì)象了:
@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);
// ...
// 創(chuàng)建的時(shí)候,會(huì)將xml文件加載到mMap中
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.
// 這里主要針對(duì)mode為多進(jìn)程的時(shí)候
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
創(chuàng)建SharedPreferenceImpl的時(shí)候會(huì)將xml文件加載到mMap中躏升,過(guò)程如下:
final class SharedPreferencesImpl implements SharedPreferences {
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = 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 map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
/* ignore */
}
synchronized (mLock) {
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
mLock.notifyAll();
}
}
}
這里看到辩棒,它使用了同步代碼塊保證多線程的同步狼忱。
而mMap就是從xml文件中解析出來(lái)的膨疏。進(jìn)去看看,還是不看了吧钻弄,就是用了XmlParser解析而已佃却。
OK,獲染桨场(創(chuàng)建)的過(guò)程看完了饲帅,下面看看put操作复凳,
public final class EditorImpl implements Editor {
@GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
public Editor putString(String key, @Nullable String value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
就這么簡(jiǎn)單?灶泵?是的育八,畢竟,真正的操作在commit/apply里:
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 {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
注意這兩行:
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
writtenToDiskLatch
是一個(gè)CountDownLatch對(duì)象赦邻,它將會(huì)等待enqueueDiskWrite()方法執(zhí)行完成髓棋,這就會(huì)致使線程阻塞。所以commit操作寫(xiě)入文件是同步的惶洲,其中commitToMemory是將mModified
中的修改寫(xiě)入到內(nèi)存中按声。
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
synchronized (SharedPreferencesImpl.this.mLock) {
mapToWriteToDisk = mMap;
boolean hasListeners = mListeners.size() > 0;
synchronized (mLock) {
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
} else {
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
}
mModified.clear();
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
而寫(xiě)入文件操作在enqueueDiskWrite()里:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
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);
}
可以看到,它開(kāi)了一個(gè)線程專門(mén)去寫(xiě)入文件恬吕,所以有些人說(shuō)commit操作是在主線程執(zhí)行的签则,其實(shí)并不是,它只是讓主線程等待子線程完成寫(xiě)入而已铐料。
那么apply()方法:
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
它跟commit不同的地方就是渐裂,不在當(dāng)前線程等待文件寫(xiě)入線程執(zhí)行完畢,而是開(kāi)了一個(gè)線程余赢,并且沒(méi)有返回值芯义。所以它對(duì)于不需要返回值的調(diào)用者來(lái)說(shuō),效率更高妻柒。
再看看讀取操作:
@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) {
}
}
}
讀取也是同步的扛拨,先加個(gè)鎖,在里面用while不斷判斷是否加載完成举塔,沒(méi)有就釋放鎖绑警,到它繼續(xù)執(zhí)行的時(shí)候,又會(huì)再判斷是否完成央渣。
如此计盒,SharedPreferences就解析完了。
小結(jié)
- 它采用通過(guò)XmlParser將文件以Map的形式加載到內(nèi)存中的方式芽丹,避免頻繁文件讀寫(xiě)北启。
- 它將修改保存在一個(gè)臨時(shí)的Map中,commit/apply的時(shí)候拔第,再統(tǒng)一提交到內(nèi)存和文件中咕村。
- commit操作會(huì)阻塞當(dāng)前線程,不需要等待SharedPreferences操作結(jié)果的話蚊俺,使用apply()效率更高
- 第一次使用SharedPreferences時(shí)懈涛,它有一個(gè)異步讀取xml文件到內(nèi)存的過(guò)程,所以立即讀取值的話會(huì)失敗泳猬。
關(guān)于線程同步批钠,可以參考:
java并發(fā)之原子性宇植、可見(jiàn)性、有序性
volatile你了解多少埋心?
深入淺出Synchronized
synchronized(this)指郁、synchronized(class)與synchronized(Object)的區(qū)別