SharedPreferences的使用和原理分析
阿新 • • 發佈:2019-01-08
簡介
SharedPreferences一般用來儲存一些簡單的資料型別,比如int,String,Boolean
SharedPreferences的內部使用ArrayMap鍵值對的形式來臨時儲存資料,最終ArrayMap的資料會通過IO流寫入到XML檔案中
這個XML檔案在手機中的位置是: /data/data/shared_prefs/
1,使用
//獲取SharedPreferences 檔名稱,模式 SharedPreferences sf = getSharedPreferences("demo",Context.MODE_PRIVATE); SharedPreferences.Editor mEditor = sf.edit(); //儲存 mEditor.putString("name","leidong"); mEditor.putBoolean("man",true); mEditor.putInt("age",35); //同步儲存到xml檔案, //mEditor.apply();另開執行緒將資料儲存在xml mEditor.commit(); //```````````````````````````` //獲取資料 String name = sf.getString("name",""); boolean man = sf.getBoolean("man",true); int age = sf.getInt("age",0);
Mode:
//私有模式。XML檔案每次都覆蓋寫入
public static final int MODE_PRIVATE = 0x0000;
//檔案開放讀寫許可權。不安全,官方已經遺棄這個用法
public static final int MODE_WORLD_READABLE = 0x0001;
public static final int MODE_WORLD_WRITEABLE = 0x0002;
//新增模式。檢查檔案是否存在,存在就往檔案追加內容,不存在就建立新的檔案。
public static final int MODE_APPEND = 0x8000
//跨程序模式,官方已經遺棄這個用法
public static final int MODE_MULTI_PROCESS = 0x0004;
2,SharedPreferences 的獲取
//Activity中 name預設為類名 public SharedPreferences getPreferences(@Context.PreferencesMode int mode) { return getSharedPreferences(getLocalClassName(), mode); } //ContextWrapper中 public SharedPreferences getSharedPreferences(String name, int mode) { return mBase.getSharedPreferences(name, mode); }
Activity,Context,ContextWrapper,ContextImpl之間的關係
ContextImpl.java
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
//存放xml檔案 File
private ArrayMap<String, File> mSharedPrefsPaths;
public SharedPreferences getSharedPreferences(String name, int mode) {
......
//xml檔案
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
//如果檔案不存在,建立檔案bignqie儲存在mSharedPrefsPaths中
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
return new File(base, name);
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}
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) {
......
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
......
return sp;
}
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
SharedPreferencesImpl.java
//標記作用,xml是否已經被載入到記憶體中
private boolean mLoaded = false;
//建構函式,初始化,建立備份檔案mBackupFile ,讀取磁碟中的xml檔案
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);//備份檔案
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
//起一個新執行緒,載入磁碟的xml檔案內容
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
//
private void loadFromDisk() {
synchronized (mLock) {
//如果xml已經被載入過,直接返回
if (mLoaded) {
return;
}
//如果備份存在,說明之前讀寫操作被中斷,把mBackupFile當成mFile來用,直接使用備份檔案中的內容。
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);//讀取xml中的內容到記憶體中,賦值給map
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
mLoaded = true;//標記為已經載入過xml檔案
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 = map;
mStatTimestamp = stat.st_mtim;
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();//喚醒其他正在等待mLock鎖的程序
}
}
}
//獲取SharedPreferences中儲存的鍵值對資料
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();//如果mLoaded為false,執行緒會一直處於等待的狀態
Integer v = (Integer)mMap.get(key);//獲取讀取到記憶體中的值(mMap鍵值對)
return v != null ? v : defValue;
}
}
SharedPreferences.Editor對資料的儲存操作
private final Map<String, Object> mModified = new HashMap<>();
public Editor edit() {
//如果該執行緒獲取到了mLock物件鎖,但是mLoaded為false,也就是載入xml過程沒結束,那麼執行緒會一直等待
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
//如果mLoaded為false,執行緒會一直處於等待的狀態
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();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
//儲存的鍵值對資料只是儲存到了內部類Editor的mModified集合中
public Editor putInt(String key, int value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//把內部類Editor的mModified集合中的資料儲存到SharedPreferencesImpl類的mMap集合中。
MemoryCommitResult mcr = commitToMemory();
//把mMap集合中的資料寫入到xml檔案中
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);//通知註冊監聽者
return mcr.writeToDiskResult;
}
public void apply() {
final long startTime = System.currentTimeMillis();
//把內部類Editor的mModified集合中的資料儲存到SharedPreferencesImpl類的mMap集合中。
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
//把mMap集合中的資料寫入到xml檔案中
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);//通知註冊監聽者
}
//把mMap集合中的資料寫入到xml檔案中。
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
//如果是apply()形式,則postWriteRunnable != null,isFromSyncCommit 為true。
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);//寫入磁碟xml的操作被封裝在新起的執行緒中
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
//如果是commit()的形式,且當前沒有寫磁碟任務(mDiskWritesInFlight == 1),則直接呼叫
//writeToDiskRunnable.run()執行writeToFile()寫入操作,不起新執行緒。
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//如果是apply()的形式,所有的執行緒都加入到QueuedWork中,以佇列的形式儲存,逐個執行緒啟動
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}