Jetpack系列——DataStore(alpha)

408 阅读3分钟

Jetpack DataStore是Google提出的一种数据存储解决方案,允许开发者使用key-value的方式或者是Protocol Buffers结构的数据对象。 DataStore使用Kotlin协程和Flow异步来实现数据存储,旨在替换SharedPreference,目前还是alpha版本。

根据Google的官方定义,DataStore提供了两种不同的数据存储的实现:

  • Preference方式:使用key-value方式进行数据存储和读取,此方式不需要预定义数据结构,而且不保证数据类型的安全性;
  • Protocol Buffers方式:将数据以预定义好的pb结构进行存储和读取,此方式能够保证数据类型的安全性。

接下来一起接入并尝试如何使用:

添加依赖

dependencies {
  // Preferences DataStore
  implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"

  // Proto DataStore
  implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
}

目前DataStore尚属于alpha阶段,所以其库版本最新为alpha02。

由于DataStore有两种实现方式,所以这里需要开发者选择对应的依赖添加,注意:这两个依赖不能同时添加

使用Preference存储key-value数据

要想使用Preference存储数据,就需要用到DataStore类和Preferences类。

我们可以通过Context.createDataStore()方法来创建一个DataStore实例对象,一般只需要指定参数name就可以,name表示的就是Preferences DataStore的名称:

val dataStore: DataStore<Preferences> = context.createDataStore(
  name = "settings"
)

还需要创建Preferences类实例:

val EXAMPLE_COUNTER = preferencesKey<Int>("example_counter")

接下来就可以从DataStore中读取数据了:

val exampleCounterFlow: Flow<Int> = dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}
exampleCounterFlow.collcect()

注意Preferences方式并不是类型安全的,定义的preferencesKey虽然指定了范型是Int,但实际存储在DataStore中的数据可能是别的类型。

使用dataStore的data属性,返回一个Flow对象,最后调用Flow的collect()方法,即可读取数据。

如果要向DataStore中写入数据,可以通过DataStore对象的edit()方法进行编辑,该方法接收一段代码,可以在其中根据需要进行更新。

suspend fun incrementCounter() {
  dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

上述代码需要在Kotlin协程中进行执行,如果单纯抽取方法,该方法需要使用suspend关键字修饰。

使用Proto存储数据

Protocol Buffers方式是通过Proto协议预先定义好数据结构再进行读写。首先需要在app/src/main/proto/文件夹下创建proto文件,在该文件中定义pb数据结构,如:

syntax = "proto3";

option java_package = "com.jia.demo";
option java_multiple_files = true;

message Store {
  int32 example_counter = 1;
}

之后rebuild项目,即可以生成对应的Java代码,关于如何在Android Studio中配置Proto环境,请参考wiki.corp.kuaishou.com/pages/viewp…

之后需要做两步操作:

1、创建Serializer类,指定范型为proto生成的Java类性,Serializer用来告诉DataStore如何读写我们定义好的数据类型对象:

class StoreSerializer : Serializer<Store> {

    override fun readFrom(input: InputStream): Store {
        return Store.parseFrom(input)
    }

    override fun writeTo(t: Store, output: OutputStream) {
        t.writeTo(output)
    }
}

2、通过Context.createDataStore()创建DataStore对象,指定参数fileName和serializer:

storeDataStore = createDataStore(
	fileName = "store.pb",
	serializer = StoreSerializer()
)

接下来就可以读写DataStore了,

suspend fun writePb2dataStore() {
	storeDataStore?.updateData { it ->
		it.toBuilder().setExampleCounter(5).build()
	}
}
storeDataStore?.data?.map {
	Log.e("jia", "onCreate: " + it.exampleCounter)
}!!.collect()

注意

DataStore还有一个好处就是其是异步执行,所以我们可以结合Kotlin的runBlocking协程,来帮助弥合同步和异步代码之间的差距,我们可以在runBlocking协程中对DataStore进行读写,如:

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}