SharedPreferences作爲Android常用的持久化組件,很常用,在系統設置應用中就大量地被使用。相信大家基本都是用過,但是在使用過程中,大家是否又知道它的實現原理呢?
基本使用
SharedPreferences使用很簡單,比如我們要它保存某個字符串,在Activity使用如下:
/**
* save number
* @param num
*/
private void saveNumber(String num) {
SharedPreferences sharedPreferences = getSharedPreferences("light_persist", MODE_PRIVATE);
sharedPreferences.edit().putString("number", num).apply();
//sharedPreferences.edit().putString("number", number).commit();
}
上面代碼執行後,會將變量num保存在/data/data/應用包名/shared_prefs/light_persist.xml。SharedPreferences使用很簡單,但這並不表示它本身也簡單,下面從三個角度來深入瞭解SharedPreferences的實現原理
對象初始化
在上面的例子中,我們是在Activity通過調用getSharedPreferences()獲取SharedPreferences對象,我們看下它的具體實現是怎樣的。首先,我們要明白以下幾點:
- Activity繼承了ContextThemeWrapper,ContextThemeWrapper繼承了ContextWrapper,而ContextWrapper繼承了抽象類Context,getSharedPreferences()爲Context中的抽象方法;ContextWrapper重寫了getSharedPreferences()方法,所以Activity中調用getSharedPreferences()方法,實際上調用的是ContextWrapper裏面的方法。
- ContextWrapper中getSharedPreferences()方法的實現是調用了Context對象mBase的getSharedPreferences()。mBase具體對象爲ContextImpl,也就是說Context.getSharedPreferences()具體實現在ContextImpl中。
對於第一點,通過類的繼承關係很容易知道;對於第二點,這個會涉及到Activity的啓動過程,在ActivityThread的performLaunchActivity()方法中會初始化context,context初始化的時序圖如下
Activity在啓動的時候會創建ContextImpl對象,並調用Activity的attach()方法將ContextImpl對象傳遞下去,最終給ContextWrapper的Context對象mBase賦值。
在明白Activity中調用getSharedPreferences()方法實際上調用了CcontextImpl.getSharedPreferences()後,我們就很容易明白SharedPreferences對象是如何初始化的了。SharedPreferences對象初始化過程如下所示
下面我們就看下SharedPreference初始化的代碼實現
@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) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
//從緩存集合中取name對應的file,如果集合中沒有,則根據name創建對應的file並添加進集合
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//getSharedPreferencesCacheLocked()根據應用包名從緩存集合中取ArrayMap集合
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
//mode校驗,在Android7.0開始不再支持MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE,
//也就是不支持其他應用讀寫該應用的SharedPreference文件。
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");
}
}
//創建SharedPreferences接口實現類對象SharedPreferencesImpl,並存儲在集合中。
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
//如果mode設置爲了MODE_MULTI_PROCESS且targetSdkVersion小於Android3.0,則從硬盤中加載SharedPreferences文件
//MODE_MULTI_PROCESS是對應用多進程的一種支持,但是在Android3.0版本開始已經棄用了
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;
}
至此SharedPreferencesImpl對象就創建完成了,至於SharedPreferencesImpl的構造方法,具體就不展開,它主要作用是變量初始化、創建備份文件和從硬盤中加載SharedPredference文件。最後我們看下SharedPreferences接口說明。
* Interface for accessing and modifying preference data returned by {@link
* Context#getSharedPreferences}. For any particular set of preferences,
* there is a single instance of this class that all clients share.
* Modifications to the preferences must go through an {@link Editor} object
* to ensure the preference values remain in a consistent state and control
* when they are committed to storage. Objects that are returned from the
* various <code>get</code> methods must be treated as immutable by the application.
*
* <p><em>Note: This class does not support use across multiple processes.</em>
有兩點需要注意:一,對於任何特定的一組preferences,所有客戶端都共享一個此接口實現類(也就是SharedPreferencesImpl)的單個實例,這個一點可以在ContextImpl.getSharedPreferencesCacheLocked()方法中得到驗證,它是根據應用包名從緩存集合中取ArrayMap集合。二,不支持跨進程使用,這個在Android3.0已經棄用了。
寫入實現
往SharedPreferences中保存數據,我們是通過Editor接口實現的,通過調用Editor.putXxx().apply()或Editor.putXxx().commit()方法將數據保存起來。我們先看下涉及到的類關係圖
Editor是SharedPreferences接口的嵌套接口,它的實現類是SharedPreferencesImpl的嵌套類EditorImpl,所以要看數據是怎麼寫入的,我們就需要分析EditorImpl裏面的putXxx() 和apply()或者commit()方法,大多數同學應該知道commit()和apply()方法的區別,前者是同步方法且運行在主線程中,後者是異步方法運行在子線程中,我們先看下apply()的實現方式,理解apply()之後,commit()也就不再話下。
首先創建Editor接口實現類EditorImpl實例對象,然後調用EditorImpl.putString(),最後通過apply()提交一個寫入數據的異步任務。我們重點關注下apply()方法實現,它的實現分爲以下幾步:
- 創建MemoryCommitResult對象,該對象中存儲了寫入SharedPreferences的數據。
- 創建阻塞CountDownLatch(MemoryCommitReuslt.writtenToDiskLatch)的任務,調用QueueWork.addFinisher()方法緩存,緩存的任務不一定會在QueueWork執行。唯一可能會執行的情況是QueueWork.waitToFinish()方法中調用。而waitToFinish()方法將會在Activity的onPause()之後,BroadcastReceiver的onReceive之後,Service的stop的時候調用,具體調用的地方是在ActivityThread中,感興趣的同學可查閱AcaptivityThread。
- 將寫入硬盤的操作的任務入QueueWork隊列,由QueueWork中的HandlerThread執行寫入的任務。
- 通知監聽
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//這裏作用是如果寫preference到硬盤沒有結束,就阻塞QueueWork中執行寫preference的線程,直到寫入完成。如果preference已經寫入硬盤,寫入過程中會調用mcr.writtenToDiskLatch.countDown()方法,而此時下面的await()方法不會阻塞,因爲writtenToDiskLatch初始化的count爲1.
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
//addFinisher方法的實現是將Runnable對象加入到一個名爲sFinisher的集合,Finisher可以理解爲執行最後操作的管理者。
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
//將等待任務從Finisher中移除,因爲此Runnable是在寫入操作完成之後調用,所以就需要從 QueueWork.sFinisher集合中移除;
QueuedWork.removeFinisher(awaitCommit);
}
};
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);
}
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) {
//將preference寫入磁盤,並調用mcr.writtenToDiskLatch.countDown()釋放latch
//writtenToDiskLatch初始化的latch個數爲1;
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;
}
}
//QueueWork.queue()最終實現是通過Queue中的HandlerThread執行writeToDiskRunnable任務。
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
以上就是通過apply()方式寫入preference的實現方式,相關說明已在代碼中註釋。補充一點,CountDownLatch是一種同步機制,作用類似於它的字面意思,可以理解它是一道門,門上有一個或者多個門閂,要想通過這道門只有所有門閂都打開後通過這道門,否則只能一直等待。
有一點需要注意,當我們通過Editor改變某個SharedPreferences文件的一項preference時,實際上最終會將該文件的所有preference重新寫入文件,這一點可以從commitToMemory()方法中看出,所以建議不要用SharedPreferences存儲數據量大的內容;另外頻繁改變的數據應和其他數據放在不同的SharedPreferences文件中。
// Returns true if any changes were made
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.
//當前寫入磁盤的操作大於0,則先拷貝
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;//mMap就是所有的preference
mDiskWritesInFlight++;//此變量會在真正寫入磁盤的操作完成後遞減
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
//是否調用了Edit.clear()方法
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.
//如果value爲EditorImpl對象本身或者爲null,則將它從map中移除
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);
}
至於Editor.commit()方法就不展開了,commit實現比apply()更簡單些。
讀取實現
在SharedPrefrencesImpl的構造方法中會加載保存的preference,並存入集合mMap。當我們通過SharedPreferences.getXxx()方法是,就是從mMap中取出對應的值。
@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;
}
}
總結
-
SharedPreferences是接口,它的實現類是SharedPreferencesImpl。在Activity中調用getSharedPreferences()最終調用的是ContextImpl.getSharedPreferences()方法;
-
Eidtor.apply()將preference寫入磁盤的任務是在QueueWork中的HandlerThread線程中執行;而Eidtor.commit()將preference寫入磁盤的任務是運行在主線程中。兩者寫入的過程中都會利用CountDownLatch同步機制來保證寫入任務執行後才執行下一個任務。