了解Android API中的SharedPreferences
Preference翻譯為偏好,但實(shí)際上它可以算是Setting(設(shè)置)的別名。兩種叫法給人的差異在于針對(duì)的對(duì)象不同:設(shè)置更傾向于設(shè)備的屬性,修改設(shè)置便是改變?cè)O(shè)備的功能;偏好則傾向于用戶(hù)的性格,用戶(hù)基于其個(gè)人的偏好來(lái)來(lái)形成設(shè)備的差異化。但是總體而言,它們是一致的,都是通過(guò)用戶(hù)的需求改變?cè)O(shè)備的體驗(yàn)。
在Android的開(kāi)發(fā)過(guò)程中,會(huì)在使用的API中見(jiàn)到很多名字中帶有Preference的類(lèi)和接口,此篇文章就來(lái)介紹一下這些“*Prefere*”的功能和用途。
在Android提供API中,帶有Preference的其實(shí)主要分為兩類(lèi):一類(lèi)是android.content包下的SharedPreferences,另一類(lèi)則是android.preference包下的Preference。它們分別實(shí)現(xiàn)不同功能,卻又相互聯(lián)系合作完成對(duì)Android程序的控制。
SharedPreferences簡(jiǎn)介
SharedPreferences是以復(fù)數(shù)形式存在,因?yàn)樵贏ndroid中它是用來(lái)存儲(chǔ)鍵值對(duì)(Key-Value Pair)數(shù)據(jù)的集合。API中包含了多個(gè)方法來(lái)方面讀取相應(yīng)類(lèi)型的數(shù)據(jù):
- String getString(String key, String defValue);
- Set<String> getStringSet(String key, Set<String> defValues);
- int getInt(String key, int defValue);
- long getLong(String key, long defValue);
- float getFloat(String key, float defValue);
- boolean getBoolean(String key, boolean defValue);
這也表明SharedPreferences所能存儲(chǔ)的類(lèi)型被限定在了String、int、long、float、boolean這些基礎(chǔ)數(shù)據(jù)類(lèi)中,唯一的集合類(lèi)型也只是Set,而它看起來(lái)更像是用來(lái)作為不能有重復(fù)數(shù)據(jù)的數(shù)組。
還可以單純檢查是否包換指定的主鍵,或者干脆將所有的鍵值對(duì)的Map獲取出來(lái):
- boolean contains(String key);
- Map<String, ?> getAll();
Android系統(tǒng)的工程師在設(shè)計(jì)SharedPreferences的時(shí)候,把讀取的功能放在了SharedPreferences上,而把寫(xiě)回的功能實(shí)現(xiàn)在了其內(nèi)嵌的Editor類(lèi)上,通過(guò)調(diào)用edit()方法來(lái)獲得一個(gè)寫(xiě)入器。這樣就很容易實(shí)現(xiàn)一個(gè)只讀的對(duì)象,只要返回一個(gè)空指針或非可用的Editor對(duì)象就可以了。
- Editor putString(String key, String value);
- Editor putStringSet(String key, Set<String> values);
- Editor putInt(String key, int value);
- Editor putLong(String key, long value);
- Editor putFloat(String key, float value);
- Editor putBoolean(String key, boolean value);
- Editor remove(String key);
SharedPreferences還有一個(gè)內(nèi)嵌接口OnSharedPreferenceChangeListener,實(shí)現(xiàn)它唯一的方法onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)并通過(guò)以下方法添加在SharedPreferences對(duì)象上就可以監(jiān)聽(tīng)其上鍵值對(duì)的增加、刪除和修改:
- void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
- void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener);
SharedPreferences的在Android系統(tǒng)中的實(shí)現(xiàn)
SharedPreferences和內(nèi)嵌的Editor其實(shí)都只是接口定義而已,并沒(méi)有實(shí)現(xiàn)任何方法。它只是用來(lái)制定了一個(gè)存儲(chǔ)鍵值對(duì)的協(xié)議,具體的實(shí)現(xiàn)方式和存儲(chǔ)形式可以是任意的。在Android系統(tǒng)中,它默認(rèn)以XML格式的文件來(lái)存儲(chǔ)這些數(shù)據(jù),實(shí)現(xiàn)的類(lèi)則是SharedPreferencesImpl。
下邊就是所保存的XML文件的基本格式,它以數(shù)據(jù)類(lèi)型作為XML元素的標(biāo)簽,主鍵(key)是標(biāo)簽name屬性的值,而主鍵對(duì)應(yīng)的值則作為value屬性的值。但如果是String類(lèi)型則作為標(biāo)簽下的content,這樣就不用轉(zhuǎn)義引號(hào)也能更好的處理?yè)Q行。另外對(duì)于null值存儲(chǔ)的結(jié)構(gòu)也比較特殊,它以null為標(biāo)簽,只有一個(gè)name屬性,沒(méi)有其他內(nèi)容。
- <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
- <map>
- <string name="Name">Ider</string>
- <boolean name="Android" value="true" />
- <set name="Subsites">
- <string>code.iderzheng.com</string>
- <string>blog.iderzheng.com</string>
- <string>manual.iderzheng.com</string>
- </set>
- <int name="VersionCode" value="21" />
- <long name="VersionNumber" value="1355" />
- <float name="Version" value="5.0" />
- <null name="Null" />
- </map>
Android系統(tǒng)會(huì)把該XML文件存儲(chǔ)在/data/data/(packagename)/shared_prefs/下,每一個(gè)XML文件就對(duì)應(yīng)一個(gè)SharedPreferences對(duì)象(實(shí)際是SharedPreferencesImpl對(duì)象)。但是SharedPreferences是接口不能用來(lái)實(shí)例化對(duì)象,而SharedPreferencesImpl是系統(tǒng)隱藏類(lèi),不能被直接訪問(wèn)使用,其構(gòu)造函數(shù)也只是包可見(jiàn)。所以不能通過(guò)new來(lái)構(gòu)建一個(gè)SharedPreferences,必須通過(guò)Context提供的getSharedPreferences(String, int)來(lái)獲得實(shí)例。
該方法的***個(gè)參數(shù)是指定XML文件名(不包含“.xml”后綴)的字符串,方法會(huì)去讀取出對(duì)應(yīng)的文件,創(chuàng)建一個(gè)SharedPreferences對(duì)象。第二個(gè)參數(shù)指定文件的訪問(wèn)權(quán)限,共有4中可選模式,從API 17開(kāi)始基于安全的考慮,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已經(jīng)被廢棄使用,只有MODE_PRIVATE和MODE_MULTI_PROCESS可使用,一般情況下指定MODE_PRIVATE即可。
對(duì)于從SharedPreferences中讀取指定主鍵的值是十分快的,因?yàn)樗写嬖赬ML的鍵值對(duì)信息全都被讀取被存儲(chǔ)在了SharedPreferences對(duì)象中的Map成員變量里,所以讀取都是基于內(nèi)存訪問(wèn)。使用Editor寫(xiě)回到文件是避不開(kāi)IO操作的,所以使用commit()提交修改還是會(huì)花費(fèi)一些時(shí)間。考慮到這點(diǎn),Android在API 9里引進(jìn)了apply()方法來(lái)異步地將修改后的內(nèi)容寫(xiě)回到文件,當(dāng)然在寫(xiě)回前也會(huì)先更新內(nèi)存中的鍵值對(duì)信息保證讀取到的時(shí)***的內(nèi)容。
既然寫(xiě)回可以是異步的,那么多次調(diào)用getSharedPreferences(String, int)獲得多個(gè)SharedPreferences賦值給不同的變量,假如一個(gè)變量做了修改,其他的對(duì)象不是會(huì)出現(xiàn)內(nèi)容不一致的情況。其實(shí)這種情況并不會(huì)出現(xiàn),因?yàn)樗袆?chuàng)建出來(lái)的SharedPreferences都被存儲(chǔ)在ContextImp的一個(gè)靜態(tài)成員變量中:
- /**
- * Map from package name, to preference name, to cached preferences.
- */
- private static ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>> sSharedPrefs;
這是一個(gè)從程序的Package名字到XML文件名再到SharedPreferences對(duì)象的二級(jí)Map。所以每次調(diào)用getSharedPreferences(String, int)得到的對(duì)象其實(shí)都是同一個(gè)實(shí)例,修改操作也都就作用在同一段內(nèi)存中保證了所有訪問(wèn)的一致性。apply()方法也會(huì)自動(dòng)將所有修改排入隊(duì)列一一寫(xiě)回文件從而不會(huì)因?yàn)轫樞虻腻e(cuò)誤而造成意料之外的錯(cuò)誤覆蓋。所以因?yàn)檫@個(gè)緩存機(jī)制的存在,多次調(diào)用getSharedPreferences(String, int)是非常效率的。而寫(xiě)回時(shí)則推薦使用apply()實(shí)現(xiàn)異步操作,而不要用commit()阻礙主線程。
SharedPreferences的使用和示例
一般而言SharedPreferences的名字和主鍵名都是固定的,所以可以定義一些final的字符串變量來(lái)保存這些名字,在讀取和寫(xiě)回時(shí)都使用這些常熟變量。對(duì)于之前展示的XML對(duì)應(yīng)的代碼就如下邊所示:
- private static final String IDER_PREFERENCE = "ider-preference";
- private static final String IDER_PREFERENCE_KEY_NAME = "Name";
- private static final String IDER_PREFERENCE_KEY_SUBSITES = "Subsites";
- private static final String IDER_PREFERENCE_KEY_IS_ANDROID = "Android";
- private static final String IDER_PREFERENCE_KEY_VERSION = "Version";
- private static final String IDER_PREFERENCE_KEY_VERSION_CODE = "VersionCode";
- private static final String IDER_PREFERENCE_KEY_VERSION_NUMBER = "VersionNumber";
- private static final String IDER_PREFERENCE_KEY_NULL = "Null";
- public void write(Context context) {
- final SharedPreferences spref = context.getSharedPreferences(IDER_PREFERENCE, MODE_PRIVATE);
- HashSet<String> strs = new HashSet<String>();
- strs.add("blog.iderzheng.com");
- strs.add("code.iderzheng.com");
- strs.add("manual.iderzheng.com");
- SharedPreferences.Editor editor = spref.edit();
- editor.putString(IDER_PREFERENCE_KEY_NAME, "Ider");
- editor.putStringSet(IDER_PREFERENCE_KEY_SUBSITES, strs);
- editor.putBoolean(IDER_PREFERENCE_KEY_IS_ANDROID, true);
- editor.putFloat(IDER_PREFERENCE_KEY_VERSION, 5.0f);
- editor.putInt(IDER_PREFERENCE_KEY_VERSION_CODE, 21);
- editor.putLong(IDER_PREFERENCE_KEY_VERSION_NUMBER, 1355);
- editor.putString(IDER_PREFERENCE_KEY_NULL, null);
- editor.apply();
- }
- public void read(Context context) {
- final SharedPreferences spref = context.getSharedPreferences(IDER_PREFERENCE, MODE_PRIVATE);
- String name = spref.getString(IDER_PREFERENCE_KEY_NAME, "");
- Set<String> strs = spref.getStringSet(IDER_PREFERENCE_KEY_SUBSITES, null);
- boolean isAndroid = spref.getBoolean(IDER_PREFERENCE_KEY_IS_ANDROID, false);
- float version = spref.getFloat(IDER_PREFERENCE_KEY_VERSION, 0);
- int versionCode = spref.getInt(IDER_PREFERENCE_KEY_VERSION_CODE, 0);
- long versionNumber = spref.getLong(IDER_PREFERENCE_KEY_VERSION_NUMBER, 0);
- boolean hasKey = spref.contains(IDER_PREFERENCE_KEY_NULL);
- }
既然SharedPreferences的名字是可以任意給定的,所以對(duì)于SharedPreferences的使用就可以有非常的針對(duì)性創(chuàng)建不同的文件來(lái)存儲(chǔ)不同的內(nèi)容。比如可以以不同用戶(hù)為名存放他們的偏好信息,可以根據(jù)不同界面保存布局信息、已訪問(wèn)的頁(yè)碼。Activity就額外實(shí)現(xiàn)了獲取SharedPreferences的方法getPreferences(int),它只需要開(kāi)發(fā)者提供文件的打開(kāi)模式,自動(dòng)以Activity的類(lèi)名作為文件名。
SharedPreferences取值時(shí)是直接將給定主鍵在Map中的值強(qiáng)制轉(zhuǎn)換成所需要的值,所以如果用putInt存儲(chǔ)了整型卻用getBoolean()來(lái)取,并不會(huì)自動(dòng)轉(zhuǎn)換成布爾型,而是直接拋出異常,所以要使用要注意保持類(lèi)型一致。
另外如果沒(méi)有存儲(chǔ)過(guò)某個(gè)主鍵,SharedPreferences會(huì)返回null值,而對(duì)于String、Set這些類(lèi)型同樣可以存儲(chǔ)null值,這樣就不能確定null是不是真是存儲(chǔ)的數(shù)據(jù)了。因此SharedPreferences還提供了contains (String key)方法來(lái)檢查給定的主鍵是真的存了null,還是因?yàn)椴](méi)有這個(gè)鍵值對(duì)才返回的null。
SharedPreferences的優(yōu)缺點(diǎn)
之前講過(guò)所以讀取過(guò)的SharedPreferences的文件都會(huì)被緩存在Map里放在內(nèi)存中,以便下次直接快速訪問(wèn),因?yàn)榭焓耂haredPreferences的一大優(yōu)點(diǎn)。但是也因?yàn)槎急尘彺?,而且存放格式是XML,所以SharedPreferences不宜存放過(guò)多的鍵值對(duì),值的內(nèi)容也不能太大。再者SharedPreferences只支持最基本的幾種類(lèi)型,存儲(chǔ)一些用戶(hù)基本信息也足夠了。
如果對(duì)設(shè)備有root權(quán)限,那么就可以直接訪問(wèn)/data/data/(packagename)/shared_prefs/目錄,將XML文件轉(zhuǎn)出來(lái)查看。或者直接用在adb shell下直接用cat指令觀察數(shù)據(jù)的改變,非常的方便。
綜合而言,存儲(chǔ)一些內(nèi)容較小、類(lèi)型簡(jiǎn)單的數(shù)據(jù),SharedPreferences絕對(duì)是***對(duì)象。