再抱一抱DataStore

再抱一抱DataStore

Android小彩虹2021-08-22 20:17:06200A+A-

好久不见

大家好,好久不见,上次发文章还是三月份,转眼间马上都六月份了,最近一直在忙没空写,已经两三个月没有发过文章了,周末正好抽出了点时间,就想着写点东西吧。

一片爆红

在之前我发过一篇 DataStore 的文章:用力抱一下 Jetpack DataStore,当时使用的版本是1.0.0-alpha05,从2020年9月发布第一个 alpha 版本之后到现在经过大半年的版本更新终于发布了 beta 版本。

官方截图

大家也都知道 Google 的性格,在 alpha 版本的库的 API 随时可能修改,事实证明我的猜测是正确的,在更新了 DataStore 的版本之后果然一片爆红。

原来代码

但不要担心,发布了 beta 版本就证明 API 已经基本完善了,也就是说之后 API 就不会有大的改动了,剩下的就是性能方面的优化了。

开始使用

之前为了使用 DataStore 我还专门写了一个工具类,但现在看来是多余的了,来看看如何使用 beta 版本的 DataStore 吧。

添加依赖

第一步肯定要来添加下依赖。

// DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0-beta01"

初始化 DataStore

在之前 alpha 版本中,初始化方法如下:

context.createDataStore(preferenceName)

通过 Context 的扩展方法 createDataStore 来创建一个 DataStore ,但现在不可以了,这个方法已经被删除,那么现在该如何创建 DataStore 呢?现在应该使用由 preferencesDataStore 创建的属性委托来创建 Datastore<Preferences> 实例。

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "PlayAndroidDataStore")

在项目中只在顶层调用一次 preferencesDataStore 方法,便可在应用的所有其余部分通过此属性访问该实例,这样可以更轻松地将 DataStore 保留为单例。

下面咱们来看看 preferencesDataStore 的源码吧。

public fun preferencesDataStore( name: String, corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null, produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() },
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): ReadOnlyProperty<Context, DataStore<Preferences>> {
    return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)
}

可以看到 preferencesDataStore 有四个参数,其中只有名字是必选参数,剩下的参数都有对应的默认值,下面就来看看这几个参数的含义吧。

  • nameDataStore 的名字,不多说;
  • corruptionHandler:如果DataStore在尝试读取数据时遇到CorruptionException,则将调用destroyHandler。无法对数据进行反序列化时,序列化程序会引发CorruptionException;
  • produceMigrations:产生迁移。 ApplicationContext作为参数传递给这些回调。在对数据进行任何访问之前,都要运行DataMigrations。不管是否成功,每个生产者和迁移都可以运行一次以上(可能是因为另一个迁移失败或对磁盘的写入失败)。
  • scope:作用域

上面介绍了 preferencesDataStore 方法中的参数,大家可以根据需求自行填写使用。

使用DataStore

创建 DataStore

使用的时候首先需要调用下咱们刚定义的 Context 的扩展属性:dataStore。

private lateinit var dataStore: DataStore<Preferences>

/** * init Context * @param context Context */
fun init(context: Context) {
    dataStore = context.dataStore
}

之后就可以通过dataStore进行操作了。

保存数据

先来看看如何进行数据的保存。

suspend fun saveBooleanData(key: String, value: Boolean) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[booleanPreferencesKey(key)] = value
    }
}

suspend fun saveIntData(key: String, value: Int) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[intPreferencesKey(key)] = value
    }
}

suspend fun saveStringData(key: String, value: String) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[stringPreferencesKey(key)] = value
    }
}

suspend fun saveFloatData(key: String, value: Float) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[floatPreferencesKey(key)] = value
    }
}

suspend fun saveLongData(key: String, value: Long) {
    dataStore.edit { mutablePreferences ->
        mutablePreferences[longPreferencesKey(key)] = value
    }
}

大家在使用的时候可以根据不同类型来分别进行保存,当然了,如果你不想分的这么细,也可以写一个方法来进行统一调用。

suspend fun <U> putData(key: String, value: U) {
    when (value) {
        is Long -> saveLongData(key, value)
        is String -> saveStringData(key, value)
        is Int -> saveIntData(key, value)
        is Boolean -> saveBooleanData(key, value)
        is Float -> saveFloatData(key, value)
        else -> throw IllegalArgumentException("This type can be saved into DataStore")
    }
}

读取数据

保存完数据就该进行读取了,来看看如何读取数据吧。

fun readBooleanFlow(key: String, default: Boolean = false): Flow<Boolean> =
    dataStore.data
        .catch {
            //当读取数据遇到错误时,如果是 `IOException` 异常,发送一个 emptyPreferences 来重新使用
            //但是如果是其他的异常,最好将它抛出去,不要隐藏问题
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[booleanPreferencesKey(key)] ?: default
        }

fun readIntFlow(key: String, default: Int = 0): Flow<Int> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[intPreferencesKey(key)] ?: default
        }

fun readStringFlow(key: String, default: String = ""): Flow<String> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[stringPreferencesKey(key)] ?: default
        }

fun readFloatFlow(key: String, default: Float = 0f): Flow<Float> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[floatPreferencesKey(key)] ?: default
        }

fun readLongFlow(key: String, default: Long = 0L): Flow<Long> =
    dataStore.data
        .catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[longPreferencesKey(key)] ?: default
        }

同样的,根据类型不同选择不同的读取方法进行使用即可,当然了,也可以像上面保存数据一样再写一个方法进行统一调用也可以。

fun <U> getData(key: String, default: U): Flow<U> {
    val data = when (default) {
        is Long -> readLongFlow(key, default)
        is String -> readStringFlow(key, default)
        is Int -> readIntFlow(key, default)
        is Boolean -> readBooleanFlow(key, default)
        is Float -> readFloatFlow(key, default)
        else -> throw IllegalArgumentException("This type can be saved into DataStore")
    }
    return data as Flow<U>
}

进阶使用

上面已经描述了如何简单使用 DataStore ,但只能使用一些基础类型,如果咱们想要使用数据类呢?当然可以使用 DataStore 所支持的 Proto,但这里咱们不说这个,应为在 Kotlin 中也可以直接通过数据类序列化的方式来使用 DataStore ,来看看如何使用吧。

定义数据类

首先咱们来定义一个数据类吧。

data class ZhuPreferences(
    val name: String,
    val age: Int
)

数据类很简单,只有两个参数,名字和年龄。

实现 DataStore 序列化器

下一步需要做的是来实现 DataStore 的序列化器。

@Serializable
data class ZhuPreferences(
    val name: String = "jiang",
    val age: Int = 20
)

object ZhuPreferencesSerializer : Serializer<ZhuPreferences> {
    override val defaultValue = ZhuPreferences()

    override suspend fun readFrom(input: InputStream): ZhuPreferences {
        try {
            return Json.decodeFromString(
                ZhuPreferences.serializer(), input.readBytes().decodeToString()
            )
        } catch (serialization: SerializationException) {
            throw CorruptionException("Unable to read UserPrefs", serialization)
        }
    }

    override suspend fun writeTo(t: ZhuPreferences, output: OutputStream) {
        output.write(Json.encodeToString(ZhuPreferences.serializer(), t).encodeToByteArray())
    }
}

由于 ParcelablesDataStore 一起使用并不安全,因为不同 Android 版本之间的数据格式可能会有所变化,所以这里使用 Serializable 的方式进行序列化。

使用 DataStore 序列化器

创建 DataStore

数据类都准备好了,下面来创建 DataStore ,这里不能使用上面提到的 preferencesDataStore 方法来进行创建了,而是需要通过 dataStore 方法。

val Context.dataStores by dataStore("test", serializer = ZhuPreferencesSerializer)

写入数据

来看看如何写入数据类的数据吧。

private suspend fun setDataStore(zhuPreferences: ZhuPreferences) {
    dataStores.updateData {
        it.copy(name = zhuPreferences.name, age = zhuPreferences.age)
    }
}

从上面代码中可以看到通过 dataStores 中的 updateData 方法来进行修改数据,然后再使用生成的 .copy() 函数更新数据。

读取数据

写入完成之后来看看如何进行读取数据吧。

private suspend fun getDataStore() {
    val name = dataStores.data.first().name
    val age = dataStores.data.first().age
}

由于在创建 dataStores 的时候已经传入了序列化器,所以读取数据很简单,直接可以调取数据类中的参数进行使用。

总结

DataStore 中使用到了 Flow 、协程,在使用的时候更适合现在的写法,但不能只因为他的写法简单就使用啊,目前即使DataStore 已经发布了 beta 版本,但是性能还是堪忧,但我相信 DataStore 在正式版本发布的时候性能应该会追上甚至超越 MMKVSP

走过路过的盆友不要忘记点赞、关注、收藏啊,如果有问题的可以在评论区告诉我。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们