[TOC]
## 使用
### 獲取SharedPreferences的兩種方式
1. 調用Context對象的getSharedPreferences()方法
2. 調用Activity對象的getPreferences()方法
兩種方式的區別://待考證
1. 調用Context對象的getSharedPreferences()方法獲得的SharedPreferences對象可以被同一應用程序下的其他組件共享.
2. 調用Activity對象的getPreferences()方法獲得的SharedPreferences對象只能在該Activity中使用.
### SharedPreferences的四種操作模式:
* Context.MODE\_PRIVATE:為默認操作模式,代表該文件是私有數據,只能被應用本身訪問,在該模式下,寫入的內容會覆蓋原文件的內容
* Context.MODE\_APPEND:模式會檢查文件是否存在,存在就往文件追加內容,否則就創建新文件.
* Context.MODE\_WORLD\_READABLE和Context.MODE\_WORLD\_WRITEABLE用來控制其他應用是否有權限讀寫該文件.
* MODE\_WORLD\_READABLE:表示當前文件可以被其他應用讀取. Android N后不支持
* MODE\_WORLD\_WRITEABLE:表示當前文件可以被其他應用寫入.Android N后不支持
## 源碼解析
### ContextImpl
~~~
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
private ArrayMap<String, File> mSharedPrefsPaths;
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
...
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
//獲取文件路徑=當前app的data目錄下的shared_prefs目錄+"/"+SharePreferences的name+“.xml”
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//讀取緩存
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
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.put(file, 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.
// 如果由其他進程修改了這個 SharedPreferences 文件,我們將會重新加載它
sp.startReloadIfChangedUnexpectedly();
}
retu
~~~
* 緩存未命中, 才構造?SharedPreferences?對象,也就是說,多次調用?getSharedPreferences?方法并不會對性能造成多大影響,因為又緩存機制?
* SharedPreferences?對象的創建過程是線程安全的,因為使用了?synchronize?關鍵字?
* 如果命中了緩存,并且參數?mode?使用了?Context.MODE\_MULTI\_PROCESS?,那么將會調用?sp.startReloadIfChangedUnexpectedly()?方法,在?startReloadIfChangedUnexpectedly?方法中,會判斷是否由其他進程修改過這個文件,如果有,會重新從磁盤中讀取文件加載數據
### SharedPreferencesImpl
#### 初始化
~~~
SharedPreferencesImpl(File file, int mode) {
//文件
mFile = file;
//回滾文件
mBackupFile = makeBackupFile(file);
//模式
mMode = mode;
//是否加載
mLoaded = false;
//key/value 緩存
mMap = null;
mThrowable = null;
//開始加載
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (mLock) {
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<String, Object> map = null;
//文件詳情 如編輯時間等
StructStat stat = null;
Throwable thrown = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16 * 1024);
//將文件讀取到
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) {
// 加載數據成功,設置 mLoaded 為 true
mLoaded = true;
mThrowable = thrown;
// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
// 將解析得到的鍵值對數據賦值給 mMap
mMap = map;
// 將文件的修改時間戳保存到 mStatTimestamp 中
mStatTimestamp = stat.st_mtim;= map;
// 將文件的大小保存到 mStatSize
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
//通知等待
mLock.notifyAll();
~~~
* 如果有備份文件,直接使用備份文件進行回滾
* 第一次調用?getSharedPreferences?方法的時候,會從磁盤中加載數據,而數據的加載時通過開啟一個子線程調用?loadFromDisk?方法進行異步讀取的?
* 將解析得到的鍵值對數據保存在?mMap?中?
* 將文件的修改時間戳以及大小分別保存在?mStatTimestamp?以及?mStatSize?中(保存這兩個值有什么用呢?我們在分析?getSharedPreferences?方法時說過,如果有其他進程修改了文件,并且?mode?為?MODE\_MULTI\_PROCESS?,將會判斷重新加載文件。如何判斷文件是否被其他進程修改過,沒錯,根據文件修改時間以及文件大小即可知道)?
* 調用?notifyAll()?方法通知喚醒其他等待線程,數據已經加載完畢
#### get
~~~
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
//等待加載完畢
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
@Override
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (mLock) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
}
private void awaitLoadedLocked() {
if (!mLoaded) {
// 嚴苛模式
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
}
~~~
getString?方法代碼很簡單,其他的例如?getInt?,?getFloat?方法也是一樣的原理,我們直接對這個疑問進行總結:?
* getXxx?方法是線程安全的,因為使用了?synchronize?關鍵字?
* getXxx?方法是直接操作內存的,直接從內存中的?mMap?中根據傳入的?key?讀取?value
* getXxx?方法有可能會卡在?awaitLoadedLocked?方法,從而導致線程阻塞等待(?**什么時候會出現這種阻塞現象呢?前面我們分析過,第一次調用?****getSharedPreferences****?方法時,會創建一個線程去異步加載數據,那么假如在調用完?****getSharedPreferences****?方法之后立即調用?****getXxx****?方法,此時的?****mLoaded****?很有可能為?****false****?,這就會導致?****awaiteLoadedLocked****?方法阻塞等待,直到?****loadFromDisk****?方法加載完數據并且調用?****notifyAll****?來喚醒所有等待線程?**)
#### SharedPreferencesImpl.edit()
~~~
public Editor edit() {
synchronized (this) {//同步方法,保證每次只有一個線程執行加載操作
awaitLoadedLocked();//等待SharedPreferencesImpl加載到內存中,見2.7
}
return new EditorImpl();//創建一個新的Editor實現。
}
~~~
#### EditorImpl
~~~
public final class EditorImpl implements Editor {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
@GuardedBy("mLock")
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
~~~
維護一個Map
#### EditorImpl.commit()
todo
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺