一片爆红
在之前我发过一篇 DataStore 的文章:用力抱一下 Jetpack DataStore,当时使用的版本是1.0.0-alpha05
大家也都知道 Google 的性格,在 alpha 版本的库的 API 随时可能修改,事实证明我的猜测是正确的,在更新了 DataStore 的版本之后果然一片爆红。
* init Context *@ param context Context fun init ( context : Context ){ dataStore = context . createDataStore ( preferenceName ) } fun readIntFlow ( key : String , default : Int =0): Flow < Int >= dataStore . data . Catch { this : FlowCollector < Preferences > if ( it is IoException ){ it . printStackTrace () emit ( emptyPreferences ())} else { throw it }. map { it : Preferences it [ preferencesKey ( key )]?: default } fun readIntData ( key : String , default : Int =0): Int { var yalue =0 runBlocking { this : CoroutineScope dataStore . data . first { it : Preferences value = it [ preferencesKey ( key )]?: default true first } } return value
但不要担心,发布了 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 有四个参数,其中只有名字是必选参数,剩下的参数都有对应的默认值,下面就来看看这几个参数的含义吧。
- name: DataStore 的名字,不多说;
- 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()) } }
由于 Parcelables 与 DataStore 一起使用并不安全,因为不同 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 在正式版本发布的时候性能应该会追上甚至超越 MMKV 和 SP 。