Jetpack DataStore [beta] 体验记录

前言

好久没写东西了,主要是 Compose 更新变化太快,官方文档更新都跟不上代码,写相关的内容立刻就会过期。java

可是在 Compose Demo 项目中不是只有 UI,数据层面逐步增长了 Room 和 DataStore,DataStore 已经迎来 beta 版本,想提早了解一下的话,如今正是时候。android

DataStore 概述

DataStore 的目标是成为 SharedPreference 的上位替代,用于少许 key-value 数据的存储,存储方式是本地文件。这听起来和 SharedPreference 并无本质区别,事实上也确实如此,由于两者的区别在 API 的封装上。SharedPreference 的实现至关简单,存取值得时候经过类型消除和类型强转保证 xml 的文件内容和 Java 的数据类型匹配,没法保证安全性。在读写数据时主要提供同步的 API,在 UI 线程读取较大内容的时候会有明显卡顿,而异步的 apply 写入又难以确认写入完成的时间。git

DataStore 从设计之初就想避免上述问题,也所以变得相对复杂,提升了使用门槛,要不要换恐怕还得从业务角度考虑,如今仅作了解便可。程序员

首先,DataStore 提供了两种不一样数据结构的实现,分别是 Preferences 实现和 Proto 实现。其中 Preferences 的实现较为简单,不须要提早定义数据类型,也不能保证类型安全,这方面跟 SharedPreference 一致;Proto 实现则是使用 protobuf 预约义要保存的数据,能够保证类型正确,代价是代码更加复杂。github

其次,DataStore 深度绑定 Kotlin 协程,读取数据和写入数据的函数都是 suspend 函数,读数据的返回值直接采用了 Flow,统一了单次获取和持续监听。web

使用 DataStore

DataStore 的两种实现对应着不一样的依赖,我以为 Preferences 实现像是一种向易用性妥协的方案,真的想要简单好用不如仍是用 SharedPreference,因此要学就一步到位,用 Proto 实现。json

Proto 实现的使用方式分为如下几步:安全

  1. 引入依赖
  2. 编写 .proto 定义数据格式
  3. 编译 .proto(手动调用或者配置 gradle 插件自动生成)
  4. 编写数据类接入代码
  5. 读写数据

比起 SharedPreference 确实多了不少步骤,但流程还算清晰,真正的坑都潜伏在细节中。markdown

1、引入依赖

implementation("androidx.datastore:datastore:1.0.0-beta02") // 截止到20210626的最新版
复制代码

2、protobuf

protobuf 的全称是 Protocal Buffers,详细信息参考官网:developers.google.com/protocol-bu…数据结构

protobuf 已经有不少应用场景了,最广为人知的应该是 gRPC。比起 xml 或者 json,protobuf 更节省空间,速度更快。

具体的语法不在本文范围内,按需学习便可。举个例子,我用来保存用户我的配置的一个 .proto 文件内容以下:

syntax = "proto3";

option java_package = "com.github.moqigit.timecount.datastore";
option java_multiple_files = true;

message UserPreferences {
  int64 lastSessionTime = 1;
  bool anti_anxious = 2;
  bool check_update = 3;
  bool cloud_storage = 4;
  string primary_color = 5;
  string light_color = 6;
  string dark_color = 7;
}
复制代码

protobuf 的使用方法是先定义数据结构,再编译成指定语言供开发者使用。(暂不支持 Kotlin,咱们用 Java 便可)

按照官方文档安装后,咱们能够在终端用 protoc 命令编译 .proto 文件
Usage: protoc [OPTION] PROTO_FILES

3、kts + protobuf 插件

每次改动都敲命令或者执行脚本编译 proto 也是个比较麻烦的事,程序员应该向自动化努力。gradle 构建系统已经有 protobuf 的插件,"简单"添加一下就能够解放双手。

简单的前提,是别用 kts。

由于用 Kotlin 编写 gradle 脚本实际上仍是比较冷门的,毕竟 groovy 的 DSL 至关简洁了,彻底能够知足平常开发工做的需求。不过用都用了,坑是自选的,只能建议各位现阶段不要太勇,别给本身找麻烦。

过程主要是查文档,看 kts 中的函数和 groovy 的区别,逐步解决构建中的错误,不赘述了,直接看结果:

// app/build.gradle.kts

plugins {
    id("com.android.application")
    id("kotlin-android")
    id("kotlin-kapt")
    id("com.google.protobuf").version("0.8.12")
}

dependencies {
    implementation("androidx.datastore:datastore:1.0.0-beta02")
    implementation("com.google.protobuf:protobuf-javalite:3.10.0")
}

// protobuf 插件的配置函数跟 groovy 写法上有必定的区别,千万别直接复制
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.10.0"
    }
    generateProtoTasks {
        all().configureEach {
            builtins {
                id("java") { option("lite") }
            }
        }
    }
}
复制代码

.proto 文件放在 app/src/main/proto 文件夹中能够省略配置 android sourceSets,make 成功后能够在 generated java 文件夹下找到生成的代码,大概是这样

image.png

4、接入数据类

文档参考:developer.android.com/topic/libra…

接入数据类是一个增长模板代码的过程,分红两步,最终目的是提供一个 DataStore 对象的获取方式。

  1. 实现 Serializer<T>,定义默认值
  2. 经过委托建立 DataStore<T> 对象供外部调用

代码以下:

object UserPreferenceSerializer : Serializer<UserPreferences> {

	override val defaultValue: UserPreferences
		get() = UserPreferences.getDefaultInstance()

	override suspend fun readFrom(input: InputStream): UserPreferences {
		try {
			return UserPreferences.parseFrom(input)
		} catch (e: Exception) {
			throw CorruptionException("Cannot read proto.", e)
		}
	}

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

val Context.userPrefDataStore : DataStore<UserPreferences> by dataStore(
    fileName = "user_pref.pb", // 保存文件名
    serializer = UserPreferenceSerializer
)
复制代码

Serializer 的代码几乎是固定的,建立 DataStore 的参数也只是多须要一个文件名,若是能经过带参数的注解之类的方式自动生成对应代码就行了,但愿正式版能有优化。

fileName 对应的文件保存在 /data/data/com.github.moqigit.timecount/files/datastore 里面,内容不是文本因此不能直接查看,安全性比 SharedPreference 高。protobuf 很是省空间,上面的7个键值对的测试数据只须要 40B 的空间,另外一个用 SharedPreference 存的2个键值对的文件就要 188B。

5、读写 API

读写 API 就相对简单了,会协程就没什么问题。

读:

private fun printData() {
    viewModelScope.launch {
        val pref = this@MainActivity.userPrefDataStore.data.firstOrNull() ?: return@launch
        Log.e("asdfg", "${pref.lastSessionTime}")
        Log.e("asdfg", "${pref.toString()}")
    }
}
复制代码

打印数据只须要一次读取,因此取 first 便可,若是须要监听数据变化,则须要改用 collect。

写:

private fun writeTestData() {
    viewModelScope.launch {
        pref.updateData {
            it.toBuilder()
                .setAntiAnxious(true)
                .setCloudStorage(true)
                .setCheckUpdate(true)
                .setDarkColor("#333333")
                .setLightColor("#262626")
                .setLastSessionTime(System.currentTimeMillis())
                .setPrimaryColor("#513f2d")
                .build()
        }
    }
}
复制代码

通过事先定义的数据结构自带数据类型,读写过程就像 data class 同样顺滑,数据类型经由编译器检查也很难出错,这应该是 protobuf 的大优点。

对应的页面还没弄好,只能在 Logcat 看效果了。

首次打开,只有一个默认值有数据 image.png

写入以后,数据更新

image.png

默认值在 Serializer 的 defaultValue 中设置,若是自定义了完善的默认值数据,初次打开就不会一片空白了。

DataStore 的 Hilt 封装

DataStore 的读写依赖 Context,使用时限制较多,解决方案也是老生常谈了,能够全局持有 applicationContext,用顶层函数或者单例提供 DataStore 对象。

但 Demo 项目已经接入 Hilt 了,DataStore 也很适合经过依赖注入建立对象,随手加一下就行了:

@Module
@InstallIn(SingletonComponent::class)
object DSManager {

    private val Context.userPrefDataStore : DataStore<UserPreferences> by dataStore(
        fileName = "user_pref.pb",
        serializer = UserPreferenceSerializer
    )
    
    @Provides
    fun provideUserPrefDS(@ApplicationContext context: Context) = context.userPrefDataStore
}
复制代码

在 Activity 的使用:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    @Inject
    lateinit var pref: DataStore<UserPreferences>
    
    //……
}
复制代码

在 ViewModel 的使用:

@HiltViewModel
class TimeCountViewModel @Inject constructor(
	private val pref: DataStore<UserPreferences>,
) : ViewModel() {
    // ……
}
复制代码



DataStore 的上手体验就是这样了,初次使用比较复杂,优点须要复杂的业务场景才能体现,我的不是很推荐急着使用,毕竟兼容历史版本是一个巨大的坑,。并且咱们也不是必定要在 SharedPreference 和 DataStore 二选一,不想用 Kotlin 协程还能够试一下 MMKV

Hilt 也比较稳定了,接下来准备写 Compose 或者 Hilt,有什么想法欢迎评论指出~

相关文章
相关标签/搜索