DataStore-SharedPreference知识点

概述

在安卓系统数据持久化的方案中,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 有着强大的类型优势,值得学习。
功能 SharedPreferences PreferencesDataStore ProtoDataStore
异步 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 调用此方法。

    ini 复制代码
        val sp: SharedPreferences = context.getSharedPreferences("名称", MODE_PRIVATE)

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

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

    ini 复制代码
        val sp: SharedPreferences = getPreferences(MODE_PRIVATE)

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

    java 复制代码
        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_READABLEMODE_WORLD_WRITEABLE 模式已被废弃。从 **Android 7.0(API 级别 24)**开始,如果您使用这些模式,Android 会抛出 SecurityException。如果您的应用需要与其他应用共享私有文件,可以通过 FLAG_GRANT_READ_URI_PERMISSION 使用 FileProvider

向SharedPreferences对象中写入值

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

java 复制代码
        //可以创建一个新的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。例如:

java 复制代码
        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值存不存在。

java 复制代码
        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() 以保存更改。例如:

java 复制代码
        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对象中的键值变化

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

SharedPreferences的使用封装

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

kotlin 复制代码
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的扩展的话,还可以进一步封装,例如:

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

官方示例代码

添加依赖

arduino 复制代码
    // 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 的名称。

kotlin 复制代码
	// 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方法。

kotlin 复制代码
    //定义要操作的key
    private val key = stringPreferencesKey("keyName")

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

kotlin 复制代码
    runBlocking {
        dataStore.edit { it[key] = "疫情" }
    }

相应的其他类型的:

kotlin 复制代码
    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 提供适当的存储值。

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

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

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

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

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

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

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

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

DataStore的使用封装

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

kotlin 复制代码
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() 可以更新数据之前,成功完成迁移。

kotlin 复制代码
    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 将类型化对象保留在磁盘上。

官方示例代码

添加依赖

arduino 复制代码
    // 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 的相关内容。

添加插件:

arduino 复制代码
buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.1'
    }
}

使用插件

bash 复制代码
plugins {
    id 'com.google.protobuf'
}

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

然后添加对应的依赖:

arduino 复制代码
    implementation  'androidx.datastore:datastore-core:1.0.0'
    implementation  'com.google.protobuf:protobuf-javalite:3.10.0'

最后配置protobuf

rust 复制代码
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关键字,下载用户量最多的就行了)。在文件中写入:

ini 复制代码
//声明协议, 也支持 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>对象

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

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

kotlin 复制代码
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>对象中写入值

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

从DataStore<T>对象中读取值

ini 复制代码
        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() 可以更新数据之前,成功完成迁移。

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

css 复制代码
    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 版中提供。

不同的地方就在构造方式

ini 复制代码
    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

ini 复制代码
    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实现。
相关推荐
敲上瘾32 分钟前
MySQL主从集群解析:从原理到Docker实战部署
android·数据库·分布式·mysql·docker·数据库架构
Jomurphys44 分钟前
测试 - 单元测试(JUnit)
android·junit·单元测试
fatiaozhang95271 小时前
中国移动中兴云电脑W132D-RK3528-2+32G_安卓9_ADB开启线刷包
android·adb·电脑·电视盒子·刷机固件·机顶盒刷机·中兴云电脑w132d
selt79110 小时前
Redisson之RedissonLock源码完全解析
android·java·javascript
Yao_YongChao11 小时前
Android MVI处理副作用(Side Effect)
android·mvi·mvi副作用
非凡ghost12 小时前
JRiver Media Center(媒体管理软件)
android·学习·智能手机·媒体·软件需求
席卷全城12 小时前
Android 推箱子实现(引流文章)
android
齊家治國平天下12 小时前
Android 14 系统中 Tombstone 深度分析与解决指南
android·crash·系统服务·tombstone·android 14
maycho12314 小时前
MATLAB环境下基于双向长短时记忆网络的时间序列预测探索
android
思成不止于此15 小时前
【MySQL 零基础入门】MySQL 函数精讲(二):日期函数与流程控制函数篇
android·数据库·笔记·sql·学习·mysql