最近在復習规肴,發(fā)現(xiàn)了關于多線程 多進程的問題镊折,面試中常問的涉及到了SharedPreference的知識焕盟,決定去看看源碼秋秤,到底是如何實現(xiàn)的。這里就不介紹它的用法和一些基礎知識了脚翘,直接開始源碼分析灼卢。
源碼解析
ContextImpl#getSharedPreferences
這里主要是看context.getSharedPreferences()方法,context的實現(xiàn)類是ContextImpl来农。
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<>();
}
//根據(jù)文件名獲取文件
file = mSharedPrefsPaths.get(name);
文件名為null,創(chuàng)建文件鞋真,并存入mSharedPrefsPaths集合中
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) {
// 獲取用于緩存的cache對象
cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
//檢查mode類型
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");
}
}
//可以看出SharedPreferencesImpl是SharedPreferences的實現(xiàn)類
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) {
//如果是多進程文件需要重新讀取文件
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
//若第一次調用該方法,則對全局變量sSharedPrefsCache進行初始化
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
//獲取包名
final String packageName = getPackageName();
//根據(jù)包名獲取packagePrefs
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
//創(chuàng)建packagePrefs沃于,并放入緩存中
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
SharedPreferencesImpl
構造方法
SharedPreferencesImpl(File file, int mode) {
mFile = file;
//file的備份文件
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
//從硬盤中加載數(shù)據(jù)
startLoadFromDisk();
}
startLoadFromDisk()
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
//創(chuàng)建并啟動了一個名為“SharedPreferencesImpl-load”的線程
new Thread("SharedPreferencesImpl-load") {
public void run() {
//從硬盤中加載數(shù)據(jù)
loadFromDisk();
}
}.start();
}
loadFromDisk()
private void loadFromDisk() {
synchronized (mLock) {
//前面出現(xiàn)過涩咖,用于判斷是否從硬盤中加載過,是則返回
if (mLoaded) {
return;
}
//判斷備份文件是否存在
if (mBackupFile.exists()) {
//刪除源文件
mFile.delete();
//將備份文件中的內容復制到源文件繁莹,作了替換
mBackupFile.renameTo(mFile);
}
}
...省略部分代碼
//用于臨時保存磁盤文件中加載解析后的鍵值對數(shù)據(jù)
Map<String, Object> map = null;
//用于保存文件的信息
StructStat stat = null;
//用于保存加載數(shù)據(jù)中產(chǎn)生的異常
Throwable thrown = null;
try {
//獲取文件的信息
stat = Os.stat(mFile.getPath());
//文件可讀檩互,則創(chuàng)建緩存輸入流
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//將文件中的數(shù)據(jù)解析成鍵值對,保存在map中
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) {
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
//設置狀態(tài)為加載完成
mLoaded = true;
mThrowable = thrown;
try {
if (thrown == null) {
if (map != null) {
//將結果保存在全局變量中
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
//若解析的數(shù)據(jù)為空咨演,即文件中沒有數(shù)據(jù)闸昨,對全局變量進行初始化
mMap = new HashMap<>();
}
}
} catch (Throwable t) {
mThrowable = t;
} finally {
//釋放鎖,隨機喚醒一個等待mLock鎖的線程
mLock.notifyAll();
}
}
}
從SharedPreferences中獲取數(shù)據(jù)
以getString為例
getString
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// 讓當前的線程等待從磁盤加載數(shù)據(jù)的線程加載完數(shù)據(jù)
awaitLoadedLocked();
// 從全局變量mMap中獲取數(shù)據(jù)
String v = (String)mMap.get(key);
// 如果獲取到數(shù)據(jù),就返回數(shù)據(jù)饵较,否則返回方法參數(shù)中給定的默認值
return v != null ? v : defValue;
}
}
awaitLoadedLocked
private void awaitLoadedLocked() {
// 如果從硬盤加載數(shù)據(jù)沒有完成
if (!mLoaded) {
//調用線程處理策略
BlockGuard.getThreadPolicy().onReadFromDisk();
}
// 如果從硬盤加載數(shù)據(jù)沒有完成拍嵌,則循環(huán)執(zhí)行
while (!mLoaded) {
try {
//釋放鎖等待
mLock.wait();
} catch (InterruptedException unused) {
}
}
//若等待期間發(fā)生異常,則拋出異常
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
寫數(shù)據(jù)
SharedPreferences.Editor
@Override
public Editor edit() {
// 同步鎖循诉,鎖對象為mLock
synchronized (mLock) {
// 讓當前的線程等待從磁盤加載數(shù)據(jù)的線程加載完數(shù)據(jù)
awaitLoadedLocked();
}
// 創(chuàng)建EditorImpl對象并返回
return new EditorImpl();
}
EditorImpl類
Editor是一個接口横辆,它的具體實現(xiàn)類是EditorImpl類。EditorImpl類是SharedPreferencesImpl類的內部類茄猫,SharedPreferences中對數(shù)據(jù)的增 刪 改都是通過調用Editor的相關方法實現(xiàn)的龄糊。
putString
private final Map mModified = new HashMap();// 用于臨時存儲需要寫入磁盤的數(shù)據(jù)或需要移除的數(shù)據(jù),之后統(tǒng)一處理
@Override
public Editor putString(String key, @Nullable String value) {
// 同步鎖募疮,鎖對象為mEditorLock
synchronized (mEditorLock) {
// 向全局變量mModified添加數(shù)據(jù)
mModified.put(key, value);
// 返回
return this;
}
}
提交數(shù)據(jù)到磁盤
commit()
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//對EditorImpl對象的操作(put remove clear等)進行整合處理
MemoryCommitResult mcr = commitToMemory();
//將數(shù)據(jù)寫入磁盤
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
//阻塞等待寫入過程完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
}
//對注冊當前SharedPreferences對象的監(jiān)聽器進行回調
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
commitToMemory
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;//用于存儲發(fā)生變化的鍵
Set<OnSharedPreferenceChangeListener> listeners = null;//用于存儲監(jiān)聽器
Map<String, Object> mapToWriteToDisk;//用于記錄需要寫入磁盤的所有的數(shù)據(jù)
synchronized (SharedPreferencesImpl.this.mLock) {
//若當前有線程在寫入
if (mDiskWritesInFlight > 0) {
//復制mMap對數(shù)據(jù)進行操作
mMap = new HashMap<String, Object>(mMap);
}
//獲取全局變量
mapToWriteToDisk = mMap;
//當前進行寫入操作的線程數(shù)加一
mDiskWritesInFlight++;
//是否有監(jiān)聽器
boolean hasListeners = mListeners.size() > 0;
//若有監(jiān)聽器
if (hasListeners) {
//進行初始化
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
//表示內存數(shù)據(jù)是否發(fā)生變化
boolean changesMade = false;
//若用戶調用clear方法清空數(shù)據(jù)
if (mClear) {
//若寫入的數(shù)據(jù)不為空炫惩,即有需要清除的數(shù)據(jù)
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
//對提交到Editor中的數(shù)據(jù)進行遍歷
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
//若值不為空,也不為自身,說明添加了新鍵值對或修改了鍵值對的值
//若打算寫入磁盤的數(shù)據(jù)包含k這個鍵阿浓,說明對值進行了修改
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
//若之前的值不為空他嚷,同時和現(xiàn)在的值相同說明實際沒有修改
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
//內存數(shù)據(jù)發(fā)生變化,次數(shù)加一
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
//創(chuàng)建MemoryCommitResult對象并返回
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
enqueueDiskWrite()
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//通過判斷參數(shù)postWriteRunnable是否為空芭毙,表示需要同步寫入磁盤還是異步寫入磁盤
final boolean isFromSyncCommit = (postWriteRunnable == null);
//創(chuàng)建writeToRunnable對象筋蓖,封裝寫入磁盤的核心操作
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
//寫入磁盤
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
//當前寫入磁盤的線程數(shù)量減一
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// 若為同步寫入磁盤
if (isFromSyncCommit) {
//表示是否有其他線程正在寫入
boolean wasEmpty = false;
synchronized (mLock) {
//若當前寫入磁盤的線程數(shù)量為1 說明除了本線程沒有其他線程寫入,wasEmpty為true
wasEmpty = mDiskWritesInFlight == 1;
}
//若沒有其他線程正在寫入
if (wasEmpty) {
//執(zhí)行線程
writeToDiskRunnable.run();
return;
}
}
//若為異步寫入或者同步寫入時有其他線程正在寫入退敦,則調用本方法異步寫入
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
apply()
public void apply() {
final long startTime = System.currentTimeMillis();
//對EditorImpl對象的操作進行整合處理
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
//阻塞等待數(shù)據(jù)寫入磁盤完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
//將awaitCommit對象保存到QueuedWork 當QueuedWork在執(zhí)行任務時需要阻塞時會調用
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
//將awaitCommit對象從QueuedWork中移除 QueuedWork.removeFinisher(awaitCommit);
}
};
//將數(shù)據(jù)寫入磁盤
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
從此可以看出粘咖,這兩個方法都是首先修改內存中緩存的mMap的值,然后將數(shù)據(jù)寫到磁盤中侈百。它們的主要區(qū)別是commit會等待寫入磁盤后再返回瓮下,而apply則在調用寫磁盤操作后就直接返回了,但是這時候可能磁盤中數(shù)據(jù)還沒有被修改钝域。