long startA = System.currentTimeMillis();
for (int i=0 ; i<200 ; i++){
SharedPreferences preferences = this.getSharedPreferences("testA", 0);
SharedPreferences.Editor edit = preferences.edit();
edit.putString("yc"+i,"yangchong"+i);
edit.commit();
}
long endA = System.currentTimeMillis();
long a = endA - startA;
Log.i("測試A","----"+a);
long startB = System.currentTimeMillis();
SharedPreferences preferencesB = this.getSharedPreferences("testB", 0);
SharedPreferences.Editor editB = preferencesB.edit();
for (int i=0 ; i<200 ; i++){
editB.putString("yc"+i,"yangchong"+i);
}
editB.commit();
long endB = System.currentTimeMillis();
long b = endB - startB;
Log.i("測試B","----"+b);
long startC = System.currentTimeMillis();
SharedPreferences.Editor editC = null;
for (int i=0 ; i<200 ; i++){
SharedPreferences preferencesC = this.getSharedPreferences("testC", 0);
if (editC==null){
editC = preferencesC.edit();
}
editC.putString("yc"+i,"yangchong"+i);
}
editC.commit();
long endC = System.currentTimeMillis();
long c = endC - startC;
Log.i("測試C","----"+c);
複製代碼
2019-08-30 15:08:16.982 3659-3659/com.cheoo.app I/測試A: ----105
2019-08-30 15:08:17.035 3659-3659/com.cheoo.app I/測試B: ----52
2019-08-30 15:08:17.069 3659-3659/com.cheoo.app I/測試C: ----34
2019-08-30 15:08:20.561 3659-3659/com.cheoo.app I/測試A: ----25
2019-08-30 15:08:20.562 3659-3659/com.cheoo.app I/測試B: ----1
2019-08-30 15:08:20.564 3659-3659/com.cheoo.app I/測試C: ----2
複製代碼
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="yc110">yangchong110</string>
<string name="yc111">yangchong111</string>
<string name="yc118">yangchong118</string>
<string name="yc119">yangchong119</string>
<string name="yc116">yangchong116</string>
<string name="yc117">yangchong117</string>
<string name="yc114">yangchong114</string>
<string name="yc115">yangchong115</string>
<string name="yc112">yangchong112</string>
<string name="yc113">yangchong113</string>
<string name="yc121">yangchong121</string>
<string name="yc122">yangchong122</string>
<string name="yc120">yangchong120</string>
<string name="yc129">yangchong129</string>
<string name="yc127">yangchong127</string>
<string name="yc128">yangchong128</string>
<string name="yc125">yangchong125</string>
<string name="yc126">yangchong126</string>
<string name="yc123">yangchong123</string>
<string name="yc124">yangchong124</string>
<string name="yc1">yangchong1</string>
<string name="yc109">yangchong109</string>
<string name="yc0">yangchong0</string>
<string name="yc3">yangchong3</string>
</map>
複製代碼
long startA = System.currentTimeMillis();
for (int i=0 ; i<200 ; i++){
SharedPreferences preferences = activity.getSharedPreferences("testA", 0);
SharedPreferences.Editor edit = preferences.edit();
edit.putString("yc"+i,"yangchong"+i);
edit.apply();
}
long endA = System.currentTimeMillis();
long a = endA - startA;
Log.i("測試A","----"+a);
long startB = System.currentTimeMillis();
SharedPreferences preferencesB = activity.getSharedPreferences("testB", 0);
SharedPreferences.Editor editB = preferencesB.edit();
for (int i=0 ; i<200 ; i++){
editB.putString("yc"+i,"yangchong"+i);
}
editB.apply();
long endB = System.currentTimeMillis();
long b = endB - startB;
Log.i("測試B","----"+b);
long startC = System.currentTimeMillis();
SharedPreferences.Editor editC = null;
for (int i=0 ; i<200 ; i++){
SharedPreferences preferencesC = activity.getSharedPreferences("testC", 0);
if (editC==null){
editC = preferencesC.edit();
}
editC.putString("yc"+i,"yangchong"+i);
}
editC.apply();
long endC = System.currentTimeMillis();
long c = endC - startC;
Log.i("測試C","----"+c);
複製代碼
2019-08-30 15:17:07.341 5522-5522/com.cheoo.app I/測試A: ----54
2019-08-30 15:17:07.346 5522-5522/com.cheoo.app I/測試B: ----5
2019-08-30 15:17:07.352 5522-5522/com.cheoo.app I/測試C: ----6
2019-08-30 15:17:10.541 5522-5522/com.cheoo.app I/測試A: ----32
2019-08-30 15:17:10.542 5522-5522/com.cheoo.app I/測試B: ----1
2019-08-30 15:17:10.543 5522-5522/com.cheoo.app I/測試C: ----1
複製代碼
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
複製代碼
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
複製代碼
@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);
if (file == null) {
// 建立一個對應路徑 /data/data/packageName/name 的 File 對象
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
// 這裏調用了 getSharedPreferences(File file, int mode) 方法
return getSharedPreferences(file, mode);
}
複製代碼
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
// 這裏使用了 synchronized 關鍵字,確保了 SharedPreferences 對象的構造是線程安全的
synchronized (ContextImpl.class) {
// 獲取SharedPreferences 對象的緩存,並複製給 cache
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
// 以參數 file 做爲 key,獲取緩存對象
sp = cache.get(file);
if (sp == null) { // 若是緩存中不存在 SharedPreferences 對象
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 對象
sp = new SharedPreferencesImpl(file, mode);
// 放入緩存 cache 中,方便下次直接從緩存中獲取
cache.put(file, sp);
// 返回新構造的 SharedPreferencesImpl 對象
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.
// 若是由其餘進程修改了這個 SharedPreferences 文件,咱們將會從新加載它
sp.startReloadIfChangedUnexpectedly();
}
// 程序走到這裏,說明命中了緩存,SharedPreferences 已經建立,直接返回
return sp;
}
複製代碼
// SharedPreferencesImpl.java
// 構造方法
SharedPreferencesImpl(File file, int mode) {
mFile = file;
// 建立災備文件,命名爲prefsFile.getPath() + ".bak"
mBackupFile = makeBackupFile(file);
mMode = mode;
// mLoaded表明是否已經加載完數據
mLoaded = false;
// 解析 xml 文件獲得的鍵值對就存放在mMap中
mMap = null;
// 顧名思義,這個方法用於加載 mFile 這個磁盤上的 xml 文件
startLoadFromDisk();
}
// 建立災備文件,用於當用戶寫入失敗的時候恢復數據
private static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
複製代碼
// SharedPreferencesImpl.java
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
//注意:這裏咱們能夠看出,SharedPreferences 是經過開啓一個線程來異步加載數據的
new Thread("SharedPreferencesImpl-load") {
public void run() {
// 這個方法纔是真正負責從磁盤上讀取 xml 文件數據
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (SharedPreferencesImpl.this) {
// 若是正在加載數據,直接返回
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 {
// 讀取數據而且將數據解析爲jia
str = new BufferedInputStream(
new FileInputStream(mFile), *);
map = XmlUtils.readMapXml(str);
} catch (XmlPullParserException | IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
/* ignore */
}
synchronized (SharedPreferencesImpl.this) {
// 加載數據成功,設置 mLoaded 爲 true
mLoaded = true;
if (map != null) {
// 將解析獲得的鍵值對數據賦值給 mMap
mMap = map;
// 將文件的修改時間戳保存到 mStatTimestamp 中
mStatTimestamp = stat.st_mtime;
// 將文件的大小保存到 mStatSize 中
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
// 通知喚醒全部等待的線程
notifyAll();
}
}
複製代碼
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
複製代碼
public final class EditorImpl implements Editor {
// putXxx/remove/clear等寫操做方法都不是直接操做 mMap 的,而是將全部
// 的寫操做先記錄在 mModified 中,等到 commit/apply 方法被調用,纔會將
// 全部寫操做同步到 內存中的 mMap 以及磁盤中
private final Map<String, Object> mModified = Maps.newHashMap();
//
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
......
其餘方法
......
}
複製代碼
@Nullable
public String getString(String key, @Nullable String defValue) {
// synchronize 關鍵字用於保證 getString 方法是線程安全的
synchronized (this) {
// 方法 awaitLoadedLocked() 用於確保加載完數據並保存到 mMap 中才進行數據讀取
awaitLoadedLocked();
// 根據 key 從 mMap中獲取 value
String v = (String)mMap.get(key);
// 若是 value 不爲 null,返回 value,若是爲 null,返回默認值
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();
}
// 前面咱們說過,mLoaded 表明數據是否已經加載完畢
while (!mLoaded) {
try {
// 等待數據加載完成以後才返回繼續執行代碼
wait();
} catch (InterruptedException unused) {
}
}
}
複製代碼
public boolean commit() {
// 前面咱們分析 putXxx 的時候說過,寫操做的記錄是存放在 mModified 中的
// 在這裏,commitToMemory() 方法就負責將 mModified 保存的寫記錄同步到內存中的 mMap 中
// 而且返回一個 MemoryCommitResult 對象
MemoryCommitResult mcr = commitToMemory();
// enqueueDiskWrite 方法負責將數據落地到磁盤上
SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */);
try {
// 同步等待數據落地磁盤工做完成才返回
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
}
// 通知觀察者
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
複製代碼
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
// 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); } // 將 mMap 賦值給 mcr.mapToWriteToDisk,mcr.mapToWriteToDisk 指向的就是最終寫入磁盤的數據 mcr.mapToWriteToDisk = mMap; // mDiskWritesInFlight 表明的是「此時須要將數據寫入磁盤,但還未處理或未處理完成的次數」 // 將 mDiskWritesInFlight 自增1(這裏是惟一會增長 mDiskWritesInFlight 的地方) mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { mcr.keysModified = new ArrayList<String>(); mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { // 只有調用clear()方法,mClear才爲 true if (mClear) { if (!mMap.isEmpty()) { mcr.changesMade = true; // 當 mClear 爲 true,清空 mMap mMap.clear(); } mClear = false; } // 遍歷 mModified for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); // 獲取 key Object v = e.getValue(); // 獲取 value // 當 value 的值是 "this" 或者 null,將對應 key 的鍵值對數據從 mMap 中移除 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); } mcr.changesMade = true; if (hasListeners) { mcr.keysModified.add(k); } } // 將 mModified 同步到 mMap 以後,清空 mModified 歷史記錄 mModified.clear(); } } return mcr; } 複製代碼
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
// 建立一個 Runnable 對象,該對象負責寫磁盤操做
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
// 顧名思義了,這就是最終經過文件操做將數據寫入磁盤的方法了
writeToFile(mcr);
}
synchronized (SharedPreferencesImpl.this) {
// 寫入磁盤後,將 mDiskWritesInFlight 自減1,表明寫磁盤的需求減小一個
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
// 執行 postWriteRunnable(提示,在 apply 中,postWriteRunnable 纔不爲 null)
postWriteRunnable.run();
}
}
};
// 若是傳進的參數 postWriteRunnable 爲 null,那麼 isFromSyncCommit 爲 true
// 舒適提示:從上面的 commit() 方法源碼中,能夠看出調用 commit() 方法傳入的 postWriteRunnable 爲 null
final boolean isFromSyncCommit = (postWriteRunnable == null);
// Typical #commit() path with fewer allocations, doing a write on the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
// 若是此時只有一個 commit 請求(注意,是 commit 請求,而不是 apply )未處理,那麼 wasEmpty 爲 true
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
// 當只有一個 commit 請求未處理,那麼無需開啓線程進行處理,直接在本線程執行 writeToDiskRunnable 便可
writeToDiskRunnable.run();
return;
}
}
// 將 writeToDiskRunnable 方法線程池中執行
// 程序執行到這裏,有兩種可能:
// 1. 調用的是 commit() 方法,而且當前只有一個 commit 請求未處理
// 2. 調用的是 apply() 方法
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
private void writeToFile(MemoryCommitResult mcr) {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
if (!mcr.changesMade) {
// If the file already exists, but no changes were
// made to the underlying map, it's wasteful to // re-write the file. Return as if we wrote it // out. mcr.setDiskWriteResult(true); return; } if (!mBackupFile.exists()) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); mcr.setDiskWriteResult(false); return; } } else { mFile.delete(); } } // Attempt to write the file, delete the backup and return true as atomically as // possible. If any exception occurs, delete the new file; next time we will restore // from the backup. 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 = Libcore.os.stat(mFile.getPath()); synchronized (this) { mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { // Do nothing } // Writing was successful, delete the backup file if there is one. mBackupFile.delete(); mcr.setDiskWriteResult(true); return; } catch (XmlPullParserException e) { Log.w(TAG, "writeToFile: Got exception:", e); } catch (IOException e) { Log.w(TAG, "writeToFile: Got exception:", e); } // Clean up an unsuccessfully written file if (mFile.exists()) { if (!mFile.delete()) { Log.e(TAG, "Couldn't clean up partially-written file " + mFile); } } mcr.setDiskWriteResult(false); } 複製代碼
public void apply() {
// 將 mModified 保存的寫記錄同步到內存中的 mMap 中,而且返回一個 MemoryCommitResult 對象
final MemoryCommitResult mcr = commitToMemory();
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);
}
};
// 將數據落地到磁盤上,注意,傳入的 postWriteRunnable 參數不爲 null,因此在
// enqueueDiskWrite 方法中會開啓子線程異步將數據寫入到磁盤中
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); } 複製代碼