日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

史上最全面,清晰的SharedPreferences解析

發布時間:2023/11/29 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 史上最全面,清晰的SharedPreferences解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

基礎用法
獲取Sp:
get
put
監聽器
原理分析
獲取SharedPreferences
構造SharedPreferences
getX原理分析
putX原理分析
創建editor
putString
apply
apply總結
commit
SharedPreferences最佳實踐
勿存儲過大value
勿存儲復雜數據
不要亂edit和apply,盡量批量修改一次提交
建議apply,少用commit
registerOnSharedPreferenceChangeListener弱引用問題
apply和commit對registerOnSharedPreferenceChangeListener的影響
不要有任何用SP進行多進程存儲的幻想
基礎用法

獲取Sp:

Activity中:getPreferences(int mode)
context.getSharedPreferences(String name, int mode)
PreferenceManager.getDefaultSharedPreferences(Context context)
1和3的獲取SP的方法最終都會調用2,只是1和3默認選取了特定的name,1中通過getLocalClassName()獲取通過包名和類名拼裝的name,3通過context.getPackageName() + "_preferences"獲取name
注意第二個參數的含義,現在均指定為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).
所存儲的數據保存在:/data/data/<package name>/shared_prefs下的指定name.xml文件中
get

sp.getX(String key, X value);
1
X為簡單的基本類型: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對象,操作完需要進行事務提交操作,可以采用commit或者apply進行。commit同步寫磁盤,返回是否成功的標識碼。apply異步寫磁盤,無返回值。(二者均是同步寫內存,先同步寫內存,之后同步/異步寫磁盤)
監聽器

sharedPreferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

}
});
1
2
3
4
5
6
如果使用匿名內部類的形式進行監聽。注意,因為OnSharedPreferenceChangeListener的引用被保存在一個WeakHashMap中,導致程序的行為不確定性。為了避免這種情況,推薦以下方式:
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;

可見 sdk 是先取了緩存(sSharedPrefs靜態變量), 如果緩存未命中, 才構造對象. 也就是說, 多次 getSharedPreferences 幾乎是沒有代價的. 同時, 實例的構造被 synchronized 關鍵字包裹, 因此構造過程是多線程安全的
構造SharedPreferences

第一次構建SharedPreferences對象
// 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);
幾個關鍵類成員信息解釋如下
1. mFile 代表我們磁盤上的配置文件
2. mBackupFile 是一個災備文件, 用戶寫入失敗時進行恢復, 后面會再說. 其路徑是 mFile 加后綴 ‘.bak’
3. mMap 用于在內存中緩存我們的配置數據, 也就是 getXxx 數據的來源

重點關注startLoadFromDisk()方法

private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run(www.thd178.com ) {
loadFromDisk(www.taohuayuan178.com );
開啟了一個從Disk讀取的線程

// SharedPreferencesImpl.java
private void loadFromDisk(www.douniu157.com) {
synchronized (SharedPreferencesImpl.this) {
if (mLoaded) {
return;
}
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}

... 略去無關代碼 ...

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()非常關鍵,他總共做了以下幾件事
1. 如果有 ‘災備’ 文件, 則直接使用災備文件回滾.
2. 把配置從磁盤讀取到內存的并保存在 mMap 字段中(看代碼最后 mMap = map)
3. 標記讀取完成, 這個字段后面 awaitLoadedLocked 會用到. 記錄讀取文件的時間, 后面 MODE_MULTI_PROCESS 中會用到
4. 發一個 notifyAll 通知已經讀取完畢, 激活所有等待加載的其他線程

這里寫圖片描述

getX原理分析

public float getFloat(String key, float defValue) {
synchronized (this) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
關鍵信息如下:
1. synchronized保證了線程安全
2. get操作一定是從mMap中讀取,既從內存中讀取,無過多性能損耗。
3. awaitLoadedLocked()保證了讀取操作一定在loadFromDisk()執行之完,同步等待。因此第一次調用get操作可能會阻塞,萬分注意,這也是sp被定義為輕量級存儲系統的重要原因

putX原理分析

put操作較為復雜,一步一步分析

創建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()無構造函數,僅僅去初始化兩個成員變量

// 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() { ... }

關鍵信息如下:
1. ·mModified 是我們每次 putXxx 后所改變的配置項
2. mClear 標識要清空配置項

putString

public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
很簡單, 僅僅是把我們設置的配置項放到了 mModified 屬性里保存. 等到 apply 或者 commit 的時候回寫到內存和磁盤. 咱們分別來看看

apply

// SharedPreferencesImpl.java
public void apply() {
final MemoryCommitResult mcr = commitToMemory();

... 略無關 ...

SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
apply核心在于兩點
1. commitToMemory()完成了內存的同步回寫
2. enqueueDiskWrite() 完成了硬盤的異步回寫, 我們接下來具體看看

// SharedPreferencesImpl.java
private MemoryCommitResult commitToMemory() {
MemoryCommitResult mcr = new MemoryCommitResult();
synchronized (SharedPreferencesImpl.this) {

... 略去無關 ...

mcr.mapToWriteToDisk = mMap;
mDiskWritesInFlight++;

synchronized (this) {
for (Map.Entry&lt;String, Object&gt; e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// &quot;this&quot; is the magic value for a removal mutation. In addition,
// setting a value to &quot;null&quot; 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;
兩個關鍵信息
1. 把 Editor.mModified 中的配置項回寫到 SharedPreferences.mMap 中, 完成了內存的同步
2. 把 SharedPreferences.mMap 保存在了 mcr.mapToWriteToDisk 中. 而后者就是即將要回寫到磁盤的數據源

// 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);
關鍵信息
使用singleThreadExecutor單一線程池去依次執行寫入磁盤的runnable序列

之后是真正執行把數據寫入磁盤的方法
// 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();
主要分為三個過程:
1. 先把已存在的老的配置文件重命名(加 ‘.bak’ 后綴), 然后刪除老的配置文件. 這相當于做了災備
2. 向 mFile 中一次性寫入所有配置項. 即 mcr.mapToWriteToDisk(這就是 commitToMemory 所說的保存了所有配置項的字段) 一次性寫入到磁盤. 如果寫入成功則刪除災備文件, 同時記錄了這次同步的時間
3. 如果上述過程 [2] 失敗, 則刪除這個半成品的配置文件

apply總結

由于apply比較復雜,稍作總結:
1. 通過 commitToMemory 將修改的配置項同步回寫到內存 SharedPreferences.mMap 中. 此時, 任何的 getXxx 都可以獲取到最新數據了
2. 通過 enqueueDiskWrite 調用 writeToFile 將所有配置項一次性異步回寫到磁盤. 這是一個單線程的線程池

這里寫圖片描述

commit

commit比較簡單,直接看代碼和時序圖即可,大致和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;
這里寫圖片描述

注意:commit最后會等待異步任務返回,說明會阻塞當前調用線程,因此說commit是同步寫入,apply是異步寫入。

以上涵蓋了大部分SharedPreferences重要源碼分析,下面總結SharedPreferences最佳實踐,提出日后要注意的問題,只說結論不解釋原因。(如果你不明白為什么,證明你前面的分析沒有深刻理解)
SharedPreferences最佳實踐

勿存儲過大value

永遠記住,SharedPreferences是一個輕量級的存儲系統,不要存過多且復雜的數據,這會帶來以下的問題
第一次從sp中獲取值的時候,有可能阻塞主線程,使界面卡頓、掉幀。
這些key和value會永遠存在于內存之中,占用大量內存。
勿存儲復雜數據

SharedPreferences通過xml存儲解析,JSON或者HTML格式存放在sp里面的時候,需要轉義,這樣會帶來很多&這種特殊符號,sp在解析碰到這個特殊符號的時候會進行特殊的處理,引發額外的字符串拼接以及函數調用開銷。如果數據量大且復雜,嚴重時可能導頻繁GC。
不要亂edit和apply,盡量批量修改一次提交

edit會創建editor對象,每進行一次apply就會創建線程,進行內存和磁盤的同步,千萬寫類似下面的代碼
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同步寫內存,同步寫磁盤。有是否成功的返回值
apply同步寫內存,異步寫磁盤。無返回值
registerOnSharedPreferenceChangeListener弱引用問題

見本文初
apply和commit對registerOnSharedPreferenceChangeListener的影響

對于 apply, listener 回調時內存已經完成同步, 但是異步磁盤任務不保證是否完成
對于 commit, listener 回調時內存和磁盤都已經同步完畢
不要有任何用SP進行多進程存儲的幻想

這個話題不需要過多討論,只記住一點,多進程別用SP,Android沒有對SP在多進程上的表現做任何約束和保證。附上Google官方注釋
@deprecated MODE_MULTI_PROCESS does not work reliably in
some versions of Android, and furthermore does not provide any mechanism for reconciling

轉載于:https://www.cnblogs.com/qwangxiao/p/8667831.html

總結

以上是生活随笔為你收集整理的史上最全面,清晰的SharedPreferences解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。