DataStore-SharedPreference知识点

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

概述

在安卓系统数据持久化的方案中,SharedPreferences一直处于很重要的位置,如果您有想要保存的相对较小键值对集合,则可以使用 SharedPreferences API。SharedPreferences 对象指向包含键值对的文件,并提供读写这些键值对的简单方法。每个 SharedPreferences 文件均由框架进行管理,可以是私有文件,也可以是共享文件。

而在后来,google又推出了DataStrore,DataStore 是一种现代数据存储解决方案,意在代替 SharedPreferences。它基于 Kotlin 协程和 Flow 构建,并克服了 SharedPreferences 的许多缺点。

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

  • Preferences DataStore 与 SharedPreferences 一样,根据键访问数据,而无需事先定义架构,也不确保类型安全。
  • Proto DataStore 使用 protocol-buffers 来定义架构。使用协议缓冲区可持久保留强类型数据,并且可以确保类型安全。与 XML 和其他类似的数据格式相比,协议缓冲区速度更快、规格更小、使用更简单,并且更清楚明了。虽然使用 Proto DataStore 需要学习新的序列化机制,但Google开发团队认为 Proto DataStore 有着强大的类型优势,值得学习。
功能SharedPreferencesPreferencesDataStoreProtoDataStore
异步 API✅(仅用于通过监听器读取已更改的值)✅(通过 Flow 以及 RxJava 2 和 3 Flowable)✅(通过 Flow 以及 RxJava 2 和 3 Flowable)
同步 API✅(但无法在界面线程上安全调用)❌❌
可在界面线程上安全调用❌✅(这项工作已在后台移至 Dispatchers.IO)✅(这项工作已在后台移至 Dispatchers.IO)
可以提示错误❌✅✅
不受运行时异常影响❌✅✅
包含一个具有强一致性保证的事务性 API❌✅✅
处理数据迁移❌✅✅
类型安全❌❌✅ 使用protocol-buffers

说明:

1、SharedPreferences 有一个看上去可以在界面线程中安全调用的同步 API,但是该 API 实际上执行磁盘 I/O 操作。此外,apply() 会阻断 fsync() 上的界面线程。每次有服务启动或停止以及每次 activity 在应用中的任何地方启动或停止时,系统都会触发待处理的 fsync() 调用。界面线程在 apply() 调度的待处理 fsync() 调用上会被阻断,这通常会导致 ANR。

2、SharedPreferences 会将解析错误作为运行时异常抛出。

ShreadPreferences

SharedPreferences 对象指向包含键值对的文件,并提供读写这些键值对的简单方法。每个 SharedPreferences 文件均由框架进行管理,可以是私有文件,也可以是共享文件。

获取SharedPreferences对象

获取SharedPreferences对象的方式有两个:

  • getSharedPreferences() - 如果您需要多个由名称(使用第一个参数指定)标识的SharedPreferences文件,则使用此方法。您可以从您的应用中的任何 Context 调用此方法。

        val sp: SharedPreferences = context.getSharedPreferences("名称", MODE_PRIVATE)
    

    为SharedPreferences文件命名时,应使用可唯一标识您的应用的名称。官方建议将应用 ID作为文件名的前缀。

  • getPreferences() - 对于 activity,如果您只需使用一个共享偏好设置文件,请从 Activity 中使用此方法。由于这会检索属于该 Activity 的默认共享偏好设置文件,因此您无需提供名称。

        val sp: SharedPreferences = getPreferences(MODE_PRIVATE)
    

    其实内部实现还是用的上面的,只不过自己生成了名称参数

        public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
            return getSharedPreferences(getLocalClassName(), mode);
        }
        
        @NonNull
        public String getLocalClassName() {
            final String pkg = getPackageName();
            final String cls = mComponent.getClassName();
            int packageLen = pkg.length();
            if (!cls.startsWith(pkg) || cls.length() <= packageLen
                    || cls.charAt(packageLen) != '.') {
                return cls;
            }
            return cls.substring(packageLen+1);
        }
    

**注意:**自 API 级别 17 起,MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE 模式已被废弃。从 **Android 7.0(API 级别 24)**开始,如果您使用这些模式,Android 会抛出 SecurityException。如果您的应用需要与其他应用共享私有文件,可以通过 FLAG_GRANT_READ_URI_PERMISSION 使用 FileProvider。

向SharedPreferences对象中写入值

写入值前,通过SharedPreferences 对象的edit()方法创建一个 SharedPreferences.Editor对象。然后使用此对象的 putInt() 或者 putString() 等方法写入的键和值。最后,调用 apply() 或 commit() 以保存更改。例如:

        //可以创建一个新的SharedPreference来对储存的文件进行操作
        val sp = context.getSharedPreferences("名称", MODE_PRIVATE)
        //像SharedPreference中写入数据需要使用Editor
        val editor: SharedPreferences.Editor = sp.edit()
        //类似键值对
        editor.putString("name", "string")
        editor.putInt("age", 0)
        editor.putBoolean("read", true)
        //editor.apply()
        editor.commit()

注意:

  • apply() 和 commit() 都是提交保存,区别在于apply是异步执行的,apply() 会立即更改内存中的 SharedPreferences 对象,但会将更新异步写入磁盘,不需要等待。commit是同步执行的,需要等待。但是由于 commit() 是同步的,您应避免从主线程调用它,因为它可能会暂停您的界面呈现。

  • 不论删除,修改,增加都必须调用apply或者commit提交保存。

  • 关于更新:如果已经插入的key已经存在。那么将更新原来的key的值。

  • 应用程序一旦卸载,SharedPreference也会被删除。

从SharedPreferences对象中读取值

通过SharedPreferences 对象,可以调用 getInt() 和 getString() 等方法去读取存储的值;如果键不存在,则可以选择返回默认值或者null。例如:

        val sp: SharedPreferences = context.getSharedPreferences("名称", MODE_PRIVATE) ?: return

        //第一个参数是键名,第二个是默认值
        val name: String = sp.getString("name", "暂无")
        val age: Int = sp.getInt("age", 0)
        val read: Boolean = sp.getBoolean("isRead", false)

也可以在读取值之前,检查下key值存不存在。

        val sp = context.getSharedPreferences("名称", MODE_PRIVATE)
        //检查当前键是否存在
        val isContains = sp.contains("key")
        //使用getAll可以返回所有可用的键值
        //val allMaps = sp.getAll()

从SharedPreferences对象中删除键值对

通过SharedPreferences 对象的edit()方法创建一个 SharedPreferences.Editor对象。然后使用此对象的 remove() 或者 clear() 等方法清除键值对。最后,调用 apply() 或 commit() 以保存更改。例如:

        val sp: SharedPreference = getSharedPreferences("名称", MODE_PRIVATE)
        val editor: SharedPrefence.Editor = sp.edit()
        //删除键值对
        editor.remove(key)
        editor.apply()
        //删除所有
        //editor.clear()
        //editor.commit()

注意:

当我们要清除SharedPreferences中的数据的时候不要直接去删除SharedPreferences对应的文件。因为SharedPreferences在应用启动后会被加载到内存里面,然后每次通过内存里面的内容覆盖这个文件的内容,如果删除掉文件后,再次执行commit()等方法,删除的文件会重生,重生文件的数据和删除之前的数据相同,因为内存里面的数据还是和之前一样的。除非删除文件后马上退出程序。

一切按照官方提供的API来操作就行了。

监听SharedPreferences对象中的键值变化

        sp.registerOnSharedPreferenceChangeListener { sharedPreferences, key ->
            if (key == "stringKey") {
                Log.d(TAG, "onCreate: key = $key   value = ${sharedPreferences.getString(key, "")}")
            }
        }

SharedPreferences的使用封装

为了更加方便的使用,我们将固定的代码封装起来,提供方法来简化操作:

object SharedPreferencesHelper {

    /**
     * 存入String值
     */
    fun put(context: Context, spName:String , key: String, value: String) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().putString(key, value).apply()
    }

    /**
     * 存入Int值
     */
    fun put(context: Context, spName:String , key: String, value: Int) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().putInt(key, value).apply()
    }

    /**
     * 存入Long值
     */
    fun put(context: Context, spName:String , key: String, value: Long) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().putLong(key, value).apply()
    }

    /**
     * 存入Float值
     */
    fun put(context: Context, spName:String , key: String, value: Float) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().putFloat(key, value).apply()
    }

    /**
     * 存入Boolean值
     */
    fun put(context: Context, spName:String , key: String, value: Boolean) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().putBoolean(key, value).apply()
    }

    /**
     * 存入Set<String?>值
     */
    fun put(context: Context, spName:String , key: String, values: Set<String?>) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().putStringSet(key, values).apply()
    }

    /**
     * 获取String值
     */
    fun getString(context: Context, spName:String , key: String, defaultValue: String): String {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).getString(key, defaultValue)?:defaultValue
    }

    /**
     * 获取Int值
     */
    fun getInt(context: Context, spName:String , key: String, defaultValue: Int): Int {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).getInt(key, defaultValue)
    }

    /**
     * 获取Long值
     */
    fun getLong(context: Context, spName:String , key: String, defaultValue: Long): Long {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).getLong(key, defaultValue)
    }

    /**
     * 获取Float值
     */
    fun getFloat(context: Context, spName:String , key: String, defaultValue: Float): Float {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).getFloat(key, defaultValue)
    }

    /**
     * 获取Boolean值
     */
    fun getBoolean(context: Context, spName:String , key: String, defaultValue: Boolean): Boolean {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).getBoolean(key, defaultValue)
    }

    /**
     * 获取Set<String?>值
     */
    fun getStringSet(context: Context, spName:String , key: String, defaultValue: Set<String?>): Set<String>? {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).getStringSet(key, defaultValue)
    }

    /**
     * 判断是否包含key值
     */
    fun contains(context: Context, spName:String , key: String): Boolean {
        return context.getSharedPreferences(spName, Context.MODE_PRIVATE).contains(key)
    }

    /**
     * 移除key值及对应的value值
     */
    fun remove(context: Context, spName:String , key: String) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().remove(key).apply()
    }

    /**
     * 清空sp中的数据
     */
    fun clear(context: Context, spName:String) {
        context.getSharedPreferences(spName, Context.MODE_PRIVATE).edit().clear().apply()
    }
}

如果你熟悉Kotlin的扩展的话,还可以进一步封装,例如:

fun Context.saveIntIntoSharedPreferences(spName: String, key: String, value: String) {
    SharedPreferencesHelper.put(this, spName, key, value)
}

DataStore

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

如果您目前是使用 SharedPreferences 存储数据的,请考虑迁移到 DataStore。

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

  • Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用 protocol-buffers 来定义架构,但可以确保类型安全。

Preferences DataStore

官方示例代码

添加依赖

    // Preferences DataStore (SharedPreferences like APIs)
    dependencies {
        implementation "androidx.datastore:datastore-preferences:1.1.2"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-preferences-rxjava2:1.1.2"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-preferences-rxjava3:1.1.2"
    }

    // 或者 使用不依赖Android sdk的库。
    dependencies {
        implementation "androidx.datastore:datastore-preferences-core:1.1.2"
    }

获取DataStore <Preferences>对象

我们可以用属性委托的方式创建**DataStore<Preferences>**的实例,如下面的代码。比较推荐的方法是在Kotlin文件顶层创建实例一次,便可在应用的所有其余部分通过此属性访问该实例。这样可以更轻松地将 DataStore 保留为单例。如果使用的是 RxJava,请使用 RxPreferenceDataStoreBuilder。必需的 name 参数是 Preferences DataStore 的名称。

	// At the top level of your kotlin file:
	val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

向DataStore <Preferences>对象中写入值

DataStore <Preferences>对象提供了一个 edit() 函数,用于以事务方式更新 DataStore 中的数据。该函数的 transform 参数接受代码块,您可以在其中根据需要更新值。转换块中的所有代码均被视为单个事务。然后为了读取内容,我们需要根据内容的属性使用特定的Key。下列代码是当内容为String的情况下,需要调用stringPreferencesKey方法。

    //定义要操作的key
    private val key = stringPreferencesKey("keyName")

定义完key之后,我们就可以通过这个key去存对应的值。

    runBlocking {
        dataStore.edit { it[key] = "疫情" }
    }

相应的其他类型的:

    private val intKey = intPreferencesKey("name")
    private val doubleKey = doublePreferencesKey("name")
    private val stringKey = stringPreferencesKey("name")
    private val booleanKey = booleanPreferencesKey("name")
    private val floatKey = floatPreferencesKey("name")
    private val longKey = longPreferencesKey("name")
    private val stringSetKey = stringSetPreferencesKey("name")

从DataStore <Preferences>对象中读取值

由于 Preferences DataStore 不使用预定义的架构,因此您必须使用相应的键类型函数为需要存储在 DataStore <Preferences>对象中的每个值定义一个键。然后,使用 DataStore.data 属性,通过 Flow 提供适当的存储值。

        val key = stringPreferencesKey("keyName")
        val stringFlow:Flow<String?> = dataStore.data.map { it[key] }
        runBlocking {
            val stringValue = stringFlow.first()
        }

并且在此基础上面还提供了读取异常的监听方式

        val key = stringPreferencesKey("keyName")
        return dataStore.data
            .catch { exception ->
                exception.printStackTrace()
            }
            .map {
            it[key]
        }.first()

从DataStore <Preferences>对象中删除键值对

同样提供了清除键值对的方式

        val key = stringPreferencesKey("keyName")
        runBlocking {
            dataStore.edit {
                it.remove(key)
                //清除所有
                it.clear()
            }
        }

监听DataStore <Preferences>对象中键值对变化

本身返回的已经是个Flow对象了,Flow对象本身就支持监听。

DataStore的使用封装

为了更加方便的使用,我们将固定的代码封装起来,提供方法来简化操作:

object DataStorePreferencesHelper {

    private val Context.dataStore: DataStore<Preferences> by androidx.datastore.preferences.preferencesDataStore(
        name = "settings"
    )

    suspend fun put(context: Context, key: String, value: String) {
        context.dataStore.edit {
            it[stringPreferencesKey(key)] = value
        }
    }

    suspend fun put(context: Context, key: String, value: Int) {
        context.dataStore.edit {
            it[intPreferencesKey(key)] = value
        }
    }

    suspend fun put(context: Context, key: String, value: Long) {
        context.dataStore.edit {
            it[longPreferencesKey(key)] = value
        }
    }

    suspend fun put(context: Context, key: String, value: Float) {
        context.dataStore.edit {
            it[floatPreferencesKey(key)] = value
        }
    }

    suspend fun put(context: Context, key: String, value: Boolean) {
        context.dataStore.edit {
            it[booleanPreferencesKey(key)] = value
        }
    }

    suspend fun put(context: Context, key: String, value: Set<String>) {
        context.dataStore.edit {
            it[stringSetPreferencesKey(key)] = value
        }
    }

    suspend fun getString(context: Context, key: String, defaultValue: String): String {
        return context.dataStore.data.map { it[stringPreferencesKey(key)] }.first() ?: defaultValue
    }

    suspend fun getInt(context: Context, key: String, defaultValue: Int): Int {
        return context.dataStore.data.map { it[intPreferencesKey(key)] }.first() ?: defaultValue
    }

    suspend fun getLong(context: Context, key: String, defaultValue: Long): Long {
        return context.dataStore.data.map { it[longPreferencesKey(key)] }.first() ?: defaultValue
    }

    suspend fun getFloat(context: Context, key: String, defaultValue: Float): Float {
        return context.dataStore.data.map { it[floatPreferencesKey(key)] }.first() ?: defaultValue
    }

    suspend fun getBoolean(context: Context, key: String, defaultValue: Boolean): Boolean {
        return context.dataStore.data.map { it[booleanPreferencesKey(key)] }.first() ?: defaultValue
    }

    suspend fun getStringSet(
        context: Context,
        spName: String,
        key: String,
        defaultValue: Set<String>
    ): Set<String> {
        return context.dataStore.data.map { it[stringSetPreferencesKey(key)] }.first() ?: defaultValue
    }

    suspend fun <T> contains(context: Context, key: Preferences.Key<T>): Boolean {
        return context.dataStore.data.map { it.contains(key) }.first()
    }

    suspend fun <T> remove(context: Context, key: Preferences.Key<T>) {
        context.dataStore.edit {
            it.remove(key)
        }
    }

    suspend fun clear(context: Context, spName: String) {
        context.dataStore.edit {
            it.clear()
        }
    }
}

从 SharedPreferences 迁移到 Preferences DataStore

DataStore 构建器提供了produceMigrations参数可以传入 SharedPreferencesMigration。DataStore 借此能够自动从 SharedPreferences 迁移到 DataStore。迁移需在 DataStore 中的任何数据访问操作发生之前运行。这意味着,必须在 DataStore.data 发出任何值之前和 DataStore.edit() 可以更新数据之前,成功完成迁移。

    private val Context.dataStoreBySp: DataStore<Preferences> by preferencesDataStore(name = "settings", produceMigrations = { context ->
        listOf(SharedPreferencesMigration(context,"spName"))
    })

**注意:**由于键只能从 SharedPreferences 迁移一次,因此在代码迁移到 DataStore 之后,您应停止使用旧 SharedPreferences。

Proto DataStore

Proto DataStore 实现使用 DataStore 和 protocol-buffers 将类型化对象保留在磁盘上。

官方示例代码

添加依赖

    // Typed DataStore (Typed API surface, such as Proto)
    dependencies {
        implementation "androidx.datastore:datastore:1.1.2"

        // optional - RxJava2 support
        implementation "androidx.datastore:datastore-rxjava2:1.1.2"

        // optional - RxJava3 support
        implementation "androidx.datastore:datastore-rxjava3:1.1.2"
    }

    // 或者 使用不依赖Android sdk的库。
    dependencies {
        implementation "androidx.datastore:datastore-core:1.1.2"
    }

等等,不要慌,使用这个功能不是单单添加依赖就可以用的。还需要添加 protocol-buffers 的相关内容。

添加插件:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.1'
    }
}

使用插件

plugins {
    id 'com.google.protobuf'
}

或者不添加插件,直接这么写
plugins {
    ...
    id "com.google.protobuf" version "0.8.17"
}

然后添加对应的依赖:

    implementation  'androidx.datastore:datastore-core:1.0.0'
    implementation  'com.google.protobuf:protobuf-javalite:3.10.0'

最后配置protobuf

dependencies {
    ...

    implementation  'androidx.datastore:datastore-core:1.0.0'
    implementation  'com.google.protobuf:protobuf-javalite:3.10.0'


}

protobuf {
    protoc {
    		//m1电脑适配环境
        if (osdetector.os == "osx") {
            artifact = "com.google.protobuf:protoc:3.10.0:osx-x86_64"
        } else {
            artifact = "com.google.protobuf:protoc:3.10.0"
        }
    }

    // 为该项目中的 Protobufs 生成 java Protobuf-lite 代码。
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}

这样, protocol-buffers 的基本环境就搭建好了。接下来就是定义我们的数据了。

在当前module的 mian 目录下新建 proto 文件夹,再新建 settings.proto 文件(这里需要下载一个插件,自己去插件市场搜protobuf关键字,下载用户量最多的就行了)。在文件中写入:

//声明协议, 也支持 prota2,普遍使用proto3
syntax = "proto3";

//设置生成的类所在的包
option java_package = "cc.fastcv.datastore.proto";
//可能会有多个文件。
option java_multiple_files = true;

message PersonPreferences {
    reserved 4;
    string name = 1;
    int32 age = 2;
    string address = 3;
    Config config = 5;
}

message Config {
    repeated string index = 1;
}

然后直接 make project 就生成了我们需要的代码。切记,要构建下我们的项目才行。
请添加图片描述

接下来的所有操作都是围绕这个进行的。

获取DataStore<T>对象

    private val Context.settingsStore: DataStore<PersonPreferences> by dataStore(
        fileName = "settings.pb",
        serializer = PersonSerializer
    )

这里需要一个serializer参数,实现为:

object PersonSerializer : Serializer<PersonPreferences> {

    override val defaultValue: PersonPreferences = PersonPreferences.getDefaultInstance()

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

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

简单来说,就是需要我们自己来实现下序列化中的数据写入与读取。

向DataStore<T>对象中写入值

        runBlocking {
            settingsStore.updateData {
                it.toBuilder()
                    .setName("张三")
                    .setAge(18)
                    .setAddress("这是一个地址")
                    .setConfig(
                        Config.getDefaultInstance().toBuilder().addIndex("a")
                            .addIndex("b").addIndex("c").build()
                    )
                    .build()
            }
        }

从DataStore<T>对象中读取值

        runBlocking {
            val person = settingsStore.data.first()
            Log.d(TAG, "onCreate: person = $person")
        }

从DataStore<T>对象中删除键值对

与基本类型不同的是,它无法清除数据。

监听DataStore <T>对象中键值对变化

本身返回的已经是个Flow对象了,Flow对象本身就支持监听。

从 SharedPreferences 迁移到 Proto DataStore

DataStore 构建器提供了produceMigrations参数可以传入 SharedPreferencesMigration。DataStore 借此能够自动从 SharedPreferences 迁移到 DataStore。迁移需在 DataStore 中的任何数据访问操作发生之前运行。这意味着,必须在 DataStore.data 发出任何值之前和 DataStore.edit() 可以更新数据之前,成功完成迁移。

但是我目前没有想到这种使用场景。

    private val Context.settingsStore: DataStore<PersonPreferences> by dataStore(
        fileName = "settings.pb",
        serializer = PersonSerializer,
        produceMigrations = { _ ->
            listOf(SharedPreferencesMigration( produceSharedPreferences = {
                getPreferences(MODE_PRIVATE)
            }) { sharedPrefs: SharedPreferencesView, currentData: PersonPreferences ->
                currentData.toBuilder()
                        .setName("张三")
                        .setAge(sharedPrefs.getInt("keyAge",18))
                        .setAddress("这是一个地址")
                        .setConfig(
                            Config.getDefaultInstance().toBuilder().addIndex("a")
                                .addIndex("b").addIndex("c").build()
                        )
                        .build()
            })
        }
    )

**注意:**由于键只能从 SharedPreferences 迁移一次,因此在代码迁移到 DataStore 之后,您应停止使用旧 SharedPreferences。

在多进程代码中使用 DataStore

注意:DataStore 多进程功能目前在 1.1.0 版中提供。

不同的地方就在构造方式

    val dataStore: DataStore<PersonPreferences> = MultiProcessDataStoreFactory.create(
        serializer = PersonSerializer,
        produceFile = {
            File("${context.cacheDir.path}/myapp.preferences_pb")
        }
    )

处理存储文件损坏

在极少数情况下,DataStore 的持久性磁盘文件可能会损坏。默认情况下,DataStore 不会自动从损坏中恢复,并且尝试从中读取会导致系统抛出 CorruptionException。

DataStore 提供了一个损坏处理程序 API,可帮助您在这种情况下妥善恢复,并避免抛出异常。配置后,损坏处理程序会将损坏的文件替换为包含预定义默认值的新文件。

如需设置此处理脚本,请在 by dataStore() 或 DataStoreFactory 工厂方法中创建 DataStore 实例时提供 corruptionHandler:

    val dataStore: DataStore<PersonPreferences> = DataStoreFactory.create(
        serializer = PersonSerializer,
        produceFile = {
            File("${context.cacheDir.path}/myapp.preferences_pb")
        },
        corruptionHandler = ReplaceFileCorruptionHandler { PersonPreferences() }
    )

注意点

  • 请勿在同一进程中为给定文件创建多个 DataStore 实例,否则会破坏所有 DataStore 功能。如果给定文件在同一进程中有多个有效的 DataStore 实例,DataStore 在读取或更新数据时将抛出 IllegalStateException。
  • DataStore 的通用类型必须不可变。更改 DataStore 中使用的类型会导致 DataStore 提供的所有保证都失效,并且可能会造成严重的、难以发现的 bug。强烈建议您使用可保证不可变性、具有简单的 API 且能够高效进行序列化的 protocol-buffers 。
  • 切勿对同一个文件混用 SingleProcessDataStore 和 MultiProcessDataStore。如果您打算从多个进程访问 DataStore,请始终使用 MultiProcessDataStore。
  • 不一定非要使用 protocol-buffers ,哈哈哈,我也是最后才发现。只要能提供serializer的实现类就行了。这里官方通过 protocol-buffers 来保证序列化的正确性,保证了类型安全。你完全可以使用gson实现。
Android Jetpack组件 DataStore的使用和简单封装,安卓面试 下面我们再写一个取数据的方法。: “新冠” }.first()你会发现和存数据又有不同,这里的first()就是取值,这个方法换个方式来看就清晰一些。然后我们在取数据按钮的点击事件中调用。下面我们运行一下:第一次我先取数据,显示的是默认值,然后我存数据再取数据。效果就是这样,但你会觉得使用起来很麻烦,不如SP好用,这个我们后面再去封装,先了解一些它的功能特性。三、数据查看和清除在进行定义dataStore时,会在手机中生成一个pb文件,这里我们用虚拟机来看,然后通过你的程序包名去找。 阅读详情

相关推荐

Android DataStore:安全存储和轻松管理数据

关于作者:优快云内容合伙人、技术专家, 从零开始做日活千万级APP。专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

2301_82000445的博客 1465

DataStore详解

项目中使用SharedPreference时还是要注意。不能用来替代SharedPreference。整个对象都将被序列化并持久化到磁盘。一致的事务方式存储数据。事务方式处理更新数据。

听海的博客 3990

Jetpack的DataStore数据存储组件的使用简介

Jetpack的DataStore数据存储组件的使用简介 介绍 Jetpack DataStore是一种数据存储解决方案,可让您使用Protocol Buffer(协议缓冲区)存储键值对或类型化对象。DataStore使用Kotlin协程和Flow异步,一致的事务方式来存储数据。 注意: 如果需要支持大型或复杂的数据集,部分更新或参考完整性,请考虑使用 Room 而不是DataStore。DataStore非常适合小型,简单的数据集,并且不支持部分更新或参照完整性。 目的 Jetpack DataSto

weixin_42324979的博客 1115

DataStore的基础用法

0. 简介 Google在推出JetPack组件以来,一直推荐我们使用DataStore组件替代到我们第一天学android就知道的SharedPreferences组件,原因很简单,因为当年的SharedPreferences存在居多的问题,DataStore就是为了解决这些问题而来的。 1. SP的缺点 至于 SP到底存在哪些问题,我们可以直接查看 DataStore源码上的注释: Synchronous API encourages StrictMode violations apply() an

成长的烧年 7862

Preferences DataStore全解析

DataStore 是用来取代的一种简易数据存储的解决方案。下面我们来看看如何使用。

不近视的猫的博客 1280

智能气象站:ESP32驱动的心知天气数据可视化实践

本文详细介绍了基于ESP32微控制器和心知天气API构建智能气象站的完整实践。通过Arduino开发环境,实现天气数据的获取、JSON解析与OLED屏可视化展示,涵盖硬件连接、API配置、低功耗优化及部署问题排查,为物联网开发者提供实用指南。

jump7的博客 210

Android Datastore 动态创建与源码解析

1、文件的写入是发生在IO层面2、使用runBlocking是会卡主线程,如果此时存在需要刷新UI的情况,严重会ANR/*** @key 参数* @value 具体的值*///更新逻辑update.ack.completeWith(//通知结果回调//.....省去//transform 就是上面的{}里面的内容return ack.await() //这里就是等待 update.ack.completeWith的结果返回,所以如果不加这行,是不会卡主线程的。

一个冬季的专栏 763

强化学习入门8—深入理解DDPG

本文是强化学习入门系列的第八篇,前面我们讲Actor-Critic时提到了DDPG。DDPG是google DeepMind团队提出的一种用于输出确定性动作的算法,它解决了Actor-Critic 神经网络每次参数更新前后都存在相关性,导致神经网络只能片面的看待问题这一缺点。同时也解决了DQN不能用于连续性动作的缺点。

小菜羊的博客 4314

Android - 键值对存储 SharedPreference、DataStore、MMKV

以键值对的方式保存少量的数据,通常用来存数程序的一些配置信息,如是否打开音效、是否使用震动效果、窗口状态、是否进入过向导界面等。

HugMua的博客 988

多普勒效应及多普勒频移的简单推导

多普勒频移的简单推导

m0_47867419的博客 3万+

从零开始:如何在STM32上搭建QP框架实现事件驱动开发(附完整代码)

本文详细介绍了如何在STM32嵌入式平台上从零开始搭建QP框架,实现事件驱动开发。通过对比传统前后台系统与RTOS的局限,深入剖析了QP框架基于活动对象和层次式状态机的核心优势,并提供了完整的环境配置、代码移植及一个智能灯光控制器的实战案例,帮助开发者构建清晰、可维护的复杂嵌入式应用。

weixin_29200741的博客 487

Android Jetpack 之 DataStore

Google 推出了 JetPack 的新成员 DataStore,DataStore 是一种新的数据存储方案。DataStore以异步、一致的事务方式存储数据,克服了 SharedPreferences 的一些缺点。 Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和流程构建而成,提供两种不同的实现: Proto DataStore,它允许您存储类型化的对象(由协议缓冲区提供支持) Pre

接受自己的普通,然后拼尽全力去与众不同 1万+

【Block总结】DiffAttention,差分注意力融入CLIP架构,抑制噪声,增强对相关特征的关注|即插即用

DiffCLIP通过微小的结构改动实现了显著的性能提升,为多模态模型的轻量化与高效化提供了新范式。

AI浩 1561

【Jectpack】DataStore

dataStore 是jectpack推出的轻量化存储组件,包含PreferenceDataStore和ProtoDataStore两种方式,基于Flow()实现数据存储 Why To Use 之前的Android轻量化存储组件SharedPreferences读写操作会阻塞主线程,在数据量不多的情况下没什么影响,但随着对组件的滥用,存储的数据量逐渐增大,性能问题逐渐显现,官方也挣扎着出了apply()异步化方案,希望挽救SharedPreferences于水火,但可惜apply()并未实现真正意义上的.

健忘的鱼的博客 2806

C#串口通信革命:RJCP.DLL.SerialPortStream实战全解析

本文介绍了如何通过RJCP.DLL.SerialPortStream库实现高性能串口通信解决方案。相比.NET自带的SerialPort类,该库提供了完全的异步非阻塞IO、动态缓冲区控制和多线程事件驱动等优势,特别适合工业自动化和物联网场景。文章详细讲解了环境搭建、核心功能对比,并通过代码示例展示了端口配置、异步通信管道搭建等关键技术。最后以Modbus RTU协议为例,演示了高级协议封装方法,为开发者提供了完整的工业级串口通信实现方案。

墨夶的博客 1871

大疆 2021 嵌入式笔试题

在深圳做嵌入式,大疆公司绝对是 Top 级别的,大疆的技术栈也很深。

qq_38769551的博客 2496

Jetpack架构组件库:DataStore

Jetpack DataStore 是一种经过改进的新数据存储解决方案,旨在取代 SharedPreferences。DataStore 基于 Kotlin 协程和 Flow 构建而成,提供以下两种不同的实现: Preferences DataStore 用于键值对存储。数据以异步、一致和事务性的方式存储,有助于避免 SharedPreferences 的一些缺点。此实现不需要预定义的架构,也不确保类型安全。 Proto DataStore 用于存储类型化对象,数据作为自定义数据类型的实例进行存储。此实现

川峰的博客 6万+

3.1 快速启动Flink集群

在本实战中,我们将快速启动Apache Flink 1.13.0集群,并在Hadoop集群环境中提交作业。首先,我们会在本地下载Flink安装包并上传至云主机,解压并配置环境变量。接着,我们将启动Flink集群,通过访问Web UI进行监控管理。然后,我们会配置集群,将Flink分发到从节点,并在集群上提交作业。最后,我们将通过命令行提交作业,并在Web UI上查看任务节点的输出结果。这个过程涵盖了Flink集群的部署、配置和作业提交,为大数据处理提供了一个完整的实战经验。

howard2005的专栏 727

Android DataStore用法介绍

Android DataStore用法介绍 DataStore是Android Jetpack中的一个组件,它是一个数据存储的解决方案,跟SharedPreferences一样,采用key-value形式存储。DataStore支持Kotlin协程和Flow操作,如果当前使用SharedPreferences,可以考虑迁移到DataStore。Jetpack DataStore 是经过改进的新版数据存储解决方案,旨在取代 SharedPreferences,让您的应用能够以异步、事务方式存储数据。跟Shar

淡了点 1万+

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Jetpack DataStore 你总要了解一下吧?

DataStore是 Android Jetpack 中的一个组件,它是一个数据存储的解决方案,跟SharedPreferences一样,采用key-value形式存储。

帅次的博客 3051

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

一文带你玩转 DataStore

缘起 SharedPreferences 说起 SharedPreferences(下面简称 SP),只要是安卓开发都不会陌生的,平时开发都离不开,不过它确实很方便,以键值对的形式存储在本地,使用非常简单: val sp = getSharedPreferences("Test", Context.MODE_PRIVATE) sp.edit { putString("jetPack", "text") } val jetPack = sp.getString("jetpack", "") 只需要上面几行代

Zhujiang 5384

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Jetpack DataStore

Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。 DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。 Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。 Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现

xiangxiongfly 1204

DataStore入门及在项目中的使用

新手踩坑DataStore,及在项目中的运用,需要了解kotlin的扩展和属性委托,才能看懂官网的代码

qq_26014653的博客 1185

【Android Jetpack】DataStore

目录1. 前言2. 分类2.1 Preferences DataStore 和SharedPreferences的区别3. 实践3.1 Preferences DataStore3.1.1 依赖3.1.2 案例3.1.3 最后3.2 Proto DataStore3.2.1 依赖3.2.1.1 添加协议缓冲区插件3.2.1.2 配置协议缓冲区3.2.2 定义架构3.2.3 简单使用3.2.3.1 创建序列化器3.2.3.2 数据存储和读取 1. 前言 Jetpack DataStore 是一种数据存储解决方

梦否 2371

JetPack之DataStore源码笔记

文章目录1. Preferences DataStore1.1. Preferences DataStore的创建1.2. Preferences DataStore的写入1.2.1. 迁移SharedPreference过程 上一篇文章分析了SharedPreferences的原理和弊端,这篇文章主要分析DataStore的源码。根据官方文档的介绍,DataStore主要有一下优势: 支持键值对和类型化对象的存储; 数据存储采用protocol buffers,更节省资源; 使用Kotlin协程以及Fl

weixin_40888127的博客 1659

Android几种数据存储的对比(MMKV、DataStore、SharedPreference)

Jetpack DataStore 是一种改进的新数据存储解决方案,允许使用协议缓冲区存储键值对或类型化对象。DataStore 以异步、一致的事务方式存储数据,克服了 SharedPreferences(以下统称为SP)的一些缺点。DataStore基于Kotlin协程和Flow实现,并且可以对SP数据进行迁移,旨在取代SP。MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。

张雨的博客 3027
上一篇: 自己封装安卓日志打印库
下一篇: 记一次有趣的时钟效果复刻实现过程
fastcv
博客等级 码龄10年 90粉丝 5原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值