喜歡的朋友,點個讚唄鼓勵鼓勵唄~java
本文章針對 Android 7.0 源碼進行分析
SharedPreferences
是 Android 中比較經常使用的存儲方法,它能夠用來存儲一些比較小的鍵值對集合,並最終會在手機的/data/data/package_name/shared_prefs/
目錄下生成一個 xml 文件存儲數據。它的使用很是簡單,是一個 Android 開發者的基本技能,在這裏不加以闡述了。android
SharedPreferences
帶給咱們很是簡單易用的數據存儲讀寫功能的同時,不知你們有沒有好奇過它底層是怎樣實現的呢?緩存
經過ContextImpl.getSharedPreferences
方法可以獲取SharedPreferences
對象, 經過getXxx/putXxx
方法可以進行讀寫操做,經過commit
方法同步寫磁盤,經過apply
方法異步寫磁盤。其中涉及到以下幾個問題:安全
SharedPreferences
對象過程當中,系統作了什麼?getXxx
方法作了什麼?putXxx
方法作了什麼?commit/apply
方法如何實現同步/異步寫磁盤?下面,咱們來一一解答這些疑惑。app
咱們直接看ContextImpl.getSharedPreferences
的源碼:
異步
@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;
}複製代碼
這段源碼的流程仍是清晰易懂的,註釋已經說得很明白,這裏咱們總結一下這個方法的要點:ide
SharedPreferences
對象,也就是說,屢次調用getSharedPreferences
方法並不會對性能形成多大影響,由於又緩存機制SharedPreferences
對象的建立過程是線程安全的,由於使用了synchronize
關鍵字mode
使用了Context.MODE_MULTI_PROCESS
,那麼將會調用sp.startReloadIfChangedUnexpectedly()
方法,在startReloadIfChangedUnexpectedly
方法中,會判斷是否由其餘進程修改過這個文件,若是有,會從新從磁盤中讀取文件加載數據接着,咱們重點關注註釋中的sp = new SharedPreferencesImpl(file, mode);//構造一個SharedPreferencesImpl
對象
這句代碼。post
// 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
這個類的構造方法作一個總結:性能
file
以及mode
分別保存在mFile
以及mMode
中.bak
備份文件,當用戶寫入失敗的時候會根據這個備份文件進行恢復工做mMap
初始化爲null
startLoadFromDisk()
方法加載數據上面四個要點中,最重要的就是最後一步,調用startLoadFromDisk()
方法加載數據:ui
// 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();
}
}複製代碼
上面的源碼中,咱們對startLoadFromDisk()
方法進行了分析,有分析咱們能夠獲得如下幾點總結:
getSharedPreferences
方法的時候,會從磁盤中加載數據,而數據的加載時經過開啓一個子線程調用loadFromDisk
方法進行異步讀取的mMap
中mStatTimestamp
以及mStatSize
中(保存這兩個值有什麼用呢?咱們在分析getSharedPreferences
方法時說過,若是有其餘進程修改了文件,而且mode
爲MODE_MULTI_PROCESS
,將會判斷從新加載文件。如何判斷文件是否被其餘進程修改過,沒錯,根據文件修改時間以及文件大小便可知道)notifyAll()
方法通知喚醒其餘等待線程,數據已經加載完畢好了,至此,咱們就解決了第一個疑問:調用ContextImpl.getSharedPreferences
方法獲取一個SharedPreferences
對象的過程,系統作了什麼工做?
下面給出一個時序流程圖:
咱們以getString
來分析這個問題:
@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) {
}
}
}複製代碼
getString
方法代碼很簡單,其餘的例如getInt
,getFloat
方法也是同樣的原理,咱們直接對這個疑問進行總結:
getXxx
方法是線程安全的,由於使用了synchronize
關鍵字getXxx
方法是直接操做內存的,直接從內存中的mMap
中根據傳入的key
讀取value
getXxx
方法有可能會卡在awaitLoadedLocked
方法,從而致使線程阻塞等待(何時會出現這種阻塞現象呢?前面咱們分析過,第一次調用getSharedPreferences
方法時,會建立一個線程去異步加載數據,那麼假如在調用完getSharedPreferences
方法以後當即調用getXxx
方法,此時的mLoaded
頗有可能爲false
,這就會致使awaiteLoadedLocked
方法阻塞等待,直到loadFromDisk
方法加載完數據而且調用notifyAll
來喚醒全部等待線程)說到寫操做方法,首先想到的是經過sharedPreferences.edit()
方法返回的SharedPreferences.Editor
,全部咱們對SharedPreferences
的寫操做都是基於這個Editor
類的。在 Android 系統中,Editor
是一個接口類,它的具體實現類是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;
}
}
public Editor putStringSet(String key, @Nullable Set<String> values) {
synchronized (this) {
mModified.put(key, (values == null) ? null : new HashSet<String>(values));
return this;
}
}
public Editor putInt(String key, int value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
public Editor putLong(String key, long value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
public Editor putFloat(String key, float value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
public Editor putBoolean(String key, boolean value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
public Editor remove(String key) {
synchronized (this) {
mModified.put(key, this);
return this;
}
}
......
其餘方法
......
}複製代碼
從EditorImpl
類的源碼咱們能夠得出如下總結:
SharedPreferences
的寫操做是線程安全的,由於使用了synchronize
關鍵字mModified
中,而並非直接對SharedPreferences.mMap
進行操做(mModified
會在commit/apply
方法中起到同步內存SharedPreferences.mMap
以及磁盤數據的做用)先分析commit()
方法,直接上源碼:
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;
}複製代碼
commit()
方法的主體結構很清晰簡單:
SharedPreferences.mMap
中(將mModified
同步到mMap
)enqueueDiskWrite
方法將數據寫入到磁盤上commit()
方法會同步阻塞等待的緣由)registerOnSharedPreferenceChangeListener
方法註冊監聽)true
or false
看完了commit()
,咱們接着來看一下它調用的commitToMemory()
方法:
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;
}複製代碼
總的來講,commitToMemory()
方法主要作了這幾件事:
mDiskWritesInFlight
自增1(mDiskWritesInFlight
表明「此時須要將數據寫入磁盤,但還未處理或未處理完成的次數」,提示,整個SharedPreferences
的源碼中,惟獨在commitToMemory()
方法中「有且僅有」一處代碼會對mDiskWritesInFlight
進行增長,其餘地方都是減)mcr.mapToWriteToDisk
指向mMap
,mcr.mapToWriteToDisk
就是最終須要寫入磁盤的數據mClear
的值,若是是true
,清空mMap
(調用clear()
方法,會設置mClear
爲true
)mModified
數據到mMap
中,而後清空mModified
MemoryCommitResult
對象,這個對象的mapToWriteToDisk
參數指向了最終須要寫入磁盤的mMap
須要注意的是,在commitToMemory()
方法中,當mClear
爲true
,會清空mMap
,但不會清空mModified
,因此依然會遍歷mModified
,將其中保存的寫記錄同步到mMap
中,因此下面這種寫法是錯誤的:
sharedPreferences.edit()
.putString("key1", "value1") // key1 不會被 clear 掉,commit 以後依舊會被寫入磁盤中
.clear()
.commit();複製代碼
分析完commitToMemory()
方法,咱們再回到commit()
方法中,對它調用的enqueueDiskWrite
方法進行分析:
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);
}複製代碼
上面的註釋已經說得很明白了,在這裏就不總結了,接着來分析下writeToFile
這個方法:
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);
}複製代碼
writeToFile
這個方法大體分爲三個過程:
mFile
中一次性寫入全部鍵值對數據,即mcr.mapToWriteToDisk
(這就是commitToMemory
所說的保存了全部鍵值對數據的字段) 一次性寫入到磁盤。 若是寫入成功則刪除備份(災備)文件,同時記錄了此次同步的時間經過上面的分析,咱們對commit()
方法的整個調用鏈以及它幹了什麼都有了認知,下面給出一個圖方便記憶理解:
分析完commit()
方法,再去分析apply()
方法就輕鬆多了:
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);
} 複製代碼
總結一下apply()
方法:
commitToMemory()
方法將mModified
中記錄的寫操做同步回寫到內存 SharedPreferences.mMap
中。此時, 任何的getXxx
方法均可以獲取到最新數據了enqueueDiskWrite
方法調用writeToFile
將方法將全部數據異步寫入到磁盤中下面也給出一個apply()
時序流程圖幫助記憶理解:
SharedPreferences
是線程安全的,它的內部實現使用了大量synchronized
關鍵字SharedPreferences
不是進程安全的getSharedPreferences
會加載磁盤 xml 文件(這個加載過程是異步的,經過new Thread
來執行,因此並不會在構造SharedPreferences
的時候阻塞線程,可是會阻塞getXxx/putXxx/remove/clear
等調用),但後續調用getSharedPreferences
會從內存緩存中獲取。 若是第一次調用getSharedPreferences
時還沒從磁盤加載完畢就立刻調用 getXxx/putXxx
, 那麼getXxx/putXxx
操做會阻塞,直到從磁盤加載數據完成後才返回getXxx
都是從內存中取的數據,數據來源於SharedPreferences.mMap
apply
同步回寫(commitToMemory()
)內存SharedPreferences.mMap
,而後把異步回寫磁盤的任務放到一個單線程的線程池隊列中等待調度。apply
不須要等待寫入磁盤完成,而是立刻返回commit
同步回寫(commitToMemory()
)內存SharedPreferences.mMap
,而後若是mDiskWritesInFlight
(此時須要將數據寫入磁盤,但還未處理或未處理完成的次數)的值等於1,那麼直接在調用commit
的線程執行回寫磁盤的操做,不然把異步回寫磁盤的任務放到一個單線程的線程池隊列中等待調度。commit
會阻塞調用線程,知道寫入磁盤完成才返回MODE_MULTI_PROCESS
是在每次getSharedPreferences
時檢查磁盤上配置文件上次修改時間和文件大小,一旦全部修改則會從新從磁盤加載文件,因此並不能保證多進程數據的實時同步MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE
。一旦指定, 直接拋異常不要使用SharedPreferences
做爲多進程通訊手段。因爲沒有使用跨進程的鎖,就算使用MODE_MULTI_PROCESS
,SharedPreferences
在跨進程頻繁讀寫有可能致使數據所有丟失。根據線上統計,SP 大約會有萬分之一的損壞率
每一個 SP 文件不能過大。SharedPreference
的文件存儲性能與文件大小相關,咱們不要將毫無關聯的配置項保存在同一個文件中,同時考慮將頻繁修改的條目單獨隔離出來
仍是每一個 SP 文件不能過大。在第一個getSharedPreferences
時,會先加載 SP 文件進內存,過大的 SP 文件會致使阻塞,甚至會致使 ANR
apply
或者commit
,都會把所有的數據一次性寫入磁盤, 因此 SP 文件不該該過大, 影響總體性能喜歡的朋友,點個讚唄鼓勵鼓勵唄~