導(dǎo)語(yǔ)
Jetpack簡(jiǎn)介及其它組件文章
DataStore就是SharedPreferences(簡(jiǎn)稱(chēng)SP)的替代品旋廷,Google為什么要用DataStore來(lái)替代SP呢趋观,因?yàn)镾P存在著很多問(wèn)題懂扼,我之前在Android SharedPreferences轉(zhuǎn)為MMKV中有詳細(xì)說(shuō)明了SP的不足,但是當(dāng)時(shí)的有些觀點(diǎn)還有些淺薄恨课,所以使用了MMKV來(lái)替代SP险胰,現(xiàn)在我更推薦大家使用DataStore替代SP志衣,下面會(huì)詳細(xì)講出屯援。
主要內(nèi)容
- DataStore的基本概念
- DataStore猛们、SP、MMKV對(duì)比
- DataStore的基本使用
- DataStore的封裝
DataStore的基本概念
Jetpack DataStore 是一種數(shù)據(jù)存儲(chǔ)解決方案狞洋,允許您使用協(xié)議緩沖區(qū)存儲(chǔ)鍵值對(duì)或類(lèi)型化對(duì)象弯淘。DataStore 使用 Kotlin 協(xié)程和 Flow 以異步、一致的事務(wù)方式存儲(chǔ)數(shù)據(jù)徘铝。
所以如果想要使用DataStore耳胎,就必須使用Kotlin,因?yàn)镈ataStore用到了flow惕它,flow用到了協(xié)程怕午,協(xié)程是Kotlin的特性。
但是淹魄,如果將DataStore封裝起來(lái)郁惜,那么直接使用Java調(diào)用的話,也是可以正常使用的甲锡,所以大家也不用擔(dān)心兆蕉。
DataStore 提供兩種不同的實(shí)現(xiàn):Preferences DataStore 和 Proto DataStore。
- Preferences DataStore 使用鍵存儲(chǔ)和訪問(wèn)數(shù)據(jù)缤沦。此實(shí)現(xiàn)不需要預(yù)定義的架構(gòu)虎韵,也不確保類(lèi)型安全。
- Proto DataStore 將數(shù)據(jù)作為自定義數(shù)據(jù)類(lèi)型的實(shí)例進(jìn)行存儲(chǔ)缸废。此實(shí)現(xiàn)要求您使用協(xié)議緩沖區(qū)來(lái)定義架構(gòu)包蓝,但可以確保類(lèi)型安全。
這里我們重點(diǎn)講Preferences DataStore企量,這足以夠大多數(shù)用戶使用测萎。
DataStore、SP届巩、MMKV對(duì)比
大量文章指出MMKV的性能是多么多么高硅瞧,但其實(shí)MMKV在存大數(shù)據(jù)的時(shí)候,因?yàn)镸MKV是自行管理一塊內(nèi)存恕汇,性能是反而更低的腕唧。
我們先來(lái)看一下三種方案進(jìn)行1000次Int值的連續(xù)寫(xiě)入耗時(shí):
這么一看MMKV快到離譜,但是SP是可以異步寫(xiě)入的瘾英,而DataStore是基于協(xié)程的枣接,我們關(guān)心的卡頓是主流程的流暢,所以我們只需要考慮主線程的耗時(shí)方咆,所以會(huì)變成這樣:
可以看到耗時(shí)大大縮減月腋,但是還是比MMKV慢很多蟀架,這是Int數(shù)據(jù)類(lèi)型的寫(xiě)入瓣赂,Int數(shù)據(jù)是很小的榆骚,我們換成長(zhǎng)字符串,再來(lái)看一下效果:
這時(shí)我們會(huì)發(fā)現(xiàn)煌集,MMKV反而成為那個(gè)最慢的妓肢,DataStore遙遙領(lǐng)先。
所以我們要注意的是主線程的耗時(shí)苫纤,而這個(gè)數(shù)據(jù)其實(shí)也并不重要碉钠,因?yàn)樵陧?xiàng)目中也很少有1000次大數(shù)據(jù)的寫(xiě)入,而1000次大數(shù)據(jù)耗時(shí)也不過(guò)不到1秒卷拘,所以選合適的更重要喊废。
DataStore的基本使用
創(chuàng)建 Preferences DataStore
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = "settings"
)
name參數(shù)是 Preferences DataStore 的名稱(chēng)。類(lèi)似SP中的name栗弟。
將內(nèi)容寫(xiě)入 Preferences DataStore
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
suspend fun putIntData() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
我們通過(guò)intPreferencesKey來(lái)定義我們的Key的name以及存儲(chǔ)的數(shù)據(jù)類(lèi)型污筷,如果是存放String類(lèi)型就是stringPreferencesKey。
通過(guò)settings[EXAMPLE_COUNTER]是可以取到數(shù)據(jù)的乍赫,通過(guò)賦值可以用來(lái)存數(shù)據(jù)瓣蛀。
從 Preferences DataStore 讀取內(nèi)容
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
通過(guò)這種方式取到的是一個(gè)被Flow,通過(guò)exampleCounterFlow.first()就可以拿到真實(shí)的數(shù)據(jù)雷厂。
在同步代碼中使用 DataStore
DataStore 的主要優(yōu)勢(shì)之一是異步 API惋增,但可能不一定始終能將周?chē)拇a更改為異步代碼。
Kotlin 協(xié)程提供 runBlocking()
協(xié)程構(gòu)建器改鲫,以幫助消除同步與異步代碼之間的差異诈皿。您可以使用 runBlocking()
從 DataStore 同步讀取數(shù)據(jù)。
val exampleData = runBlocking { context.dataStore.data.first() }
對(duì)界面線程執(zhí)行同步 I/O 操作可能會(huì)導(dǎo)致 ANR 或界面卡頓钩杰。您可以通過(guò)從 DataStore 異步預(yù)加載數(shù)據(jù)來(lái)減少這些問(wèn)題:
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
context.dataStore.data.first()
// You should also handle IOExceptions here.
}
}
這樣纫塌,DataStore 可以異步讀取數(shù)據(jù)并將其緩存在內(nèi)存中。以后使用 runBlocking() 進(jìn)行同步讀取的速度可能會(huì)更快讲弄,或者如果初始讀取已經(jīng)完成措左,可能也可以完全避免磁盤(pán) I/O 操作。
DataStore的封裝
封裝
既然我們都使用Kotlin了避除,那就使用Kotlin的特性擴(kuò)展函數(shù)來(lái)實(shí)現(xiàn)吧怎披。
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
/**
* Created by 郭士超 on 2022/11/6 15:48
* Describe:DataStoreExt.kt
*/
/**
* 存放數(shù)據(jù)
*/
fun <T> DataStore<Preferences>.putData(key: String, value: T) {
runBlocking {
when(value) {
is String -> {
putString(key, value)
}
is Int -> {
putInt(key, value)
}
is Long -> {
putLong(key, value)
}
is Float -> {
putFloat(key, value)
}
is Double -> {
putDouble(key, value)
}
is Boolean -> {
putBoolean(key, value)
}
}
}
}
/**
* 取出數(shù)據(jù)
*/
fun <T> DataStore<Preferences>.getData(key: String, defaultValue: T): T {
val data = when(defaultValue) {
is String -> {
getString(key, defaultValue)
}
is Int -> {
getInt(key, defaultValue)
}
is Long -> {
getLong(key, defaultValue)
}
is Float -> {
getFloat(key, defaultValue)
}
is Double -> {
getDouble(key, defaultValue)
}
is Boolean -> {
getBoolean(key, defaultValue)
}
else -> {
throw IllegalArgumentException("This type cannot be saved to the Data Store")
}
}
return data as T
}
/**
* 清空數(shù)據(jù)
*/
fun DataStore<Preferences>.clear() = runBlocking { edit { it.clear() } }
/**
* 存放String數(shù)據(jù)
*/
private suspend fun DataStore<Preferences>.putString(key: String, value: String) {
edit {
it[stringPreferencesKey(key)] = value
}
}
/**
* 存放Int數(shù)據(jù)
*/
private suspend fun DataStore<Preferences>.putInt(key: String, value: Int) {
edit {
it[intPreferencesKey(key)] = value
}
}
/**
* 存放Long數(shù)據(jù)
*/
private suspend fun DataStore<Preferences>.putLong(key: String, value: Long) {
edit {
it[longPreferencesKey(key)] = value
}
}
/**
* 存放Float數(shù)據(jù)
*/
private suspend fun DataStore<Preferences>.putFloat(key: String, value: Float) {
edit {
it[floatPreferencesKey(key)] = value
}
}
/**
* 存放Double數(shù)據(jù)
*/
private suspend fun DataStore<Preferences>.putDouble(key: String, value: Double) {
edit {
it[doublePreferencesKey(key)] = value
}
}
/**
* 存放Boolean數(shù)據(jù)
*/
private suspend fun DataStore<Preferences>.putBoolean(key: String, value: Boolean) {
edit {
it[booleanPreferencesKey(key)] = value
}
}
/**
* 取出String數(shù)據(jù)
*/
private fun DataStore<Preferences>.getString(key: String, default: String? = null): String = runBlocking {
return@runBlocking data.map {
it[stringPreferencesKey(key)] ?: default
}.first()!!
}
/**
* 取出Int數(shù)據(jù)
*/
private fun DataStore<Preferences>.getInt(key: String, default: Int = 0): Int = runBlocking {
return@runBlocking data.map {
it[intPreferencesKey(key)] ?: default
}.first()
}
/**
* 取出Long數(shù)據(jù)
*/
private fun DataStore<Preferences>.getLong(key: String, default: Long = 0): Long = runBlocking {
return@runBlocking data.map {
it[longPreferencesKey(key)] ?: default
}.first()
}
/**
* 取出Float數(shù)據(jù)
*/
private fun DataStore<Preferences>.getFloat(key: String, default: Float = 0.0f): Float = runBlocking {
return@runBlocking data.map {
it[floatPreferencesKey(key)] ?: default
}.first()
}
/**
* 取出Double數(shù)據(jù)
*/
private fun DataStore<Preferences>.getDouble(key: String, default: Double = 0.00): Double = runBlocking {
return@runBlocking data.map {
it[doublePreferencesKey(key)] ?: default
}.first()
}
/**
* 取出Boolean數(shù)據(jù)
*/
private fun DataStore<Preferences>.getBoolean(key: String, default: Boolean = false): Boolean = runBlocking {
return@runBlocking data.map {
it[booleanPreferencesKey(key)] ?: default
}.first()
}
使用
封裝完成我們應(yīng)該如何使用呢?
//第一步先創(chuàng)建一個(gè)自己的Application
class MyApplication: Application() {
companion object {
lateinit var instance : MyApplication
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
/**
* Created by 郭士超 on 2022/11/6 16:38
* Describe:AppDataStore.kt
*/
object AppDataStore {
// 創(chuàng)建DataStore
private val MyApplication.appDataStore: DataStore<Preferences> by preferencesDataStore(
name = "App"
)
// DataStore變量
private val dataStore = MyApplication.instance.appDataStore
private fun <T> putData(key: String, value: T) {
dataStore.putData(key, value)
}
private fun <T> gutData(key: String, value: T): T {
return dataStore.getData(key, value)
}
fun clear() {
dataStore.clear()
}
private const val NUMBER = "number"
fun putNumber(number: Int) {
dataStore.putData(NUMBER, number)
}
fun getNumber(): Int {
return dataStore.getData(NUMBER, 0)
}
}
封裝好之后瓶摆,我們?cè)谑褂玫臅r(shí)候凉逛,不同的模塊使用不同的XxxDataStore,這樣可以解耦合群井,我們將put和get方法放到XxxDataStore中統(tǒng)一管理状飞,方便我們快速定位這個(gè)方法都在哪里調(diào)用,從而更快定位到問(wèn)題或者加快開(kāi)發(fā)速度。