首页 > 其他分享 >Android Jetpack DataStore

Android Jetpack DataStore

时间:2023-04-26 15:36:18浏览次数:43  
标签:runBlocking String Jetpack value key fun Android DataStore

原文地址 www.jianshu.com

导语

Jetpack简介及其它组件文章
DataStore就是SharedPreferences(简称SP)的替代品,Google为什么要用DataStore来替代SP呢,因为SP存在着很多问题,我之前在Android SharedPreferences转为MMKV中有详细说明了SP的不足,但是当时的有些观点还有些浅薄,所以使用了MMKV来替代SP,现在我更推荐大家使用DataStore替代SP,下面会详细讲出。

主要内容

  • DataStore的基本概念
  • DataStore、SP、MMKV对比
  • DataStore的基本使用
  • DataStore的封装

DataStore的基本概念

Jetpack DataStore 是一种数据存储解决方案,允许您使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。
所以如果想要使用DataStore,就必须使用Kotlin,因为DataStore用到了flow,flow用到了协程,协程是Kotlin的特性。
但是,如果将DataStore封装起来,那么直接使用Java调用的话,也是可以正常使用的,所以大家也不用担心。
DataStore 提供两种不同的实现:Preferences DataStore 和 Proto DataStore。

  • Preferences DataStore 使用键存储和访问数据。此实现不需要预定义的架构,也不确保类型安全。
  • Proto DataStore 将数据作为自定义数据类型的实例进行存储。此实现要求您使用协议缓冲区来定义架构,但可以确保类型安全。
    这里我们重点讲Preferences DataStore,这足以够大多数用户使用。

DataStore、SP、MMKV对比

大量文章指出MMKV的性能是多么多么高,但其实MMKV在存大数据的时候,因为MMKV是自行管理一块内存,性能是反而更低的。
我们先来看一下三种方案进行1000次Int值的连续写入耗时:

连续写入1000次Int值耗时

这么一看MMKV快到离谱,但是SP是可以异步写入的,而DataStore是基于协程的,我们关心的卡顿是主流程的流畅,所以我们只需要考虑主线程的耗时,所以会变成这样:

连续写入1000次Int值耗时(异步)
可以看到耗时大大缩减,但是还是比MMKV慢很多,这是Int数据类型的写入,Int数据是很小的,我们换成长字符串,再来看一下效果:
连续写入1000次长字符串耗时(异步)
这时我们会发现,MMKV反而成为那个最慢的,DataStore遥遥领先。

所以我们要注意的是主线程的耗时,而这个数据其实也并不重要,因为在项目中也很少有1000次大数据的写入,而1000次大数据耗时也不过不到1秒,所以选合适的更重要。

DataStore的基本使用

创建 Preferences DataStore
 private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
        name = "settings"
    ) 

name参数是 Preferences DataStore 的名称。类似SP中的name。

将内容写入 Preferences DataStore
 val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
    suspend fun putIntData() {
        context.dataStore.edit { settings ->
            val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
            settings[EXAMPLE_COUNTER] = currentCounterValue + 1
        }
    } 

我们通过intPreferencesKey来定义我们的Key的name以及存储的数据类型,如果是存放String类型就是stringPreferencesKey。
通过settings[EXAMPLE_COUNTER]是可以取到数据的,通过赋值可以用来存数据。

从 Preferences DataStore 读取内容
 val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
    val exampleCounterFlow: Flow<Int> = context.dataStore.data
        .map { preferences ->
            // No type safety.
            preferences[EXAMPLE_COUNTER] ?: 0
        } 

通过这种方式取到的是一个被Flow,通过exampleCounterFlow.first()就可以拿到真实的数据。

在同步代码中使用 DataStore

DataStore 的主要优势之一是异步 API,但可能不一定始终能将周围的代码更改为异步代码。
Kotlin 协程提供 runBlocking() 协程构建器,以帮助消除同步与异步代码之间的差异。您可以使用 runBlocking() 从 DataStore 同步读取数据。

val exampleData = runBlocking { context.dataStore.data.first() } 

对界面线程执行同步 I/O 操作可能会导致 ANR 或界面卡顿。您可以通过从 DataStore 异步预加载数据来减少这些问题:

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

这样,DataStore 可以异步读取数据并将其缓存在内存中。以后使用 runBlocking() 进行同步读取的速度可能会更快,或者如果初始读取已经完成,可能也可以完全避免磁盘 I/O 操作。

DataStore的封装

封装

既然我们都使用Kotlin了,那就使用Kotlin的特性扩展函数来实现吧。

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking

/**
 * Created by 郭士超 on 2022/11/6 15:48
 * Describe:DataStoreExt.kt
 */

/**
 * 存放数据
 */
fun <T> DataStore<Preferences>.putData(key: String, value: T) {
    runBlocking {
        when(value) {
            is String -> {
                putString(key, value)
            }
            is Int -> {
                putInt(key, value)
            }
            is Long -> {
                putLong(key, value)
            }
            is Float -> {
                putFloat(key, value)
            }
            is Double -> {
                putDouble(key, value)
            }
            is Boolean -> {
                putBoolean(key, value)
            }
        }
    }
}

/**
 * 取出数据
 */
fun <T> DataStore<Preferences>.getData(key: String, defaultValue: T): T {
     val data = when(defaultValue) {
        is String -> {
            getString(key, defaultValue)
        }
        is Int -> {
            getInt(key, defaultValue)
        }
        is Long -> {
            getLong(key, defaultValue)
        }
        is Float -> {
            getFloat(key, defaultValue)
        }
        is Double -> {
            getDouble(key, defaultValue)
        }
        is Boolean -> {
            getBoolean(key, defaultValue)
        }
        else -> {
            throw IllegalArgumentException("This type cannot be saved to the Data Store")
        }
    }
    return data as T
}


/**
 * 清空数据
 */
fun DataStore<Preferences>.clear() = runBlocking { edit { it.clear() } }


/**
 * 存放String数据
 */
private suspend fun DataStore<Preferences>.putString(key: String, value: String) {
    edit {
        it[stringPreferencesKey(key)] = value
    }
}

/**
 * 存放Int数据
 */
private suspend fun DataStore<Preferences>.putInt(key: String, value: Int) {
    edit {
        it[intPreferencesKey(key)] = value
    }
}

/**
 * 存放Long数据
 */
private suspend fun DataStore<Preferences>.putLong(key: String, value: Long) {
    edit {
        it[longPreferencesKey(key)] = value
    }
}

/**
 * 存放Float数据
 */
private suspend fun DataStore<Preferences>.putFloat(key: String, value: Float) {
    edit {
        it[floatPreferencesKey(key)] = value
    }
}

/**
 * 存放Double数据
 */
private suspend fun DataStore<Preferences>.putDouble(key: String, value: Double) {
    edit {
        it[doublePreferencesKey(key)] = value
    }
}

/**
 * 存放Boolean数据
 */
private suspend fun DataStore<Preferences>.putBoolean(key: String, value: Boolean) {
    edit {
        it[booleanPreferencesKey(key)] = value
    }
}


/**
 * 取出String数据
 */
private fun DataStore<Preferences>.getString(key: String, default: String? = null): String = runBlocking {
    return@runBlocking data.map {
        it[stringPreferencesKey(key)] ?: default
    }.first()!!
}

/**
 * 取出Int数据
 */
private fun DataStore<Preferences>.getInt(key: String, default: Int = 0): Int = runBlocking {
    return@runBlocking data.map {
        it[intPreferencesKey(key)] ?: default
    }.first()
}

/**
 * 取出Long数据
 */
private fun DataStore<Preferences>.getLong(key: String, default: Long = 0): Long = runBlocking {
    return@runBlocking data.map {
        it[longPreferencesKey(key)] ?: default
    }.first()
}

/**
 * 取出Float数据
 */
private fun DataStore<Preferences>.getFloat(key: String, default: Float = 0.0f): Float = runBlocking {
    return@runBlocking data.map {
        it[floatPreferencesKey(key)] ?: default
    }.first()
}

/**
 * 取出Double数据
 */
private fun DataStore<Preferences>.getDouble(key: String, default: Double = 0.00): Double = runBlocking {
    return@runBlocking data.map {
        it[doublePreferencesKey(key)] ?: default
    }.first()
}

/**
 * 取出Boolean数据
 */
private fun DataStore<Preferences>.getBoolean(key: String, default: Boolean = false): Boolean = runBlocking {
    return@runBlocking data.map {
        it[booleanPreferencesKey(key)] ?: default
    }.first()
} 
使用

封装完成我们应该如何使用呢?

//第一步先创建一个自己的Application
class MyApplication: Application() {

    companion object {
        lateinit var instance : MyApplication
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }

} 
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore

/**
 * Created by 郭士超 on 2022/11/6 16:38
 * Describe:AppDataStore.kt
 */
object AppDataStore {

    // 创建DataStore
    private val MyApplication.appDataStore: DataStore<Preferences> by preferencesDataStore(
        name = "App"
    )

    // DataStore变量
    private val dataStore = MyApplication.instance.appDataStore

    private fun <T> putData(key: String, value: T) {
        dataStore.putData(key, value)
    }

    private fun <T> gutData(key: String, value: T): T {
        return dataStore.getData(key, value)
    }

    fun clear() {
        dataStore.clear()
    }

    private const val NUMBER = "number"
    fun putNumber(number: Int) {
        dataStore.putData(NUMBER, number)
    }
    fun getNumber(): Int {
        return dataStore.getData(NUMBER, 0)
    }

} 

封装好之后,我们在使用的时候,不同的模块使用不同的XxxDataStore,这样可以解耦合,我们将put和get方法放到XxxDataStore中统一管理,方便我们快速定位这个方法都在哪里调用,从而更快定位到问题或者加快开发速度。

更多内容戳这里(整理好的各种文集)

标签:runBlocking,String,Jetpack,value,key,fun,Android,DataStore
From: https://www.cnblogs.com/cps666/p/17356231.html

相关文章

  • android:Retrofit2网络请求封装
    原文地址blog.csdn.netRetrofit使用=================================================================================Retrofit是一个网络请求框架,先来说一下怎么使用网络权限(添加到AndroidManifest.xml)<uses-permissionandroid:/>gradle依赖(添加到build.gra......
  • DataStore 封装
    原文地址www.learnfk.com之前ContentProvider,Room,DataStore一起弄,对于蒟蒻我来说步子迈得太大了,bug满天飞(DataStore一直给我报错说同时打开了多个DataStore,却又找不到问题所在),遂不得不暂且抛下DataStore换回SharedPreference,后来才发现是被Hook应用在启动的时候,LSPosed检测......
  • Android原生项目引入Flutter
    原文地址www.jianshu.com前言目前Flutter可以说是非常火热了,多次更新过后也越来越稳定,受到了很多开发者的青睐。不过纯Flutter开发还是存在一定成本和风险的,尤其是对于规模稍大一些的项目,可能更加适合的是将Flutter用于项目中的某一个模块,因此我们有必要了解一下如何在原生......
  • Android中好用的库
    原文地址www.jianshu.com本库主要提供一个简单易用的自定义分段控件,方便快速实现分段效果,支持xml配置、代码配置、分段规则按均分/比例分、数字分段、文本分段、渐变分段、bar条样式正常/圆形/三角形,segment文字样式、进度设置、进度标记类型设置、分段描述设置、其它更多自......
  • Android多进程时Application初始化问题
    原文地址blog.csdn.net之前在做项目时,遇到一个大坑,就是我的APP的Application的onCreate方法,竟然执行了好几次,这就导致我在onCreate里面做了一些初始化的操作被重复执行了,导致奇怪的bug产生。后来冷静下来分析一下,才发现有一些第三方组件,比如百度推送之类的,它们是单独开了一......
  • Android之Service设置android:process作用
    原文地址blog.csdn.net在AndroidManifest.xml中定义service时会看到这样的代码android:process=”:remote”,例如:1.<service2.android:3.android:enabled="true"4.android:exported="false"5.android:process=":remote"/&g......
  • Android音频开发之音频基本概念
    原文地址www.jianshu.comAndroid音频开发之音频基本概念音频采集一般使用AudioRecod或者MediaRecord音频采集的来源是什么?MediaRecorder.AudioSource.MIC一般是麦克风采样率(单位:赫兹)每秒钟音频采样点个数(8000/44100Hz),模拟信号数字化的过程,用0101来表示的数......
  • Android音频开发之AudioTrack
    原文地址www.jianshu.com在前两节中分享了Android音频开发之音频基本概念和Android音频开发之音频采集,本文分享的是如何使用AudioTrack来播放使用AudioRecord采集后的PCM数据。构造AudioTrack实例publicAudioTrack(intstreamType,intsampleRateInHz,intcha......
  • Android音频开发之音频采集
    原文地址www.jianshu.comAndroid音频开发之音频采集在Android系统中,一般使用AudioRecord或者MediaRecord来采集音频。AudioRecord是一个比较偏底层的API,它可以获取到一帧帧PCM数据,之后可以对这些数据进行处理。而MediaRecorder是基于AudioRecorder的API(最......
  • Android 开发中打印日志
    在Android开发过程中,我们常常需要打印堆栈来定位问题或者调试问题,这里记录一下相关的方法。1.MTK平台1.1preloader 1.2LK 2.高通平台2.1sbl 2.2xbl 2.3abl  3.kernel  4.native层4.1打印堆栈 Android.bpcc_binary{name:"print_s......