Android Jetpack 之 DataStore

2023-11-14

1. 概述

Google 推出了 JetPack 的新成员 DataStore,DataStore 是一种新的数据存储方案。DataStore 以异步、一致的事务方式存储数据,克服了 SharedPreferences 的一些缺点。

Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和流程构建而成,提供两种不同的实现:

  • Proto DataStore,它允许您存储类型化的对象(由协议缓冲区提供支持)
  • Preferences DataStore,用于存储键值对

以异步、一致的事务方式存储数据,克服了 SharedPreferences 的大部分缺点。

 Google 的目的很明确,就是打算用来取代 SharedPreferences,相信很快 SharedPreferences 就会变成 Deprecated 了。DataStore 提供了两种不同的实现:Preferences DataStore 和 Proto DataStore。DataStore 和 SharedPreferences 的数据存储方案对比如下所示:(链接,链接可能访问不了,多访问几次)

Feature

SharedPreferences

PreferencesDataStore

ProtoDataStore

Async API

✅ (only for reading changed values, via listener)

✅ (via Flow)

✅ (via Flow)

Synchronous API

✅ (but not safe to call on UI thread)

Safe to call on UI thread

❌*

✅ (work is moved to Dispatchers.IO under the hood)

✅ (work is moved to Dispatchers.IO under the hood)

Can signal errors

Safe from runtime exceptions

❌**

Has a transactional API with strong consistency guarantees

Handles data migration

✅ (from SharedPreferences)

✅ (from SharedPreferences)

Type safety

✅ with Protocol Buffers

2. DataStore 的导入

目前 DataStore 还处于 alpha 版本,要使用 DataStore 需要添加的依赖如下:

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

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

3. DataStore 的使用

DataStore 分为 Preferences DataStore 和 Proto DataStore。

3.1 Preferences DataStore

Preferences DataStore 是由类 DataStore 和 Preferences 实现,用于存储简单的键值对到磁盘。

3.1.1 Preferences DataStore 的创建

    private val DATASTORE_PREFERENCE_NAME = "DataStorePreference"//定义 DataStore 的名字
    mDataStorePre = this.createDataStore(
           name = DATASTORE_PREFERENCE_NAME
    )

 createDataStore 是 Context 的一个扩展方法:

fun Context.createDataStore(
    name: String,
    corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null,
    migrations: List<DataMigration<Preferences>> = listOf(),
    scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
): DataStore<Preferences> =
    PreferenceDataStoreFactory.create(
        produceFile = {
            File(this.filesDir, "datastore/$name.preferences_pb")
        },
        corruptionHandler = corruptionHandler,
        migrations = migrations,
        scope = scope
    )

3.1.2 数据的写入和读取

    private suspend fun savePreInfo(value: String) {
        var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
        mDataStorePre.edit { mutablePreferences ->
            mutablePreferences[preKey] = value
        }
    }

    private suspend fun readPreInfo(): String {
        var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)
        var value = mDataStorePre.data.map { preferences ->
            preferences[preKey] ?: ""
        }
        return value.first()
    }

Preferences DataStore 以键值对的形式存储在本地,首先应该定义一个 Key:

 var preKey = preferencesKey<String>(PREFERENCE_KEY_NAME)

key 的类型是 Preferences.Key<T>,但是只支持 Int , String, Boolean , Float , Long 类型:

inline fun <reified T : Any> preferencesKey(name: String): Preferences.Key<T> {
    return when (T::class) {
        Int::class -> {
            Preferences.Key<T>(name)
        }
        String::class -> {
            Preferences.Key<T>(name)
        }
        Boolean::class -> {
            Preferences.Key<T>(name)
        }
        Float::class -> {
            Preferences.Key<T>(name)
        }
        Long::class -> {
            Preferences.Key<T>(name)
        }
        Set::class -> {
            throw IllegalArgumentException("Use `preferencesSetKey` to create keys for Sets.")
        }
        else -> {
            throw IllegalArgumentException("Type not supported: ${T::class.java}")
        }
    }
}

Preferences DataStore 中是通过 DataStore.edit() 写入数据,edit 方法是个 suspend 函数,必须在协程中进行调用;通过 DataStore.data 去读取数据,返回的是一个 Flow<T> 。

具体的使用例子如下:

            R.id.btn_pre_save -> {
                var textPre = edit_pre.text.trim().toString()
                lifecycleScope.launch {
                    savePreInfo(textPre)
                }
            }
            R.id.btn_pre_read -> {
                lifecycleScope.launch {
                    var value = readPreInfo()
                    Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
                }

            }

3.1.3 从 SharedPreference 迁移数据

Google 推出 DataStore 的目的是为了取代 SharedPreference,对于老项目,就需要从 SharedPreference 中进行数据的迁移,从 SharedPreference 迁移到 DataStore。

3.1.1 中 createDataStore 方法的参数中有 migrations 参数:

migrations: List<DataMigration<Preferences>> = listOf()

只需要在 createDataStore 方法中按照如下格式就可以自动完成数据的迁移:

        mDataStorePre = this.createDataStore(
            name = DATASTORE_PREFERENCE_NAME,
            migrations = listOf(SharedPreferencesMigration(this, SP_PREFERENCE_NAME))
        )

3.2 Proto DataStore 

Proto DataStore 是通过 protocol buffers 将对象序列化存储在磁盘。Protocol buffers 是什么,在这之前我听都没听过,怎么办,学呗。

Protocol buffers 的介绍可以参考:

https://developers.google.cn/protocol-buffers

在这里,只需要知道:

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

  • 更简单

  • 数据描述文件只需原来的1/10至1/3

  • 解析速度是原来的20倍至100倍

  • 减少了二义性

  • 生成了更容易在编程中使用的数据访问类

3.2.1 Proto DataStore 的创建

在创建 Proto DataStore 的时候,在 AndroidStudio 中,必须先做如下配置:

在 project 的 build.gradle 中添加依赖:

classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8'

在 app 的 build.gradle 中,修改的比较多,所以整个文件都贴出来:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.protobuf'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "cn.zzw.datastore"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    buildFeatures {
        dataBinding true
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    sourceSets {
        main {
            proto {
                srcDir 'src/main/proto'
                include '**/*.proto'
            }
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    // Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha02"
    // Proto DataStore
    implementation "androidx.datastore:datastore-core:1.0.0-alpha02"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
    implementation "com.google.protobuf:protobuf-javalite:3.10.0"
}

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'
                }
            }
        }
    }
}

接着,在目录 app/src/main/proto 创建文件 user_prefs.proto:

syntax = "proto3";

option java_package = "cn.zzw.datastore";
option java_multiple_files = true;

message UserPreferences {
      int32 id = 1;
      string name = 2;
      int32 age = 3;
      string phone = 4;
}

记得要执行 rebuild project 。

接着创建 UserPreferencesSerializer:

package cn.zzw.datastore

import androidx.datastore.Serializer
import java.io.InputStream
import java.io.OutputStream

object UserPreferencesSerializer : Serializer<UserPreferences> {
    override fun readFrom(input: InputStream): UserPreferences {
        return UserPreferences.parseFrom(input)
    }

    override fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)

}

最后创建 Proto DataStore :

        mDataStorePro =
            this.createDataStore(
                fileName = "user_pros.pb",
                serializer = UserPreferencesSerializer
            )

3.2.2 数据的写入和读取

    private suspend fun saveProInfo(value: String) {
        mDataStorePro.updateData { preferences ->
            preferences.toBuilder().setId(110).setName(value).setAge(39).setPhone("119120").build()
        }
    }

    private suspend fun readProInfo(): String {+
        val userPreferencesFlow: Flow<UserPreferences> = mDataStorePro.data
        return userPreferencesFlow.first().toString()
    }

调用如下:

            R.id.btn_pro_save -> {
                var textPre = edit_pro.text.trim().toString()
                lifecycleScope.launch {
                    saveProInfo(textPre)
                }
            }
            R.id.btn_pro_read -> {
                lifecycleScope.launch {
                    var value = readProInfo()
                    Toast.makeText(this@MainActivity, value, Toast.LENGTH_SHORT).show()
                }
            }

4.总结

目前 DataStore 还处于 alpha 版本,等到正式版本出来后,还是要考虑下用它来替换 SharedPreference。DataStore 和 SharedPreference 一样适合用来存储小且简单的数据,如果是较多的数据,还是推荐用 Room。此篇只是记录了 DataStore 的基础用法,等后续正式版本出来后,再来研究下它的源码,看它是如何实现。

参考:

https://developer.android.google.cn/topic/libraries/architecture/datastore

https://codelabs.developers.google.com/codelabs/android-preferences-datastore#5

https://scalereal.com/android/2020/09/03/hello-datastore-bye-sharedpreferences-android.html

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Jetpack 之 DataStore 的相关文章

  • Phonegap从Java代码获取本地存储值?

    我已经使用phonegap在客户端保存了数据本地存储 http docs phonegap com en 1 2 0 phonegap storage storage md html现在我想用java代码访问保存的数据 这可能吗 我怎样才能
  • 如何在android中的视频视图中获取视频的总长度

    我有一个问题 我想获取在视频视图中运行的视频的总长度 为此我正在使用视频视图的 getDuration 方法 但当我将其与 currentPosition 进行比较时 它总是返回 1 实际上我希望如果视频的当前位置等于视频的总长度那么它应该
  • Android 工具栏:横向模式下的小标题文本

    我正在 Android 上测试新的 Toolbar 和 AppCompat 主题 但遇到了问题 我的工具栏标题文本在纵向模式下看起来是正常大小的 但在横向模式下它变得相当小 尽管我没有在代码中执行任何操作来更改标题的文本大小 以下是屏幕截图
  • audioTrack play() 只播放一次

    第一次使用Android的AudioTrack 我创建了一个 AndroidAudioDevice 类 我用这个构造函数初始化它 public AndroidAudioDevice constructor Log i Audio const
  • 如何在 Android 中嵌入和播放 YouTube 视频

    我们可以在 Android 应用程序中观看 YouTube 视频吗 我的意思是 如果我们有 YouTube 上的视频链接 我们可以在 VideoView 或其他小部件中播放它吗 有什么想法吗 答案很简单 是 请查看以下链接 如何在我的 An
  • 如何在屏幕上动态移动 Textview? (框架布局)

    我有一个应用程序 可以在屏幕上的 FrameLayout 上显示相机视图 屏幕处于固定风景模式 我需要编写一个带有动态确定的屏幕坐标的textView 坐标以百分比确定 例如 将文本视图写入屏幕坐标 x 80 y 20 屏幕上 将文本视图写
  • Android - GC 滞后于列表视图滚动“更大”的图像

    在列表视图中 我想在列表条目上绘制一个图像 这 20 张图像必须缩放以填充垂直模式的宽度 手机分辨率为 480 x 800 像素 SGS2 图像分辨率为 400x400 大小约为 100KB 我已将图像放在可绘制文件夹中 当我滚动列表时 它
  • Android 4.3 KeyStore - 尝试检索密钥时链== null

    下列的这个博客 http nelenkov blogspot de 2013 08 credential storage enhancements android 43 html 我使用此代码来创建和存储KeyPair在 Android 密
  • Android Facebook SDK - 无法接收访问令牌

    我正在尝试在我的 Android 应用程序中使用 Facebook SDK 这是片段 Facebook myFacebook new Facebook 123456789012345 myFacebook authorize LogInSc
  • 使用 Bixolon R200 进行打印的 Android 应用程序

    我正在开发一个 Android 应用程序 用于使用 Bixolon R200 进行打印 我在工作中使用 bxlprint jar 的类 当我想打印波斯字符串时 打印机打印奇怪的字符 我使用UTF 8和UTF 16进行编码 你能帮我吗 Str
  • 将 Android 应用程序与服务器上的 Matlab 应用程序连接

    我正在 Android 上开发一个应用程序 它将获取图像输入 并将该输入传递到安装 MATLAB 应用程序的服务器 MATLAB 应用程序将计算结果并将其返回到该 Android 应用程序 我想知道我可以使用哪个服务器 如何将 MATLAB
  • Nexus 10 (Android 4.4.2) 中未设置 FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

    我有一个主要活动 A 它将调用另一个活动 B 其 oncreate 如下所述 当我在活动 B 中按主页按钮并按最近使用的应用程序时 从未设置标志 FLAG ACTIVITY LAUNCCHED FROM HISTORY 我使用的是三星 Ne
  • 如何将数据一次性插入sqlite数据库

    我需要将数据添加到 sqlite 数据库一次 也就是说 我希望我的应用程序的用户看到该数据已加载 如何做到这一点 我使用查询执行了它 INSERT INTO TABLE NAME VALUES 值1 值2 值3 值N 但是每次应用程序打开该
  • 离子和电容器 - Android 启动画面响应能力

    Context 这与闪屏图像响应能力有关 根据我的研究 它之所以发生是因为缺少文档电容器文档 启动画面 https capacitorjs com docs apis splash screen Problem 当实现电容器的闪屏插件时 问
  • Android:RunOnUiThread 与 AsyncTask

    我相信 Google 建议开发人员使用 AsyncTask 但是 我想知道它与使用 new Thread 然后调用 RunOnUiThread 在性能和内存效率方面有何不同 使用 RunOnUithread 的示例 some code 1
  • Android studio - 如何使用 gradle 中的可执行 jar 文件

    所以我有一个 custom rules xml 文件 我试图在 gradle 中重建 到目前为止 我对其他所有事情都没有问题 但我试图完成的最后一部分是在我使用 gradle 构建过程生成的未签名 apk 上运行一个特殊的 apk 签名工具
  • registerForActivityResult TakePicture 未触发

    我尝试使用新的 registerForActivityResult 来拍照 我可以打开相机意图 但拍照后 未触发回调 并且我在 logcat 上看不到任何有关 Activity Result 或错误的信息 我也尝试了RequestPermi
  • 我可以启动 AndroidManifst.xml 文件中未指定的新活动吗

    我正在尝试执行一些动态代码 然后加载一个新类 该类应该是Activity我想启动它 但使用常规startActivity Intent 想要Activity在 AndroidManifest xml 文件中 声明 有什么线索吗 您无法启动未
  • 抱歉,该视频无法在视频视图中播放?

    freinds 我正在使用以下代码在我的应用程序中显示 mp4 视频 并面临以下问题 我在 google 和 stackoverflow 上看到了很多与这个问题相关的帖子 但每个人都给出了自己的建议 并且没有共同的答案 1 我在模拟器中看不
  • 如何消除按钮和其他视图之间的额外间隙?

    当我创建按钮视图时 Android 总是在该按钮与其下方的其他视图之间创建一些额外的空间 在下面的示例中 第二个按钮上方有一个按钮 您可以看到这两个按钮之间的间隙 我怎样才能摆脱这个差距 谢谢

随机推荐

  • C语言结构体中定义函数指针详解

    C语言结构体中定义函数指针详解 结构体指针函数应用场景之一 驱动程序编写 结构体的一些基本用法 形式 先定义结构体类型 再定义变量 形式 在定义类型的同时定义变量 形式 直接定义变量 用无名结构体直接定义变量只能一次 结构体指针在嵌入式Li
  • canvas绘制线条、矩形、曲线及填充

    1 绘制线条和填充 1 绘制线段的API是上下文对象的方法 2 beginPath 开始定义一条新的路径 3 moveTo 开始定义一条新的子路径 该方法确定了线段的起点 4 lineTo 将上面定义的线段起点和指定的新的点连接起来 5 到
  • Vue+element ui -- 自定义表单验证:金额

    在实际项目中 表单验证可以说非常普遍 尤其是财务系统项目和商城项目 涉及到金额的输入框更是很多 那么验证用户输入信息的争取与否 就变得至关重要 不单要做到准确还要照顾用户的输入习惯以及舒适度 这边笔记记录了原来我在项目中进行 金额 方面的自
  • xp系统电脑服务器端口,XP的电脑用CMD指令怎么开3389端口

    输入命令 REG ADD HKLM SYSTEM CurrentControlSet Control Terminal Server v fDenyTSConnections t REG DWORD d 00000000 f 如果用CMD命
  • MATLAB与深度学习(一)— Deep Learning Toolbox

    MATLAB与深度学习 一 Deep Learning Toolbox 最近 我在学习基于matlab的深度学习的内容 并整理出如下学习笔记 本文借鉴和引用了网上许多前辈的经验和代码 如有冒犯 请及时与我联系 1 MATLAB与深度学习的简
  • 使用ImageMagick为你的网站减重(转)

    片在网站所占的比重越来越重 更好的优化图片可以提高网站速度 减少宽带流量 1 对用户上传图片进行缩放 对于用户自己上传的图片不能简单的 用css限制大小 因为这样每次加载图片时候还是会加载整幅大图 占用多余的宽带 并且影响页面加载速度 应该
  • 安装程序无法自动安装Virtual Machine Communication Interface Sockets(VSock)驱动程序

    关于 VMware Tools安装时出现的问题的解决办法 安装时出现问题对话框 安装程序无法自动安装Virtual Machine Communication Interface Sockets VSock 驱动程序 必须手动安装此驱动程序
  • 动态场景下基于实例分割的SLAM(毕业设计开题及语义分割部分)

    动态场景下基于实例分割的SLAM 毕业论文设计思路及流水 前言 今年选了个比较难的毕设题目 这里记录一下自己思路和流程 为之后的学弟学妹 划掉 铺个方向 会按日期不定期的更新 一 开题 2019 12 24 考研前选择课题是 利用深度学习对
  • Linux ixgbe网卡(光模块)兼容性问题(没有网卡配置文件)

    Linux ixgbe网卡 光模块 兼容性问题 ixgbe光纤网卡的驱动在默认情况下不支持第三方兼容光模块 会导致网卡驱动加载失败 表现为 执行lspci grep 82599能看到网卡在pci设备中 06 00 0 Ethernet co
  • 剑指 Offer 15. 二进制中1的个数

    public class Solution you need to treat n as an unsigned value public int hammingWeight int n int div 1 int num 0 for in
  • 李宏毅 机器学习 2016 秋:3、Gradient Descent

    文章目录 三 Gradient Descent 3 1 Tuning your learning rates 3 2 Stochastic Gradient Descent 3 3 Feature Scaling 3 4 理论支持 三 Gr
  • 免费html5代码,HTML5(示例代码)

    一 什么是HTML5 1 1 HTML5 简介 万维网的核心语言 标准通用标记语言下的一个应用超文本标记语言 HTML 的第五次重大修改 作为新HTML语言 具有新的元素 属性和行为 XHTML可扩展超文本标记语言 是一种增强了的HTML
  • 微信小程序:动画效果集合

    Life is like riding a bicycle To keep your balance you must keep moving 生活就像骑自行车 为了保持平衡 你必须不断前进 微信小程序 心跳动画 https blog cs
  • 使用 appium 进行微信小程序的自动化测试

    目录 前言 微信小程序结构 自动化用例的调整 示例代码 后记 前言 微信小程序是一种流行的移动应用程序 它在移动设备上提供了丰富的功能和用户体验 为了确保微信小程序的质量和稳定性 自动化测试是必不可少的一环 Appium是一个强大的自动化测
  • assert断言(没有返回值,不需要console.log,断言未通过会抛出错误,通过不会抛出错误)

    1 assert value message 保证value是true就不会抛出错误 2 assert deepEqual actual expected message 表达式 1 表示测试 actual 参数与 expected 参数是
  • Custom numeric format strings

    string Multiplier Multiplier 0 Console WriteLine string Format Multiplier 1000000 string LiteralChar LiteralChar 0 000 C
  • vue3 图片路径转base64 base64转file(二进制一般后台需要格式) file转base64

    1 图片路径转64 function imageUrlToBase64 url let homeImage new Image 解决跨域问题 homeImage setAttribute crossOrigin anonymous home
  • 为什么前端监控要用GIF打点

    1背景 我们知道 目前主流的前端监控 百度统计 友盟 谷歌统计 都在用GIF进行打点 但是 为什么这些系统都会使用GIF 难道是因为没有其他的解决方案吗 这得从前端监控的原理说起 2前端监控的原理 所谓的前端监控 其实是在满足一定条件后 由
  • DataTable筛选出现异常

    异常详细信息 System ArgumentException 在 Range 对象中 Min 37 必须小于或等于 max 1 解决方法 转化字段类型为int 再次出现问题 HH gt 0 and HH lt 35 出来的数据序列为 1
  • Android Jetpack 之 DataStore

    1 概述 Google 推出了 JetPack 的新成员 DataStore DataStore 是一种新的数据存储方案 DataStore 以异步 一致的事务方式存储数据 克服了 SharedPreferences 的一些缺点 Jetpa