介紹:
在官方尚未出手之前,存儲鍵值對等小型數(shù)據(jù)集可能普遍采用兩種方式,SharedPreferences或是MMKV(如果您需要支持大型或復雜數(shù)據(jù)集、部分更新或參照完整性,請考慮使用 Room,而不是 DataStore。DataStore 非常適合簡單的小型數(shù)據(jù)集,不支持部分更新或參照完整性。) MMKV這次暫時不提及,因為DatStore本身對比的也就是SharedPreferences,而且官方也是明確的建議我們遷移到DataStore。 DataStore包含了兩種實現(xiàn)方式:
- Preferences DataStore僅使用鍵存儲和訪問值數(shù)據(jù)。此實現(xiàn)不需要預定義的架構(gòu),并且不提供類型安全性。
- Proto DataStore將數(shù)據(jù)存儲為自定義數(shù)據(jù)類型的實例。此實現(xiàn)要求您使用協(xié)議緩沖區(qū)(protobuf – PB協(xié)議)定義架構(gòu),但它提供類型安全性。
與SharedPreferences的對比:
首先我們來看官方的一張對比
功能 | SharedPreferences | PreferencesDataStore | ProtoDataStore |
異步 API | (僅用于通過監(jiān)聽器讀取已更改的值) | (通過 Flow 以及 RxJava 2 和 3 Flowable) | (通過 Flow 以及 RxJava 2 和 3 Flowable) |
同步 API | (但無法在界面線程上安全調(diào)用) | ||
可在界面線程上安全調(diào)用 | 1 | (這項工作已在后臺移至 Dispatchers.IO) | (這項工作已在后臺移至 Dispatchers.IO) |
可以提示錯誤 | |||
不受運行時異常影響 | 2 | ||
包含一個具有強一致性保證的事務性 API | |||
處理數(shù)據(jù)遷移 | |||
類型安全 | 使用協(xié)議緩沖區(qū) |
我們先暫時只看PreferencesDataStore和SharedPreferences 首先同步API和異步API這兩點區(qū)別是沒有問題的。 SharedPreferences:
- apply()來完成異步操作:會立即更改內(nèi)存中的 SharedPreferences 對象,但會將更新異步寫入磁盤。而且apply()還有個問題就是,雖然他本身是異步的來完成IO操作,但是在SharedPreferencesImpl.EditorImpl.apply()中會添加到QueuedWork中,當Service或者Activity啟動或停止時,具體可見ActivityThread中handleServiceArgs,handleStopService,handlePauseActivity,handleStopActivity均會執(zhí)行QueuedWork.waitToFinish()等待數(shù)據(jù)寫入的完成,因為要保證數(shù)據(jù)不會丟失,但是我們也知道,onPause() 是不適合執(zhí)行耗時操作的,因為當你期待另一個Activity的時候,會先onPause當前Activity,這很明顯,假如你寫入了較多內(nèi)容,然后立馬啟動了另一個Activity,結(jié)果在onPause()被阻塞,就很容易導致ANR。
- commit()來實現(xiàn)同步操作,但應避免從主線程調(diào)用它,因為它可能會阻塞UI線程,這點沒什么好說的,而且會返回Boolean值來表示寫入是否成功。
詳細的可以查看SharedPreferences.Editor接口提供的注釋,具體的實現(xiàn)在SharedPreferencesImpl.EditorImpl我這里就不貼源碼了。 回到DataStore,PreferencesDataStore本身是基于攜程Flow來實現(xiàn)的,所以異步API這點沒有任何問題,不過至于同步的使用方式,放到后面來說,我們先看普遍的異步使用方式。我就不一一復述了。
使用:
private const val USER_PREFERENCES_NAME = “user_preferences”private val Context.dataStore by preferencesDataStore( name = USER_PREFERENCES_NAME)
首先是通過委托拿到DataStore單例.
public fun preferencesDataStore( name: String, corruptionHandler: ReplaceFileCorruptionHandler? = null, produceMigrations: (Context) -> List = { listOf() }, scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())): ReadOnlyProperty { return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)}internal class PreferenceDataStoreSingletonDelegate internal constructor( private val name: String, private val corruptionHandler: ReplaceFileCorruptionHandler?, private val produceMigrations: (Context) -> List, private val scope: CoroutineScope) : ReadOnlyProperty { private val lock = Any() @GuardedBy(“lock”) @Volatile private var INSTANCE: DataStore? = null override fun getValue(thisRef: Context, property: KProperty): DataStore { return INSTANCE ?: synchronized(lock) { if (INSTANCE == null) { val applicationContext = thisRef.applicationContext INSTANCE = PreferenceDataStoreFactory.create( corruptionHandler = corruptionHandler, migrations = produceMigrations(applicationContext), scope = scope ) { applicationContext.preferencesDataStoreFile(name) } } INSTANCE!! } }}
本質(zhì)上還是走的PreferenceDataStoreFactory.create()創(chuàng)建,是一個非常標準的雙重檢查鎖單例。 來看一下create()函數(shù)的參數(shù)吧
- corruptionHandler: 異常處理,當反序列化錯誤時會走到這,可以用于讀取錯誤是返回默認值或捕獲異常。
- migrations: 用于遷移SharedPreferences到PreferenceDataStore
- scope: 協(xié)程的作用域,指定IO操作及數(shù)據(jù)轉(zhuǎn)換的執(zhí)行的協(xié)程作用域
- produceFile: 基于提供的Context和name創(chuàng)建或讀取對應的文件,默認路徑為this.applicationContext.filesDir + datastore/fileName
推薦做法也是通過PreferenceDataStoreFactory來創(chuàng)建DataStore實例并作為單例注入需要它的類中。
讀?。?/h1>
dataStore.data .catch { exception -> // 有異常拋出 if (exception is IOException) { // 使用默認空值 emit(emptyPreferences()) } else { // 其他異常則繼續(xù)拋出 throw exception } }.map { preferences -> // 數(shù)據(jù)轉(zhuǎn)化 }.collect { // 收集數(shù)據(jù) }
emptyPreferences()可以參考上面提到的官方教學示例,里面會詳細介紹PreferenceData的KV 可以看出dataStore返回的是一個Flow,你可以很方便的轉(zhuǎn)換成你所需要的數(shù)據(jù)。
寫入:
val INT_KEY = intPreferencesKey(“int_key”)dataStore.edit { preferences -> preferences[INT_KEY] = 1}// 調(diào)用public suspend fun DataStore.edit( transform: suspend (MutablePreferences) -> Unit): Preferences { return this.updateData { // It’s safe to return MutablePreferences since we freeze it in // PreferencesDataStore.updateData() it.toMutablePreferences().apply { transform(this) } }}
直接調(diào)用edit函數(shù),不過要注意的是edit可能會拋出異常。 同時還有兩點需要注意:
總結(jié):
總的來說還是很推薦使用DataStore,與協(xié)程的搭配,用起來也是非常的便利,至于PB協(xié)議的ProtoDataStore,可以參考官方的示例來實踐,差別主要是還是集中在PB文件的處理。
作者:Lowae鏈接:https://juejin.cn/post/7109395564789235720