dataStore 是jectpack推出的轻量化存储组件,包含PreferenceDataStore和ProtoDataStore两种方式,基于Flow()实现数据存储
Why To Use
之前的Android轻量化存储组件SharedPreferences读写操作会阻塞主线程,在数据量不多的情况下没什么影响,但随着对组件的滥用,存储的数据量逐渐增大,性能问题逐渐显现,官方也挣扎着出了apply()异步化方案,希望挽救SharedPreferences于水火,但可惜apply()并未实现真正意义上的异步,只能另立新王DataStore,两者区别如下
功能 | SharePreferences | PreferenceDataStore | PotoDataStore |
---|
是否阻塞主线程 | Y | N | N | 是否线程安全 | N | Y | Y | 是否支持跨进程 | Y | N | N | 是否类型安全 | N | N | Y | 是否能监听数据变化 | N | Y | Y |
How To Use
PreferenceDataStore
- 引入依赖
implementation 'androidx.datastore:datastore-preferences:1.0.0'
- 初始化对象
val preDataStore:DataStore<Preferences> by preferencesDataStore(name="user")
也可以使用Context拓展函数初始化(需引入implementation 'androidx.datastore:datastore-preferences:1.0.0-alpha05' 在1.0.0版本中没有该方法,不知为啥)
private val dataStore by lazy {
context.createDataStore(
name = USER
)
}
本质都是调用PreferenceDataStoreFactory.create() 方法初始化
INSTANCE = PreferenceDataStoreFactory.create(
corruptionHandler = corruptionHandler,
migrations = produceMigrations(applicationContext),
scope = scope
) {
applicationContext.preferencesDataStoreFile(name)
}
- 写入数据
suspend fun saveDataToDataStore(key:String,value:String){
preDataStore.edit { it[stringPreferencesKey(key)]=value }
}
由于edit方法是一个挂起函数,所以保存方法也要声明为挂起函数,在协程中调用 4. 读取数据
fun getDataFromDataStore(key: String): Flow<String?> {
return preDataStore.data.map { it[stringPreferencesKey(key)] }
}
注意返回的是Flow类型,使用时可通过flow的collect方法获取,由于collect也是挂起函数,所以也需运行在协程中
GlobalScope.launch(Dispatchers.Main) {
getDataFromDataStore("key1").collect { tvShow.setText(it) }
}
使用Dispatchers线程调度设置到主线程更新UI数据,需引入依赖
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
ProtoDataStore
protoDataStore 最大的特点就是类型安全,通过序列化对象存储数据
- 引入依赖
implementation 'androidx.datastore:datastore-core:1.0.0'
implementation 'com.google.protobuf:protobuf-java:3.14.0'
implementation 'com.google.protobuf:protoc:3.14.0'
根目录下
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.13'
app gradle 里配置 plugins { id ‘com.google.protobuf’ } 在android 同级配置闭包 protobuf { protoc { artifact = ‘com.google.protobuf:protoc:3.14.0’ // 也可以配置本地编译器路径 }
generateProtoTasks {
all().each { task ->
task.builtins {
remove java
}
task.builtins {
java {}// 生产java源码
}
}
}
} android闭包下指定proto文件路径 sourceSets { main { java { srcDir ‘src/main/java’ } proto { srcDir ‘src/main/proto’ //指定.proto文件路径 } } }
- 定义架构
在app/src/main/proto/ 目录的proto文件中定义架构,用于保存对象的类型,使用的是protobuf 语言
syntax = "proto3";
option java_package = "com.example.application";
option java_multiple_files = true;
message User {
int32 age = 1;
string userName=2;
}
主要是对message修饰的类进行定义,定义完后重构项目使proto文件生效
- 定义proto DataStore 对象
3.1 实现Serializer类,T为proto文件定义的类型,重写读取和写入的方法
object UserSerializer:Serializer<User>{
override val defaultValue: User
get() = User.getDefaultInstance()
override suspend fun readFrom(input: InputStream): User {
try {
return User.parseFrom(input)
}catch (e: InvalidProtocolBufferException){
throw CorruptionException("Cannot read proto.", e)
}
}
override suspend fun writeTo(t: User, output: OutputStream) {
t.writeTo(output)
}
}
3.2 通过UserSerializer得到Proto DataStore对象:
val userDataStore:DataStore<User> by dataStore(fileName = "user.pb",serializer = UserSerializer)
本质是调用DataStoreFactory.create()方法获取对象
DataStoreFactory.create(
serializer = serializer,
produceFile = { applicationContext.dataStoreFile(fileName) },
corruptionHandler = corruptionHandler,
migrations = produceMigrations(applicationContext),
scope = scope
)
- 设置值
suspend fun saveDataUseProto(age : Int,name:String){
userDataStore.updateData { user->
user.toBuilder().setAge(age)
.setUserName(name)
.build()
}
}
User对象中设置了age和name两个值,动态生成的User文件提供build方法设置属性值
- 获取值
fun getDataUseProto():Flow<User>{
return userDataStore.data.map { it }
}
这里it代指User对象,通过对flow操作可以获取user中的值
问题
之前使用SharedPreferences的项目如何更换到DataStore中?
在DataStore函数的创建方法中,有个参数produceMigrations,传人SharedPreferences的文件名进行初始化即可将文件内容设置到DataStore中
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)
}
由创建函数可知produceMigrations 是一个list类型的数组,说明可以一次将sp文件全部转换完毕,转换完后原来的sp文件将会删除
val preDataStore2:DataStore<Preferences> by preferencesDataStore(name="user",produceMigrations ={listOf<DataMigration<Preferences>>(SharedPreferencesMigration(this,"user"))})
DataStore如何在同步代码中使用
使用阻塞方法获取值val exampleData = runBlocking { context.dataStore.data.first() } ,但如果都只是使用阻塞方式调用就可能导致UI线程卡顿,所以DataStore 又提供了预读取功能来预先加载,这样即使是阻塞调用也不会损耗太多性能
override fun onCreate(savedInstanceState: Bundle?) {
lifecycleScope.launch {
context.dataStore.data.first()
}
}
实践
GitHub DataStoreDemo
参考
在Android中使用proto 一文玩转dataStore DataStore|Android 开发者
|