Android-带你从源码角度理解SharedPreferences存储原理
SP的特點(diǎn)以及基本使用方式
SharedPreferences因非常適合存儲(chǔ)較小鍵值集合數(shù)據(jù)且使用非常簡(jiǎn)單的特點(diǎn),而受到廣大程序員們熱愛(ài)。
SP使用非常簡(jiǎn)單:
SP源碼分析
SP是如何讀取數(shù)據(jù)的
其實(shí)Context實(shí)現(xiàn)就是ContextImpl中,要想搞清楚SP是如何讀取數(shù)據(jù)的,第一步當(dāng)然是要了解ContextImpl.getSharedPreferences方法是如何實(shí)現(xiàn)的
/*** Map from package name, to preference name, to cached preferences.*///緩存所有應(yīng)用的SP容器,該容器key對(duì)應(yīng)應(yīng)用名稱,value則為每個(gè)應(yīng)用存儲(chǔ)所有sp的容器(ArrayMap)private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;@Overridepublic SharedPreferences getSharedPreferences(String name, int mode) {SharedPreferencesImpl sp;synchronized (ContextImpl.class) {if (sSharedPrefs == null) {//如果靜態(tài)對(duì)象不存在,直接創(chuàng)建一個(gè)Map,以便后期用于保存spsSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();}//獲取當(dāng)前應(yīng)用包名final String packageName = getPackageName();//從保存sp的容器中通過(guò)包名查找當(dāng)前應(yīng)用所有sp;每個(gè)app的所有sp都是保存在ArrayMap中,ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);if (packagePrefs == null) {//如果從sp容器沒(méi)找到保存當(dāng)前應(yīng)用sp的ArrayMap直接創(chuàng)建一個(gè)packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();//將創(chuàng)建的對(duì)象保存到sp容器中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) {//如果targetSdk版本好小于19,且傳入的文件名為null的話,默認(rèn)將文件名命名為"null"if (name == null) {name = "null";}}//從當(dāng)前應(yīng)用的sp容器中通過(guò)文件名去查找spsp = packagePrefs.get(name);if (sp == null) {//如果沒(méi)找到,直接創(chuàng)建一個(gè)文件名以name命名的xml文件File prefsFile = getSharedPrefsFile(name);//此處極為關(guān)鍵,該構(gòu)造器是讀取文件操作sp = new SharedPreferencesImpl(prefsFile, mode);//將創(chuàng)建sp對(duì)象保存到當(dāng)前應(yīng)用sp容器中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.//如果讀取模式是跨線程或targetSdk版本小于11,再次重新load下文件而已sp.startReloadIfChangedUnexpectedly();}return sp;}從上面源碼可以看出,getSharedPreferences方法是先根據(jù)當(dāng)前應(yīng)用名稱來(lái)獲取一個(gè)ArrayMap(存儲(chǔ)sp的容器)如果沒(méi)有直接創(chuàng)建并保存到內(nèi)存中,然后再根據(jù)文件名來(lái)獲取SharedPreferencesImpl的對(duì)象(沒(méi)找到則直接創(chuàng)建SharedPreferencesImpl),這短短的三十幾行代碼還是比較簡(jiǎn)單的。
因?yàn)槭庆o態(tài)變量存儲(chǔ)鍵值數(shù)據(jù)的所以我們用SP存儲(chǔ)的數(shù)據(jù)在內(nèi)存中是一直存在;所以我們盡量用一個(gè)文件來(lái)存在數(shù)據(jù),以達(dá)到減少內(nèi)存對(duì)象
大家可以看到getSharePreferences返回的對(duì)象類型其實(shí)是SharedPreferencesImpl類型,只不過(guò)該類實(shí)現(xiàn)了SharedPreferences接口而已,接下來(lái)我們先看看SharedPreferencesImpl構(gòu)造器做了啥東東。
SharedPreferencesImpl.java
final class SharedPreferencesImpl implements SharedPreferences {private final File mFile;private final File mBackupFile;private final int mMode;private Map<String, Object> mMap; // guarded by 'this'private int mDiskWritesInFlight = 0; // guarded by 'this'//文件是否加載成功private boolean mLoaded = false; // guarded by 'this'//文件的時(shí)間以及大小private long mStatTimestamp; // guarded by 'this'private long mStatSize; // guarded by 'this'private final Object mWritingToDiskLock = new Object();private static final Object mContent = new Object();private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =new WeakHashMap<OnSharedPreferenceChangeListener, Object>();SharedPreferencesImpl(File file, int mode) {//給類成員變量賦值mFile = file;mBackupFile = makeBackupFile(file);mMode = mode;mLoaded = false;mMap = null;//開(kāi)啟一個(gè)線程讀取文件startLoadFromDisk();}private static File makeBackupFile(File prefsFile) {return new File(prefsFile.getPath() + ".bak");}private void startLoadFromDisk() {synchronized (this) {//使用同步代碼代碼塊,對(duì)mloader進(jìn)行賦值mLoaded = false;}//開(kāi)啟線程讀取文件new Thread("SharedPreferencesImpl-load") {public void run() {synchronized (SharedPreferencesImpl.this) {loadFromDiskLocked();}}}.start();}private void loadFromDiskLocked() {//如果文件已經(jīng)加載完畢直接返回if (mLoaded) {return;}if (mBackupFile.exists()) {mFile.delete();mBackupFile.renameTo(mFile);}// Debuggingif (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 {str = new BufferedInputStream(new FileInputStream(mFile), 16*1024);//使用XmlUtils工具類讀取xml文件數(shù)據(jù)map = XmlUtils.readMapXml(str);} catch (XmlPullParserException e) {Log.w(TAG, "getSharedPreferences", e);} catch (FileNotFoundException e) {Log.w(TAG, "getSharedPreferences", e);} catch (IOException e) {Log.w(TAG, "getSharedPreferences", e);} finally {IoUtils.closeQuietly(str);}}} catch (ErrnoException e) {}//修改文件加載完成標(biāo)志mLoaded = true;if (map != null) {mMap = map;//如果有數(shù)據(jù),將數(shù)據(jù)已經(jīng)賦值給類成員變量mMap,mStatTimestamp = stat.st_mtime;//記錄文件上次修改時(shí)間mStatSize = stat.st_size;//記錄文件大小(字節(jié)單位)} else {//沒(méi)有數(shù)據(jù)直接創(chuàng)建一個(gè)hashmap對(duì)象mMap = new HashMap<String, Object>();}//此處非常關(guān)鍵是為了通知其他線程文件已讀取完畢,你們可以執(zhí)行讀/寫(xiě)操作了notifyAll();}}通過(guò)上述分析我們不難發(fā)現(xiàn)調(diào)用getSharedPreferences方法就已經(jīng)開(kāi)啟一個(gè)線程去讀取文件了
我們?cè)賮?lái)看看SharedPreferencesImpl.getString內(nèi)部是如何執(zhí)行的
至此關(guān)于SP的讀操作以全部分析完畢,相信大家對(duì)SP讀操作有了更深入的認(rèn)識(shí)了;下面我們繼續(xù)再看看SP的寫(xiě)操作是怎么玩的
SP是如何寫(xiě)入數(shù)據(jù)的
通常寫(xiě)數(shù)據(jù)大體是這樣的
//寫(xiě)操作 Context context = getActivity(); SharedPreferences sp = getSharedPreferences("fileName", Context.MODE_PRIVATE); sp.edit().putString("key", "value").commit();getSharedPreferences大家都很清楚了,我們現(xiàn)在edit方法到底做了什么
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();}return new EditorImpl(); }可以看到edit方法非常簡(jiǎn)單,首先是通過(guò)同步代碼塊調(diào)用了awaitLoadedLocked方法,緊接著直接返回了一個(gè)EditorImpl實(shí)例對(duì)象,我們繼續(xù)追蹤看看EditorImpl類是put、commit方法
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) {synchronized (this) {mModified.put(key, value);return this;}}public Editor remove(String key) {synchronized (this) {//注意此處并沒(méi)有執(zhí)行刪除操作,而是將其對(duì)應(yīng)key的value設(shè)置了當(dāng)前this//commitToMemory方法中會(huì)對(duì)此做特殊處理mModified.put(key, this);return this;}}public Editor clear() {synchronized (this) {mClear = true;return this;}}public boolean commit() {//第一步 commitToMemory方法可以理解為對(duì)SP中的mMap對(duì)象同步到最新數(shù)據(jù)狀態(tài)MemoryCommitResult mcr = commitToMemory();//第二步 寫(xiě)文件;注意第二個(gè)參數(shù)為null,寫(xiě)文件操作會(huì)運(yùn)行在當(dāng)前線程SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);try {mcr.writtenToDiskLatch.await();} catch (InterruptedException e) {return false;}//第三步 通知監(jiān)聽(tīng)器數(shù)據(jù)改變notifyListeners(mcr);//第四步 返回寫(xiě)操作狀態(tài)return mcr.writeToDiskResult;}... }從上面可以看到,其實(shí)putXXX方法知識(shí)把鍵值數(shù)據(jù)先存放到內(nèi)存中去了(mModified對(duì)象中);比較有意思到是remove、clear方法,remove方法會(huì)將要?jiǎng)h除的數(shù)據(jù)的value設(shè)置為EditorImpl自己(commitToMemory方法會(huì)對(duì)此做特殊處理);
clear方法也僅僅是設(shè)置一個(gè)標(biāo)志位而已(commitToMemory方法中用到);
最關(guān)鍵的方法還是commit方法,我們可以看到其實(shí)commit方法主要分為四步,第一步將sp數(shù)據(jù)同步到最新?tīng)顟B(tài)并返回mcr對(duì)象;第二步將mcr對(duì)象中數(shù)據(jù)寫(xiě)入文件;第三步通知監(jiān)聽(tīng)器數(shù)據(jù)已發(fā)生改變;最后一步就是返回寫(xiě)操作狀態(tài)是否成功
相信大家應(yīng)該SP的讀寫(xiě)有了更深刻的認(rèn)識(shí)!
總結(jié)
以上是生活随笔為你收集整理的Android-带你从源码角度理解SharedPreferences存储原理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Android组件化初探
- 下一篇: 迁移至Android3.0遇到一些问题