瞭解 Android API 中的 SharedPreferences

Ider發表於2016-04-26

Preference翻譯為偏好,但實際上它可以算是Setting(設定)的別名。兩種叫法給人的差異在於針對的物件不同:設定更傾向於裝置的屬性,修改設定便是改變裝置的功能;偏好則傾向於使用者的性格,使用者基於其個人的偏好來來形成裝置的差異化。但是總體而言,它們是一致的,都是通過使用者的需求改變裝置的體驗。

在Android的開發過程中,會在使用的API中見到很多名字中帶有Preference的類和介面,此篇文章就來介紹一下這些“*Prefere*”的功能和用途。

在Android提供API中,帶有Preference的其實主要分為兩類:一類是android.content包下的SharedPreferences,另一類則是android.preference包下的Preference。它們分別實現不同功能,卻又相互聯絡合作完成對Android程式的控制。

SharedPreferences簡介

SharedPreferences是以複數形式存在,因為在Android中它是用來儲存鍵值對(Key-Value Pair)資料的集合。API中包含了多個方法來方面讀取相應型別的資料:

這也表明SharedPreferences所能儲存的型別被限定在了Stringintlongfloatboolean這些基礎資料類中,唯一的集合型別也只是Set,而它看起來更像是用來作為不能有重複資料的陣列。

還可以單純檢查是否包換指定的主鍵,或者乾脆將所有的鍵值對的Map獲取出來:

Android系統的工程師在設計SharedPreferences的時候,把讀取的功能放在了SharedPreferences上,而把寫回的功能實現在了其內嵌的Editor類上,通過呼叫edit()方法來獲得一個寫入器。這樣就很容易實現一個只讀的物件,只要返回一個空指標或非可用的Editor物件就可以了。

SharedPreferences還有一個內嵌介面OnSharedPreferenceChangeListener,實現它唯一的方法onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key)並通過以下方法新增在SharedPreferences物件上就可以監聽其上鍵值對的增加、刪除和修改:

SharedPreferences的在Android系統中的實現

SharedPreferences和內嵌的Editor其實都只是介面定義而已,並沒有實現任何方法。它只是用來制定了一個儲存鍵值對的協議,具體的實現方式和儲存形式可以是任意的。在Android系統中,它預設以XML格式的檔案來儲存這些資料,實現的類則是SharedPreferencesImpl

下邊就是所儲存的XML檔案的基本格式,它以資料型別作為XML元素的標籤,主鍵(key)是標籤name屬性的值,而主鍵對應的值則作為value屬性的值。但如果是String型別則作為標籤下的content,這樣就不用轉義引號也能更好的處理換行。另外對於null值儲存的結構也比較特殊,它以null為標籤,只有一個name屬性,沒有其他內容。

Android系統會把該XML檔案儲存在/data/data/(packagename)/shared_prefs/下,每一個XML檔案就對應一個SharedPreferences物件(實際是SharedPreferencesImpl物件)。但是SharedPreferences是介面不能用來例項化物件,而SharedPreferencesImpl是系統隱藏類,不能被直接訪問使用,其建構函式也只是包可見。所以不能通過new來構建一個SharedPreferences,必須通過Context提供的getSharedPreferences(String, int)來獲得例項。

該方法的第一個引數是指定XML檔名(不包含“.xml”字尾)的字串,方法會去讀取出對應的檔案,建立一個SharedPreferences物件。第二個引數指定檔案的訪問許可權,共有4中可選模式,從API 17開始基於安全的考慮,MODE_WORLD_READABLEMODE_WORLD_WRITEABLE已經被廢棄使用,只有MODE_PRIVATEMODE_MULTI_PROCESS可使用,一般情況下指定MODE_PRIVATE即可。

對於從SharedPreferences中讀取指定主鍵的值是十分快的,因為所有存在XML的鍵值對資訊全都被讀取被儲存在了SharedPreferences物件中的Map成員變數裡,所以讀取都是基於記憶體訪問。使用Editor寫回到檔案是避不開IO操作的,所以使用commit()提交修改還是會花費一些時間。考慮到這點,Android在API 9裡引進了apply()方法來非同步地將修改後的內容寫回到檔案,當然在寫回前也會先更新記憶體中的鍵值對資訊保證讀取到的時最新的內容。

既然寫回可以是非同步的,那麼多次呼叫getSharedPreferences(String, int)獲得多個SharedPreferences賦值給不同的變數,假如一個變數做了修改,其他的物件不是會出現內容不一致的情況。其實這種情況並不會出現,因為所有建立出來的SharedPreferences都被儲存在ContextImp的一個靜態成員變數中:

這是一個從程式的Package名字到XML檔名再到SharedPreferences物件的二級Map。所以每次呼叫getSharedPreferences(String, int)得到的物件其實都是同一個例項,修改操作也都就作用在同一段記憶體中保證了所有訪問的一致性。apply()方法也會自動將所有修改排入佇列一一寫回檔案從而不會因為順序的錯誤而造成意料之外的錯誤覆蓋。所以因為這個快取機制的存在,多次呼叫getSharedPreferences(String, int)是非常效率的。而寫回時則推薦使用apply()實現非同步操作,而不要用commit()阻礙主執行緒。

SharedPreferences的使用和示例

一般而言SharedPreferences的名字和主鍵名都是固定的,所以可以定義一些final的字串變數來儲存這些名字,在讀取和寫回時都使用這些常熟變數。對於之前展示的XML對應的程式碼就如下邊所示:

既然SharedPreferences的名字是可以任意給定的,所以對於SharedPreferences的使用就可以有非常的針對性建立不同的檔案來儲存不同的內容。比如可以以不同使用者為名存放他們的偏好資訊,可以根據不同介面儲存佈局資訊、已訪問的頁碼。Activity就額外實現了獲取SharedPreferences的方法getPreferences(int),它只需要開發者提供檔案的開啟模式,自動以Activity的類名作為檔名。

SharedPreferences取值時是直接將給定主鍵在Map中的值強制轉換成所需要的值,所以如果用putInt儲存了整型卻用getBoolean()來取,並不會自動轉換成布林型,而是直接丟擲異常,所以要使用要注意保持型別一致。

另外如果沒有儲存過某個主鍵,SharedPreferences會返回null值,而對於StringSet這些型別同樣可以儲存null值,這樣就不能確定null是不是真是儲存的資料了。因此SharedPreferences還提供了contains (String key)方法來檢查給定的主鍵是真的存了null,還是因為並沒有這個鍵值對才返回的null

SharedPreferences的優缺點

之前講過所以讀取過的SharedPreferences的檔案都會被快取在Map裡放在記憶體中,以便下次直接快速訪問,因為快事SharedPreferences的一大優點。但是也因為都背快取,而且存放格式是XML,所以SharedPreferences不宜存放過多的鍵值對,值的內容也不能太大。再者SharedPreferences只支援最基本的幾種型別,儲存一些使用者基本資訊也足夠了。

如果對裝置有root許可權,那麼就可以直接訪問/data/data/(packagename)/shared_prefs/目錄,將XML檔案轉出來檢視。或者直接用在adb shell下直接用cat指令觀察資料的改變,非常的方便。

綜合而言,儲存一些內容較小、型別簡單的資料,SharedPreferences絕對是首選物件。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

瞭解 Android API 中的 SharedPreferences

相關文章