史上最全面,清晰的SharedPreferences解析
基礎(chǔ)用法
獲取Sp:
get
put
監(jiān)聽(tīng)器
原理分析
獲取SharedPreferences
構(gòu)造SharedPreferences
getX原理分析
putX原理分析
創(chuàng)建editor
putString
apply
apply總結(jié)
commit
SharedPreferences最佳實(shí)踐
勿存儲(chǔ)過(guò)大value
勿存儲(chǔ)復(fù)雜數(shù)據(jù)
不要亂edit和apply,盡量批量修改一次提交
建議apply,少用commit
registerOnSharedPreferenceChangeListener弱引用問(wèn)題
apply和commit對(duì)registerOnSharedPreferenceChangeListener的影響
不要有任何用SP進(jìn)行多進(jìn)程存儲(chǔ)的幻想
基礎(chǔ)用法
獲取Sp:
Activity中:getPreferences(int mode)
context.getSharedPreferences(String name, int mode)
PreferenceManager.getDefaultSharedPreferences(Context context)
1和3的獲取SP的方法最終都會(huì)調(diào)用2,只是1和3默認(rèn)選取了特定的name,1中通過(guò)getLocalClassName()獲取通過(guò)包名和類(lèi)名拼裝的name,3通過(guò)context.getPackageName() + "_preferences"獲取name
注意第二個(gè)參數(shù)的含義,現(xiàn)在均指定為MODE_PRIVATE,其余的都被廢棄。含義如下:File creation mode: the default mode, where the created file can only be accessed by the calling application (or all applications sharing the same user ID).
所存儲(chǔ)的數(shù)據(jù)保存在:/data/data/<package name>/shared_prefs下的指定name.xml文件中
get
sp.getX(String key, X value);
1
X為簡(jiǎn)單的基本類(lèi)型:float,int,long,String,Boolean,Set
put
SharedPreferences sharedPreferences = getPreferences(0);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putFloat("float", 1f);
editor.putBoolean("boolean", true);
editor.apply();
1
2
3
4
5
首先獲取Editor對(duì)象,操作完需要進(jìn)行事務(wù)提交操作,可以采用commit或者apply進(jìn)行。commit同步寫(xiě)磁盤(pán),返回是否成功的標(biāo)識(shí)碼。apply異步寫(xiě)磁盤(pán),無(wú)返回值。(二者均是同步寫(xiě)內(nèi)存,先同步寫(xiě)內(nèi)存,之后同步/異步寫(xiě)磁盤(pán))
監(jiān)聽(tīng)器
sharedPreferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
}
});
1
2
3
4
5
6
如果使用匿名內(nèi)部類(lèi)的形式進(jìn)行監(jiān)聽(tīng)。注意,因?yàn)镺nSharedPreferenceChangeListener的引用被保存在一個(gè)WeakHashMap中,導(dǎo)致程序的行為不確定性。為了避免這種情況,推薦以下方式:
private OnSharedPreferenceChangeListener mListener = new OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(
SharedPreferences sharedPreferences, String key) {
Log.i(LOGTAG, "instance variable key=" + key);
}
};
@Override
protected void onResume() {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).registerOnSharedPreferenceChangeListener(mListener);
super.onResume();
}
@Override
protected void onPause() {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).unregisterOnSharedPreferenceChangeListener(mListener);
super.onPause();
原理分析
獲取SharedPreferences
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
if (sSharedPrefs == null) {
sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
}
final String packageName = getPackageName();
ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
sSharedPrefs.put(packageName, packagePrefs);
}
// 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";
}
}
sp = packagePrefs.get(name);
if (sp == null) {
File prefsFile = getSharedPrefsFile(name);
sp = new SharedPreferencesImpl(prefsFile, mode);
packagePrefs.put(name, 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.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
可見(jiàn) sdk 是先取了緩存(sSharedPrefs靜態(tài)變量), 如果緩存未命中, 才構(gòu)造對(duì)象. 也就是說(shuō), 多次 getSharedPreferences 幾乎是沒(méi)有代價(jià)的. 同時(shí), 實(shí)例的構(gòu)造被 synchronized 關(guān)鍵字包裹, 因此構(gòu)造過(guò)程是多線程安全的
構(gòu)造SharedPreferences
第一次構(gòu)建SharedPreferences對(duì)象
// SharedPreferencesImpl.java
SharedPreferencesImpl(File file, int www.huarenyl.cn mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
startLoadFromDisk(www.mcyllpt.com);
幾個(gè)關(guān)鍵類(lèi)成員信息解釋如下
1. mFile 代表我們磁盤(pán)上的配置文件
2. mBackupFile 是一個(gè)災(zāi)備文件, 用戶(hù)寫(xiě)入失敗時(shí)進(jìn)行恢復(fù), 后面會(huì)再說(shuō). 其路徑是 mFile 加后綴 ‘.bak’
3. mMap 用于在內(nèi)存中緩存我們的配置數(shù)據(jù), 也就是 getXxx 數(shù)據(jù)的來(lái)源
重點(diǎn)關(guān)注startLoadFromDisk()方法
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run(www.thd178.com ) {
loadFromDisk(www.taohuayuan178.com );
開(kāi)啟了一個(gè)從Disk讀取的線程
// SharedPreferencesImpl.java
private void loadFromDisk(www.douniu157.com) {
synchronized (SharedPreferencesImpl.this) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
... 略去無(wú)關(guān)代碼 ...
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
map = XmlUtils.readMapXml(str);
synchronized (SharedPreferencesImpl.this) {
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();;
}
notifyAll();
loadFromDisk()非常關(guān)鍵,他總共做了以下幾件事
1. 如果有 ‘災(zāi)備’ 文件, 則直接使用災(zāi)備文件回滾.
2. 把配置從磁盤(pán)讀取到內(nèi)存的并保存在 mMap 字段中(看代碼最后 mMap = map)
3. 標(biāo)記讀取完成, 這個(gè)字段后面 awaitLoadedLocked 會(huì)用到. 記錄讀取文件的時(shí)間, 后面 MODE_MULTI_PROCESS 中會(huì)用到
4. 發(fā)一個(gè) notifyAll 通知已經(jīng)讀取完畢, 激活所有等待加載的其他線程
這里寫(xiě)圖片描述
getX原理分析
public float getFloat(String key, float defValue) {
synchronized (this) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
關(guān)鍵信息如下:
1. synchronized保證了線程安全
2. get操作一定是從mMap中讀取,既從內(nèi)存中讀取,無(wú)過(guò)多性能損耗。
3. awaitLoadedLocked()保證了讀取操作一定在loadFromDisk()執(zhí)行之完,同步等待。因此第一次調(diào)用get操作可能會(huì)阻塞,萬(wàn)分注意,這也是sp被定義為輕量級(jí)存儲(chǔ)系統(tǒng)的重要原因
putX原理分析
put操作較為復(fù)雜,一步一步分析
創(chuàng)建editor
// SharedPreferencesImpl.java
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 (this) {
awaitLoadedLocked();
}
EditorImpl()無(wú)構(gòu)造函數(shù),僅僅去初始化兩個(gè)成員變量
// SharedPreferencesImpl.java
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;
... 略去方法定義 ...
public Editor putString(String key, @Nullable String value) { ... }
public boolean commit() { ... }
關(guān)鍵信息如下:
1. ·mModified 是我們每次 putXxx 后所改變的配置項(xiàng)
2. mClear 標(biāo)識(shí)要清空配置項(xiàng)
putString
public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
很簡(jiǎn)單, 僅僅是把我們?cè)O(shè)置的配置項(xiàng)放到了 mModified 屬性里保存. 等到 apply 或者 commit 的時(shí)候回寫(xiě)到內(nèi)存和磁盤(pán). 咱們分別來(lái)看看
apply
// SharedPreferencesImpl.java
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
... 略無(wú)關(guān) ...
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
apply核心在于兩點(diǎn)
1. commitToMemory()完成了內(nèi)存的同步回寫(xiě)
2. enqueueDiskWrite() 完成了硬盤(pán)的異步回寫(xiě), 我們接下來(lái)具體看看
// SharedPreferencesImpl.java
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {
... 略去無(wú)關(guān) ...
mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
synchronized (this) {
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.
if (v == this || v == null) {
mMap.remove(k);
} else {
mMap.put(k, v);
}
}
mModified.clear();
}
}
return mcr;
兩個(gè)關(guān)鍵信息
1. 把 Editor.mModified 中的配置項(xiàng)回寫(xiě)到 SharedPreferences.mMap 中, 完成了內(nèi)存的同步
2. 把 SharedPreferences.mMap 保存在了 mcr.mapToWriteToDisk 中. 而后者就是即將要回寫(xiě)到磁盤(pán)的數(shù)據(jù)源
// SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr);
}
...
}
};
...
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
關(guān)鍵信息
使用singleThreadExecutor單一線程池去依次執(zhí)行寫(xiě)入磁盤(pán)的runnable序列
之后是真正執(zhí)行把數(shù)據(jù)寫(xiě)入磁盤(pán)的方法
// SharedPreferencesImpl.java
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 (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
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);
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
return;
}
// Clean up an unsuccessfully written file
mFile.delete();
主要分為三個(gè)過(guò)程:
1. 先把已存在的老的配置文件重命名(加 ‘.bak’ 后綴), 然后刪除老的配置文件. 這相當(dāng)于做了災(zāi)備
2. 向 mFile 中一次性寫(xiě)入所有配置項(xiàng). 即 mcr.mapToWriteToDisk(這就是 commitToMemory 所說(shuō)的保存了所有配置項(xiàng)的字段) 一次性寫(xiě)入到磁盤(pán). 如果寫(xiě)入成功則刪除災(zāi)備文件, 同時(shí)記錄了這次同步的時(shí)間
3. 如果上述過(guò)程 [2] 失敗, 則刪除這個(gè)半成品的配置文件
apply總結(jié)
由于apply比較復(fù)雜,稍作總結(jié):
1. 通過(guò) commitToMemory 將修改的配置項(xiàng)同步回寫(xiě)到內(nèi)存 SharedPreferences.mMap 中. 此時(shí), 任何的 getXxx 都可以獲取到最新數(shù)據(jù)了
2. 通過(guò) enqueueDiskWrite 調(diào)用 writeToFile 將所有配置項(xiàng)一次性異步回寫(xiě)到磁盤(pán). 這是一個(gè)單線程的線程池
這里寫(xiě)圖片描述
commit
commit比較簡(jiǎn)單,直接看代碼和時(shí)序圖即可,大致和apply相同
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
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;
這里寫(xiě)圖片描述
注意:commit最后會(huì)等待異步任務(wù)返回,說(shuō)明會(huì)阻塞當(dāng)前調(diào)用線程,因此說(shuō)commit是同步寫(xiě)入,apply是異步寫(xiě)入。
以上涵蓋了大部分SharedPreferences重要源碼分析,下面總結(jié)SharedPreferences最佳實(shí)踐,提出日后要注意的問(wèn)題,只說(shuō)結(jié)論不解釋原因。(如果你不明白為什么,證明你前面的分析沒(méi)有深刻理解)
SharedPreferences最佳實(shí)踐
勿存儲(chǔ)過(guò)大value
永遠(yuǎn)記住,SharedPreferences是一個(gè)輕量級(jí)的存儲(chǔ)系統(tǒng),不要存過(guò)多且復(fù)雜的數(shù)據(jù),這會(huì)帶來(lái)以下的問(wèn)題
第一次從sp中獲取值的時(shí)候,有可能阻塞主線程,使界面卡頓、掉幀。
這些key和value會(huì)永遠(yuǎn)存在于內(nèi)存之中,占用大量?jī)?nèi)存。
勿存儲(chǔ)復(fù)雜數(shù)據(jù)
SharedPreferences通過(guò)xml存儲(chǔ)解析,JSON或者HTML格式存放在sp里面的時(shí)候,需要轉(zhuǎn)義,這樣會(huì)帶來(lái)很多&這種特殊符號(hào),sp在解析碰到這個(gè)特殊符號(hào)的時(shí)候會(huì)進(jìn)行特殊的處理,引發(fā)額外的字符串拼接以及函數(shù)調(diào)用開(kāi)銷(xiāo)。如果數(shù)據(jù)量大且復(fù)雜,嚴(yán)重時(shí)可能導(dǎo)頻繁GC。
不要亂edit和apply,盡量批量修改一次提交
edit會(huì)創(chuàng)建editor對(duì)象,每進(jìn)行一次apply就會(huì)創(chuàng)建線程,進(jìn)行內(nèi)存和磁盤(pán)的同步,千萬(wàn)寫(xiě)類(lèi)似下面的代碼
SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE);
sp.edit().putString("test1", "sss").apply();
sp.edit().putString("test2", "sss").apply();
sp.edit().putString("test3", "sss").apply();
sp.edit().putString("test4", "sss").apply();
建議apply,少用commit
commit同步寫(xiě)內(nèi)存,同步寫(xiě)磁盤(pán)。有是否成功的返回值
apply同步寫(xiě)內(nèi)存,異步寫(xiě)磁盤(pán)。無(wú)返回值
registerOnSharedPreferenceChangeListener弱引用問(wèn)題
見(jiàn)本文初
apply和commit對(duì)registerOnSharedPreferenceChangeListener的影響
對(duì)于 apply, listener 回調(diào)時(shí)內(nèi)存已經(jīng)完成同步, 但是異步磁盤(pán)任務(wù)不保證是否完成
對(duì)于 commit, listener 回調(diào)時(shí)內(nèi)存和磁盤(pán)都已經(jīng)同步完畢
不要有任何用SP進(jìn)行多進(jìn)程存儲(chǔ)的幻想
這個(gè)話題不需要過(guò)多討論,只記住一點(diǎn),多進(jìn)程別用SP,Android沒(méi)有對(duì)SP在多進(jìn)程上的表現(xiàn)做任何約束和保證。附上Google官方注釋
@deprecated MODE_MULTI_PROCESS does not work reliably in
some versions of Android, and furthermore does not provide any mechanism for reconciling
轉(zhuǎn)載于:https://www.cnblogs.com/qwangxiao/p/8667831.html
總結(jié)
以上是生活随笔為你收集整理的史上最全面,清晰的SharedPreferences解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python----面向对象---自定义
- 下一篇: 孕妇梦到自己挖红薯是什么意思