Aprendizaje de DataStore bajo JetPack

1. Introducción a DataStore

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

2. Por qué usar DataStore, las desventajas de SharedPreferences

Hablando de SharedPreferences, todos los desarrolladores de Android están familiarizados con él. Se almacena localmente en forma de pares clave-valor, lo cual es muy fácil de usar. Pero también a menudo encontramos algunos problemas en el proyecto:

1.getXXX(),会方法可能会导致主线程阻塞,在主线程调用 get 方法,必须等待 SP 加载完毕,会导致主线程阻塞

2.SP 不能保证类型安全,可能会出现 ClassCastException 异常,因为使用相同的 key 进行操作的时候,putXXX 方法可以使用不同类型的数据覆盖掉相同的 key。

3.SP 加载的数据会一直留在内存中,通过 getSharedPreferences() 方法加载的数据,最后会将数据存储在静态的成员变量中。

4.apply() 方法是异步的,可能会发生 ANR,
apply() 方法是异步的,本身是不会有任何问题,但是当生命周期处于  handleStopService() 、 handlePauseActivity() 、 handleStopActivity()  的时候会一直等待 apply() 方法将数据保存成功,否则会一直等待,从而阻塞主线程造成 ANR

5.SP 不能用于跨进程通信

En comparación con SharedPreferences, DataStore tiene las siguientes ventajas

1. DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
2. 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
3. 没有 apply() 和 commit() 等等数据持久的方法
4. 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
5. 可以监听到操作成功或者失败结果

Echemos un vistazo a una imagen de la diferencia entre SharedPreferences y DataStore en Google Analytics:

3. Uso de DataStore

Hay dos formas de implementar Jetpack DataStore:

(1)Proto DataStore:存储类的对象(typed objects ),通过 protocol buffers 将对象序列化存储在本地 (2)Preferences DataStore:以键值对的形式存储在本地和 SharedPreferences 类似

Al mismo tiempo, Preferences DataStore solo admite datos de pares clave-valor Int , Long , Boolean , Float , String , adecuados para almacenar datos simples y pequeños, y no admite actualizaciones parciales. Si se modifica uno de los valores, Se volverá a serializar todo el contenido del archivo.

Usar Preferences DataStore en el proyecto
1.需要添加 Preferences DataStore 依赖
    // Preferences DataStore
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha01"

2. 构建 DataStore
    private val PREFERENCE_NAME = "DataStore"
    var dataStore: DataStore<Preferences> = createDataStore(
        name = PREFERENCE_NAME
    )
3.从Preferences中存或读取数据
    suspend fun saveDataLong(key: Preferences.Key<Long>, value: Long) {
        dataStore.edit { mutablePreferences ->
            mutablePreferences[key] = value
        }
    }

    fun getDataLong(key: Preferences.Key<Long>): Flow<Long?> =
        dataStore.data.catch {
            if (it is IOException) {
                it.printStackTrace()
                emit(emptyPreferences())
            } else {
                throw it
            }
        }.map {
            it[key] ?: -1L
        }

    fun getDataLongSyn(key: Preferences.Key<Long>): Long {
        var value = -1L
        runBlocking {
            dataStore.data.first {
                value = it[key] ?: -1
                true
            }
        }
        return value
    }

4.迁移 SP 数据到 DataStore
我们来看看createDataStore的构造方法
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
    )
分析下构造方法中的几个参数
1. name:这个没啥好说的,就是 DataStore 的名字
2. corruptionHandler:如果数据存储在尝试读取数据时遇到 CorruptionException,则调用corruptionHandler。当数据无法反序列化时,序列化程序将引发CorruptionException
3. migrations:这个参数就是用来迁移 SP 的
4. scope:这个参数协程的作用域

迁移 SharedPreferences 到 DataStore 
(1)传入一个SharedPreferencesMigration对象
(2)当 DataStore 对象构建完了之后,需要执行一次读取或者写入操作,即可完成 SharedPreferences 迁移到 DataStore,当迁移成功之后,会自动删除 SharedPreferences 使用的文件
有一点需要注意的是:迁移成功后会删除sp对应的xml文件,应该立即停止使用sp.
dataStore = context.createDataStore(
    name = preferenceName,
    migrations = listOf(
        SharedPreferencesMigration(
            context,
            "你存储 SP 的 Name"
        )
    )
)

4. Acerca del acceso asincrónico a los valores

Primero mira un ejemplo de demostración

    fun saveData() {
        GlobalScope.launch {
            saveDataInt(ketInt, 4)
            saveDataLong(keyLong, 5)
            saveDataString(keyString, "charles")
            saveDataBoolean(keyBoolean, true)
        }
    }

    fun getData() {
        //异步
        GlobalScope.launch {
            getDataInt(ketInt).collect {
                Log.e("Charles", "ketInt==$it")
            }
            getDataBoolean(keyBoolean).collect {
                Log.e("Charles", "keyBoolean==$it")
            }
            getDataLong(keyLong).collect {
                Log.e("Charles", "keyLong==$it")
            }
            getDataString(keyString).collect {
                Log.e("Charles", "ketString==$it")
            }
        }

Según la lógica normal, lo que pensamos es ir sacando los valores que guardé uno por uno, pero ahora la impresión sí es:

2021-03-22 17:03:26.461 22076-22439/com.example.myapplication E/Charles: ketInt==4

Esto se debe a que una de las principales ventajas de DataStore es la API asincrónica, pero es posible que no siempre sea posible cambiar el código circundante a código asincrónico. Esto puede suceder si usa una base de código existente que usa E / S de disco síncrono, o si sus dependencias no proporcionan API asíncronas. La corrutina de Kotlin proporciona el constructor de corrutinas runBlocking () para ayudar a eliminar la diferencia entre el código sincrónico y asincrónico. Puede utilizar runBlocking () para leer datos de forma sincrónica desde DataStore.

Siempre que se agregue runBlocking, el código en el bloque bloqueará el hilo de llamada hasta el final de la ejecución. Obviamente, si el subproceso principal está bloqueado debido a operaciones que consumen mucho tiempo, el subproceso principal se atascará, así que use almacenamiento asíncrono o lea para operaciones que consumen mucho tiempo .

    fun getDataLongSyn(key: Preferences.Key<Long>): Long {
        var value = -1L
        runBlocking {
            dataStore.data.first {
                value = it[key] ?: -1
                true
            }
        }
        return value
    }

DataStore se implementa en base a Flow, porque el almacenamiento anterior es asíncrono y el flujo de datos adquiridos también es asincrónico. Si desea obtenerlo de vez en cuando, puede usar first ()

Después de sumar, la impresión obtenida al tomar secuencialmente los valores es:

2021-03-22 17:29:49.062 25850-25850/com.example.myapplication E/Charles: ketInt==true
2021-03-22 17:29:49.062 25850-25850/com.example.myapplication E/Charles: ketInt==5
2021-03-22 17:29:49.062 25850-25850/com.example.myapplication E/Charles: ketInt==charles
2021-03-22 17:29:49.064 25850-26008/com.example.myapplication E/Charles: ketInt==4

La realización de operaciones de E / S síncronas en subprocesos de interfaz puede causar ANR o congelamiento de la interfaz. Estos problemas se pueden reducir al precargar datos de forma asíncrona desde DataStore:

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

Para el uso de Flow, consulte el sitio web oficial: (1) kotlin.github.io/kotlinx.cor ...

(2) cloud.tencent.com/developer/a…

resumen:

 这里只说了Preferences DataStore的使用,其实用起来还是很简单的,最主要是去学习kotlin的协程和Flow的
 用法这一块的东西还是比较有吸引点的.而且Flow很类似rxJava里面的Observable.

Supongo que te gusta

Origin blog.csdn.net/zhireshini233/article/details/115220295
Recomendado
Clasificación