前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jetpack组件之DataStore

Jetpack组件之DataStore

作者头像
八归少年
发布2022-06-29 15:42:51
1.2K0
发布2022-06-29 15:42:51
举报
文章被收录于专栏:program

疫情距离我最近的一次,隔离的第10天,居家办公的第8天,希望疫情早点过去,结束隔离✊。

首语

数据持久化指将哪些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑关机的情况下,数据依然不会丢失。

Android系统中主要提供了三种方式来实现数据持久化功能。即文件存储、SharedPreferences存储及数据库存储。其中SharedPreferences是使用键值对的方式来存储轻量型数据,使用比较简单,且程序卸载后也会一并清除,不会残留数据。但是SharedPreferences也存在很多缺点,它是对磁盘进行I/O操作,会引起性能问题,导致ANR,且多线程场景下效率低下、存储延迟,存储较大数据如json或html会频繁引起GC,导致界面卡顿,曾经在项目开发中使用SharedPreferences碰到数据缓存延迟的情况,后面就使用了腾讯的MMKV

现在,Google推出DataStore,旨在代替SharedPreferences,克服其大部分缺点。

Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。

对比

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

  • Preferences DataStore 由类 DataStore 和 Preferences 实现,使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 将数据作为自定义数据类型的实例序列化存储在磁盘。此实现要求您使用协议缓冲区(Protocol Buffers)来定义架构,但可以确保类型安全。

Protocol Buffers (ProtocolBuffer/ protobuf )是Google公司开发的一种数据描述语言,类似于XML能够将结构化数据序列化,可用于数据存储、通信协议等方面。同XML相比,Protocol buffers在序列化结构化数据方面有许多优点:

  • 更简单。
  • 数据描述文件只需原来的1/10至1/3。
  • 解析速度是原来的20倍至100倍。
  • 减少了二义性。
  • 生成了更容易在编程中使用的数据访问类。

下图是Google对SharedPreferences与DataStore两种不同实现的对比。

DataStore对比
DataStore对比

依赖

代码语言:javascript
复制
// Preferences DataStore
implementation "androidx.datastore:datastore-preferences:1.0.0"
// Proto DataStore
implementation "androidx.datastore:datastore-core:1.0.0"

使用

在两种实现中,除非另外特指,否则 DataStore 会将首选项存储在文件中,并且所有的数据操作都会在 Dispatchers.IO 上执行。

Preferences DataStore
创建

使用由PreferencesDataStore创建的属性委托来创建 Datastore<Preferences> 实例。在 kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性访问该实例。这样可以更轻松地将 DataStore 保留为单例。

代码语言:javascript
复制
val dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
读取

由于 Preferences DataStore 不使用预定义的架构,因此您必须使用相应的键类型函数为需要存储在 DataStore<Preferences> 实例中的每个值定义一个键。例如,如需为 int 值定义一个键,请使用 intPreferencesKey()。。然后,使用 DataStore.data 属性读取内容。

代码语言:javascript
复制
val USER_INFO = intPreferencesKey("user_info")
dataStore.data.collect {
	val i = it.toPreferences()[USER_INFO]
    tvContent.text=i.toString()
}
写入

Preferences DataStore 提供了一个edit() 函数,用于以事务方式更新 DataStore 中的数据。该函数的 transform 参数接受代码块,您可以在其中根据需要更新值。转换块中的所有代码均被视为单个事务,查看源码可知。

代码语言:javascript
复制
 dataStore.edit { settings ->
            val currentCounterValue = settings[USER_INFO] ?: 0
            settings[USER_INFO] = currentCounterValue + 1
        }
Proto DataStore

Proto DataStore 实现使用 DataStore 和协议缓冲区将类型化的对象保留在磁盘上。

导入
  1. 导入plugins 插件。在app的build.gradle中添加如下代码。
代码语言:javascript
复制
plugins {
    id "com.android.application"
    id "kotlin-android"
    id "com.google.protobuf" version "0.8.12"
}
  1. 添加依赖。
代码语言:javascript
复制
implementation "com.google.protobuf:protobuf-javalite:3.10.0"
  1. 配置 protoc 命令,与dependencies同级。
代码语言:javascript
复制
protobuf {
    protoc {
        // //从仓库下载 protoc 这里的版本号需要与依赖 com.google.protobuf:protobuf-javalite:xxx 版本相同
        artifact = 'com.google.protobuf:protoc:3.10.0'
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option "lite"
                }
            }
        }
    }

    // 默认生成目录 $buildDir/generated/source/proto 通过 generatedFilesBaseDir 改变生成位置
    generatedFilesBaseDir = "$projectDir/src/main"
}
  1. 设置proto文件位置。
代码语言:javascript
复制
android {
    sourceSets {
        main {
            proto {
                // proto 文件默认路径是 src/main/proto
                // 可以通过 srcDir 修改 proto 文件的位置
                srcDir 'src/main/proto'
            }
        }
    }
}
  1. 编译项目。

在app/src/main目录下新建一个文件夹proto,然后在文件夹proto下新建一个.proto类型的文件UserPrefs,编写proto文件及其字段,重新构建项目。

代码语言:javascript
复制
// 固定的,还有proto2
syntax="proto3";

// 格式:包名 + . + 文件名
option java_package = "com.yhj.kotlincomponent.protobuf";
//可以生成单独的.java每个生成的类的文件
option java_multiple_files = true;

message Settings {
  int32 count = 1;
}

这里推荐安装 Protocol Buffer Editor插件,它为协议缓冲区文件提供编辑器支持。语法高亮、编辑器增强功能等有点,调试起来非常方便。

对于proto3语法,使用技巧,参考Google proto3教程,讲解详细。

构建完成后,就可以看到app\src\main\debug目录下生成了protobuf文件目录,里面包含Settings、SettingsOrBuilder、UserPrefs文件。

创建

定义一个实现 Serializer<T> 的类,其中 T 是 proto 文件中定义的类型。此序列化器类会告知 DataStore 如何读取和写入您的数据类型。请务必为该序列化器添加默认值,以便在尚未创建任何文件时使用。

使用由 dataStore 创建的属性委托来创建 DataStore<T> 的实例,其中 T 是在 proto 文件中定义的类型。在您的 Kotlin 文件顶层调用该实例一次,便可在应用的所有其余部分通过此属性委托访问该实例。filename 参数会告知 DataStore 使用哪个文件存储数据,而 serializer 参数会告知 DataStore 上面中定义的序列化器类的名称。

代码语言:javascript
复制
object SettingsSerializer : Serializer<Settings> {
    override val defaultValue: Settings = Settings.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): Settings {
        try {
            return Settings.parseFrom(input)
        } catch (exception: InvalidProtocolBufferException) {
            throw CorruptionException("Cannot read proto.", exception)
        }
    }

    override suspend fun writeTo(
        t: Settings,
        output: OutputStream
    ) = t.writeTo(output)
}

val Context.settingsDataStore: DataStore<Settings> by dataStore(
    fileName = "settings.pb",
    serializer = SettingsSerializer
)
写入

Proto DataStore 提供了一个updateData() 函数,用于以事务方式更新存储的对象。updateData() 为您提供数据的当前状态,作为数据类型的一个实例,并在原子读-写-修改操作中以事务方式更新数据。

代码语言:javascript
复制
 settingsDataStore.updateData { currentSettings ->
                currentSettings.toBuilder()
                    .setCount(currentSettings.count + 1)
                    .build()
            }
读取

使用 DataStore.data来获取存储的数据。

代码语言:javascript
复制
settingsDataStore.data.collect {
    Log.e("yhj", it.count.toString(), )
}

迁移SharedPreferences

在创建DataStore时,preferencesDataStore参数里包含produceMigrations参数,用来迁移SharedPreferences,需要执行一次读取或者写入操作,DataStore才会自动合并,迁移成功后会删除原有的SharedPreferences文件,Proto DataStore 用法相同。

代码语言:javascript
复制
val dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings", produceMigrations = { con ->
        listOf(SharedPreferencesMigration(con, "app_cache"))
    })

原理

使用由PreferencesDataStore创建的属性委托来创建 Datastore<Preferences> 实例。

代码语言:javascript
复制
override fun getValue(thisRef: Context, property: KProperty<*>): DataStore<Preferences> {
        return INSTANCE ?: synchronized(lock) {
            if (INSTANCE == null) {
                val applicationContext = thisRef.applicationContext

                INSTANCE = PreferenceDataStoreFactory.create(
                    corruptionHandler = corruptionHandler,
                    migrations = produceMigrations(applicationContext),
                    scope = scope
                ) {
                    applicationContext.preferencesDataStoreFile(name)
                }
            }
            INSTANCE!!
        }
    }

创建了一个文件用于将键值对写入磁盘,文件位于applicationContext.filesDir+datastore/的子目录中。文件后缀名为.preferences_pb,

代码语言:javascript
复制
public fun Context.preferencesDataStoreFile(name: String): File =
    this.dataStoreFile("$name.preferences_pb")

public fun Context.dataStoreFile(fileName: String): File =
    File(applicationContext.filesDir, "datastore/$fileName")

数据的读取直接通过dataStore.data获取,数据的写入通过dataStore.edit,实际上也是通过dataStore.updateData来写入的

代码语言:javascript
复制
public interface DataStore<T> {
   
    public val data: Flow<T>
    
    public suspend fun updateData(transform: suspend (t: T) -> T): T
}
代码语言:javascript
复制
public suspend fun DataStore<Preferences>.edit(
    transform: suspend (MutablePreferences) -> Unit
): Preferences {
    return this.updateData {
        // It's safe to return MutablePreferences since we freeze it in
        // PreferencesDataStore.updateData()
        it.toMutablePreferences().apply { transform(this) }
    }
}

总结

DataStore的两种实现相比而言,Preferences DataStore相对简单一些,Proto DataStore比较复杂些。

DataStore克服了SharedPreference的许多缺点,Google也大力推荐,所以是时候跟SharedPreference说再见了,拥抱 Jetpack DataStore。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 首语
  • 对比
  • 依赖
  • 使用
    • Preferences DataStore
      • 创建
      • 读取
      • 写入
    • Proto DataStore
      • 导入
      • 创建
      • 写入
      • 读取
  • 迁移SharedPreferences
  • 原理
  • 总结
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档