一、DataStore 介绍
DataStore 是 Android Jetpack 中的一个组件,它是一个数据存储的解决方案,跟 SharedPreferences 一样,采用key-value形式存储。
DataStore 保证原子性,一致性,隔离性,持久性。尤其是,它解决了 SharedPreferences API 的设计缺陷。
Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences,让应用能够以异步、事务方式存储数据。
注意:DataStore 比较适合小数据和简单操作,并且无法局部的更新数据。如果你需要支持大型或复杂的数据集、部分更新或引用完整性,请考虑使用 Room 而不是 DataStore。
Preferences DataStore 和 Proto DataStore
- Preferences DataStore:与SharedPreferences类似,通过键值对存储数据,此实现不需要预定义模式,也不提供类型安全。
- Proto DataStore:通过Protocol-Buffers定义存储数据类型以及结构,保证类型安全。
本文重点了解Preferences DataStore。
二、Preferences DataStore
与SharedPreferences类似,通过键值对存储数据,此实现不需要预定义模式,也不提供类型安全。
2.1 添加依赖
在你项目的app_module对应的build.gradle中添加如下依赖:
dependencies { //Typed DataStore (Typed API surface, such as Proto) implementation "androidx.datastore:datastore:1.0.0" // //可选 - RxJava2 support // implementation "androidx.datastore:datastore-rxjava2:1.0.0" // //可选 - RxJava3 support implementation "androidx.datastore:datastore-rxjava3:1.0.0" //Preferences DataStore (SharedPreferences like APIs) implementation "androidx.datastore:datastore-preferences:1.0.0" // // 可选 - RxJava2 support // implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0" // // 可选 - RxJava3 support implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0" }
2.2 使用 Preferences DataStore 存储键值对
首先看看 DataStore 源码,DataStore 是一个接口。
package androidx.datastore.core import kotlinx.coroutines.flow.Flow import java.io.IOException /** * DataStore数据存储提供了一种安全、持久的方式来存储少量数据,如 preferences 和应用程序状态。 * 数据存储提供了ACID保证。它是线程安全的,并且不阻塞。特别是,它解决了SharedReferences API的这些设计缺陷: * 1. Synchronous API encourages StrictMode violations * 2. apply() and commit() have no mechanism of signalling errors * 3. apply() will block the UI thread on fsync() * 4. Not durable – it can returns state that is not yet persisted * 5. No consistency or transactional semantics * 6. Throws runtime exception on parsing errors * 7. Exposes mutable references to its internal state */ public interface DataStore<T> { public val data: Flow<T> public suspend fun updateData(transform: suspend (t: T) -> T): T }
以上可以看出 DataStore 是基于 协程 和 Flow 实现的。
- data 是一个 Flow 对象。
- updateData() 用于更新对象。
并且查看 DataStore 的其他相关源码你会发现他们都是基于Kotlin语言开发。Google 对于推 Kotlin 那是相当执着。
2.2.1 创建 DataStore
- 使用 preferencesDataStore(Kotlin) 创建Datastore 的实例。
- 如果你使用 RxJava,要使用 RxPreferenceDataStoreBuilder。
必需的 name 参数是 Preferences DataStore 的名称。
这里我们使用的是RxJava:
RxDataStore<Preferences> dataStore = new RxPreferenceDataStoreBuilder(this, /*name=*/ "datastore_sc").build(); //创建使用的key Preferences.Key<String> nameKey = PreferencesKeys.stringKey("name"); Preferences.Key<Integer> ageKey = PreferencesKeys.intKey("age");
默认路径:/data/data/com.scc.datastorage/files/datastore/datastore_sc.preferences_pb
这里创建了两个key,分别是 String 类型和 Int 类型。
2.2.1 DataStore 写入数据
//关于 nameKey 和 ageKey 请看上面代码。 putString(nameKey, "name-Scc"); putInteger(ageKey, 25); //2022/1/25 功能:存入String类型的数据 private void putString(Preferences.Key<String> nameKey, String value) { dataStore.updateDataAsync(new Function<Preferences, Single<Preferences>>() { @Override public Single<Preferences> apply(Preferences preferences) throws Throwable { MutablePreferences mutablePreferences = preferences.toMutablePreferences(); mutablePreferences.set(nameKey, value); return Single.just(mutablePreferences); } }); } //2022/1/25 功能:存入Integer类型的数据 private void putInteger(Preferences.Key<Integer> ageKey, Integer value) { dataStore.updateDataAsync(new Function<Preferences, Single<Preferences>>() { @Override public Single<Preferences> apply(Preferences preferences) throws Throwable { MutablePreferences mutablePreferences = preferences.toMutablePreferences(); mutablePreferences.set(ageKey, value); return Single.just(mutablePreferences); } }); }
这里写入的两种类型,这样更加仿版理解和你写成自己的工具类,因为使用 DataStore 比较少实践少就不提供工具类了,以免误导大家。
2.2.3 DataStore 读取数据
getString(nameKey); getInteger(ageKey); //2022/1/25 功能:获取String类型数据 private void getString(Preferences.Key<String> nameKey) { Log.e("DataStore", nameKey.toString()); Flowable<String> example = dataStore.data().map(new Function<Preferences, String>() { @Override public String apply(Preferences preferences) { Log.e("DataStore.apply", preferences.get(nameKey)); return preferences.get(nameKey); } }); Log.e("DataStore.Flowable", example.first("default").blockingGet()); } //2022/1/25 功能:获取Integer类型数据 private void getInteger(Preferences.Key<Integer> ageKey) { Log.e("DataStoreKey", ageKey.toString()); Flowable<Integer> example = dataStore.data().map(new Function<Preferences, Integer>() { @Override public Integer apply(Preferences preferences) { Log.e("DataStore.apply", preferences.get(ageKey).intValue() + ""); return preferences.get(ageKey); } }); Log.e("DataStore.Flowable", example.first(12).blockingGet().toString()); }
三、Proto DataStore
通过Protocol-Buffers定义存储数据类型以及结构,保证类型安全。
Proto DataStore 实现使用 DataStore 和 Protocol-Buffers 将类型化对象持久保存到磁盘。
什么是 Protocol-Buffers?
Protocol-Buffers是谷歌的语言中立、平台中立、可扩展的机制,用于序列化结构化数据——比如XML,但更小、更快、更简单。您只需定义一次数据的结构化方式,然后就可以使用特殊生成的源代码,轻松地在各种数据流之间以及使用各种语言编写和读取结构化数据。
3.1 定义架构
Proto DataStore 需要app/src/main/proto/目录中的 proto 文件中的预定义模式。此架构定义了您在 Proto DataStore 中持久保存的对象的类型。
syntax = "proto3"; option java_multiple_files = true; option java_package = "com.scc.datastorage.proto"; option java_outer_classname = "User"; message User{ string name = 1; int32 age = 2; }
.proto 文件以包声明开头,这有助于防止不同项目之间的命名冲突。
- java_multiple_files:可以为每个生成的类生成一个单独的 .java 文件。
- java_package:指定生成的类应该使用什么 Java 包名称。如果您没有明确指定,它只会匹配包声明给出的包名。
- java_outer_classname:定义了类名。如果没有设置这个 options ,它将通过将文件名转换为大写驼峰式来生成。例如,默认情况下,“my_proto.proto”将使用“MyProto”作为包装类名称。
定义 message:
- 可以使用许多标准的简单数据类型可用作字段类型,包括 bool、int32、float、double 和 string。
- 还可以通过使用其他 message 类型作为字段类型来为你的消息添加进一步的结构。
每个元素上的 "= 1"、"= 2" 标记标识该字段在二进制编码中使用的唯一 "tag"。
更多关于 的内容可以去官网找找 protobuf 语言指南。
注意:存储对象的类是在编译时从 proto 文件中定义的message生成的。确保你 rebuild 你的项目。
3.2 创建 Proto DataStore
- 1.定义一个实现 Serializer 的类,其中 T 是 proto 文件中定义的类型。
- 2.使用 dataStore 创建的属性委托来创建 DataStore 的实例,其中 T 是 proto 文件中定义的类型。
写到这里 proto 文件中定义的类无法生成,也尝试了很多办法,不知道什么情况。后续步骤可参照官网,其他方法和Preferences DataStore类似。就不占用篇幅了。
四、相关链接
个人感觉 DataStore 还没有 SP 和 MMKV 好用,推荐使用 MMKV 毕竟上线好几年了,而且是大厂推出,相对稳定一些,有些个人自己实现了 SP 的功能,但是万一有 Bug 或者突然不继续维护了是不是就尴尬了。