目录

1. 前言2. 分类2.1 Preferences DataStore 和SharedPreferences的区别

3. 实践3.1 Preferences DataStore3.1.1 依赖3.1.2 案例3.1.3 最后

3.2 Proto DataStore3.2.1 依赖3.2.1.1 添加协议缓冲区插件3.2.1.2 配置协议缓冲区

3.2.2 定义架构3.2.3 简单使用3.2.3.1 创建序列化器3.2.3.2 数据存储和读取

1. 前言

Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore使用Kotlin 协程和Flow 以异步、一致的事务方式存储数据。如果您当前在使用SharedPreferences存储数据,请考虑迁移到 DataStore。如果您需要支持大型或复杂数据集、部分更新或参照完整性,请考虑使用Room,而不是 DataStore。

2. 分类

DataStore 提供两种不同的实现:Preferences DataStore和 Proto DataStore。

Preferences DataStore使用键值对存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。DataStore 是基于 Flow 实现的,不会阻塞主线程。只支持Int,Long,Boolean,Float,String键值对数据,适合存储简单、小型的数据,并且不支持局部更新,如果修改了其中一个值,整个文件内容将会被重新序列化。Proto DataStore将数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。 Proto DataStore使用协议缓冲区来定义架构。使用协议缓冲区可持久保留强类型数据。与 XML 和其他类似的数据格式相比,协议缓冲区速度更快、规格更小、使用更简单,并且更清楚明了。虽然使用 Proto DataStore 需要学习新的序列化机制,但我们认为 Proto DataStore 有着强大的优势,值得学习。

2.1 Preferences DataStore 和SharedPreferences的区别

SharedPreferences 有一个看上去可以在界面线程中安全调用的同步API,但是该 API 实际上执行磁盘 I/O 操作。此外,apply()会阻断fsync()上的界面线程。每次有服务启动或停止以及每次 activity 在应用中的任何地方启动或停止时,系统都会触发待处理的fsync()调用。界面线程在apply()调度的待处理fsync()调用上会被阻断,这通常会导致ANR。

3. 实践

3.1 Preferences DataStore

3.1.1 依赖

// DataStore Preferences

implementation("androidx.datastore:datastore-preferences:1.0.0")

// optional - RxJava2 support

implementation("androidx.datastore:datastore-preferences-rxjava2:1.0.0")

// optional - RxJava3 support

implementation("androidx.datastore:datastore-preferences-rxjava3:1.0.0")

3.1.2 案例

下面案例来源于官网案例,地址:DataStore | Android 开发者 | Android Developers (google.cn)。

class MainActivity2 : AppCompatActivity() {

// 创建一个DataStore,并申明在顶层以方便调用

private val Context.dataStore: DataStore by preferencesDataStore(name = "settings")

// 声明一个int类型的key

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")

private val textView: TextView by lazy { findViewById(R.id.text) }

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

}

// 两个按钮的监听事件

fun buttonOnClick(view: View){

runBlocking {

launch {

when(view.id){

R.id.update -> {

incrementCounter()

}

R.id.read -> {

textView.text = getData().toString()

}

}

}

}

}

// 存储-自增1

suspend fun incrementCounter() {

dataStore.edit { settings ->

val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0

settings[EXAMPLE_COUNTER] = currentCounterValue + 1

}

}

// 获取当前值

suspend fun getData(): Int {

return dataStore.data.map { settings ->

settings[EXAMPLE_COUNTER] ?: 0

}.first()

}

}

结果:

3.1.3 最后

在前面提到了,这种类型的Preferences DataStore还支持的数据类型有:Long,Boolean,Float,String等,不妨看看对应的声明函数:

longPreferencesKey()

booleanPreferencesKey()

floatPreferencesKey()

stringSetPreferencesKey()

对应的,随便找一个方法,看下是如何实现的:

@JvmName("longKey")

public fun longPreferencesKey(name: String): Preferences.Key = Preferences.Key(name)

也就是其实底层也还是使用Preferences.Key来指明类型。和SP类似数据内容也存储在本地磁盘data/data/packagename/中:

3.2 Proto DataStore

对于Preferences DataStore中的键只能为上述指定的类型,故而如果我们需要存储自定义对象的数据的时候,就显得力不从心了。故而在jetpack中提供了Proto DataStore。

3.2.1 依赖

引入datastore的依赖:

implementation("androidx.datastore:datastore:1.0.0")

// optional - RxJava2 support

implementation("androidx.datastore:datastore-rxjava2:1.0.0")

// optional - RxJava3 support

implementation("androidx.datastore:datastore-rxjava3:1.0.0")

为了使用 Proto DataStore,让协议缓冲区为我们的架构生成代码,我们需要对 build.gradle 文件进行一些更改:

3.2.1.1 添加协议缓冲区插件

plugins {

...

id "com.google.protobuf" version "0.8.12"

}

3.2.1.2 配置协议缓冲区

implementation "com.google.protobuf:protobuf-javalite:3.10.0"

在dependencies平级添加:

protobuf {

protoc {

artifact = "com.google.protobuf:protoc:3.10.0"

}

// Generates the java Protobuf-lite code for the Protobufs in this project. See

// https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation

// for more information.

generateProtoTasks {

all().each { task ->

task.builtins {

java {

option 'lite'

}

}

}

}

}

3.2.2 定义架构

在app/src/main/目录下创建proto目录,然后创建一个xxx.proto文件: testDemo.proto文件内容:

syntax = "proto3"; // 声明proto的版本

// 定义生成的类的包名

option java_package = "com.weizu.jetpackdemo.proto";

// 声明的是内部类MyProtoBean, 格式: 类型+字段名称+字段编号

message MyProtoBean {

int32 _id = 1;

string _name = 2;

int32 _age = 3;

bool _isMan = 4;

}

至于更加详细的解释可以查阅:protobuf 语言指南以及使用 Proto DataStore (google.cn) 然后Build一下,就可以看到生成的文件: 打开文件可以看到在该文件中生成了配置中对应的message类,和对应的set/get方法: 至此环境配置完毕,接着开始简单使用。

3.2.3 简单使用

3.2.3.1 创建序列化器

我们需要实现序列化器,以告知 DataStore 如何读取和写入我们在 proto 文件中定义的数据类型。如果磁盘上没有数据,序列化器还会定义默认返回值。

object MyBeanSerializer: Serializer {

override val defaultValue: TestDemo.MyProtoBean

get() = TestDemo.MyProtoBean.getDefaultInstance()

override suspend fun readFrom(input: InputStream): TestDemo.MyProtoBean {

try {

return TestDemo.MyProtoBean.parseFrom(input)

} catch (exception: InvalidProtocolBufferException) {

throw CorruptionException("Cannot read proto.", exception)

}

}

override suspend fun writeTo(t: TestDemo.MyProtoBean, output: OutputStream) {

t.writeTo(output)

}

}

3.2.3.2 数据存储和读取

/**

* @author 梦否 on 2022/3/28

* @blog https://mengfou.blog.csdn.net/

*/

class MainActivity2 : AppCompatActivity() {

private val textView: TextView by lazy { findViewById(R.id.text) }

private val dataStore: DataStore by lazy {

DataStoreFactory.create(

produceFile = { applicationContext.dataStoreFile("user_prefs.pb") },

serializer = MyBeanSerializer

)

}

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

}

private var i = 0

// 按钮监听函数

fun buttonOnClick(view: View) {

runBlocking {

launch {

when (view.id) {

R.id.update -> {

storeData(1 + i, 20 + i, true, "张三${i}")

i++

}

R.id.read -> {

val first = readData().first()

Log.e("TAG", "id:${first.id}")

Log.e("TAG", "age:${first.age}")

Log.e("TAG", "isMan:${first.isMan}")

Log.e("TAG", "name:${first.name}")

}

}

}

}

}

// 读取数据时处理异常

private fun readData(): Flow {

return dataStore.data

.catch { exception ->

// dataStore.data throws an IOException when an error is encountered when reading data

if (exception is IOException) {

Log.e("TAG", "Error reading sort order preferences.", exception)

emit(TestDemo.MyProtoBean.getDefaultInstance())

} else {

throw exception

}

}

}

// 添加数据

private suspend fun storeData(id: Int, age: Int, isMan: Boolean, name: String) {

dataStore.updateData { preferences ->

preferences.toBuilder()

.setAge(age)

.setId(id)

.setIsMan(isMan)

.setName(name)

.build()

}

}

}

结果:

每次插入数据都会覆盖掉以前的数据,也就是在使用对象存储数据的时候,只能存储一条数据。

参考链接

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: