江西省城乡和住房建设厅网站seo分析
简介
- 首先,DataStore是Jetpack一部分,是一种数据存储解决方案。
- 其次,DataStore使用协程及flow以异步、一致的方式实现数据的存储。
- 最后是DataStore的实现,分为Preferences DataStore和Proto DataStore:
- Preferences DataStore 类似于SharedPreferences,键值对存储,本篇的主要介绍。
- Proto DataStore 将数据作为自定义数据类型的实例进行存储,基于Google protobuf实现。
DataStore和SharedPreferences(SP)
- SP存在的问题:
- 内存浪费问题,加载的数据会一直存在在内存中。
- get方法可能会阻塞主线程。
- apply方法虽然为异步,也可能会发生ANR。
- SP也无法保证类型安全。
- SP不支持跨进程。
- DataStore的特点:
- 基于协程和Flow实现,保证了主线程的安全性。
- 以事务的方式进行处理,保证了操作的原子性、一致性、隔离性及持久性。
- Preferences DataStore可以支持SP的迁移,保证数据的完整性。
- 说明:
- 此处只是列出SP存在的问题,并没有SP一无是处的的说法,DataStore在SP的基础上解决了不少的问题,但是也没有说SP存在的问题已经全部解决了。
- 至少,DataStore在SP的基础上解决了如类型安全、阻塞导致anr等问题,确实这方面优于SP。
使用
Preferences DataStore
基本使用流程
引入
def dataStoreVersion = '1.0.0-beta01'
implementation "androidx.datastore:datastore-preferences:$dataStoreVersion"
创建DataStore
//指定DataStore的文件名
//对应最终件:/data/data/org.geekbang.aac/files/datastore/user_preferences.preferences_pb
private const val USER_PREFERENCES_NAME = "user_preferences"
//扩展属性DataStore,实际类型为DataStore<Preferences>
private val Context.dataStore by preferencesDataStore(
name = USER_PREFERENCES_NAME,//指定名称
produceMigrations = {context -> //指定要恢复的sp文件,无需恢复可不写listOf(SharedPreferencesMigration(context, USER_PREFERENCES_NAME))
}
)
定义Key
val SORT_ORDER = stringPreferencesKey("sort_order")
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
//... 通过查看源码可以看到支持的其它数据类型
存储
//edit要在suspend函数中
override suspend fun updateShowCompleted(showCompleted: Boolean) {dataStore.edit { preferences ->//...这里可以做一些数据的逻辑处理preferences[SHOW_COMPLETED] = showCompleted// 整个tranform中的所有代码块被视为单个事务}
}
读取
override val userPreferencesFlow = dataStore.data.catch { exception ->if (exception is IOException) {//进行IO异常处理,确保能得到默认值Log.e(TAG, "Error reading preferences.", exception)emit(emptyPreferences())} else {throw exception}}.map { preferences ->//真正的获取存储的一个字段val sortOrder = SortOrder.valueOf(preferences[SORT_ORDER] ?: SortOrder.NONE.name)val showCompleted = preferences[SHOW_COMPLETED] ?: falseUserPreferences(showCompleted, sortOrder)}
使用总结
一个对应的preferences_pb文件对应一个.kt文件,里面包含了文件名定义,DataStore定义,Key定义,存取方法定义;例如:
//TaskConfigDataStore.kt
/*** 文件名
*/
private const val TASK_CONFIG_PREFERENCES_FILE_NAME = "task_config_pre"
/*** dataStore对象
*/
val Context.taskConfigDataStore : DataStore<Preferences> by preferencesDataStore(
name = TASK_CONFIG_PREFERENCES_FILE_NAME
)
/** Keys **/
val SHOW_COMPLETED = booleanPreferencesKey("show_completed")
val OPEN_COUNT = intPreferencesKey("open_count")
//other keys
/** 存取方法 **/
fun getShowCompleted(context: Context): Flow<Boolean>
= context.taskConfigDataStore.data.catch { e->if(e is IOException){emptyPreferences()}else{throw e}}.map { pre->pre[SHOW_COMPLETED] ?: false}
suspend fun setShowCompleted(context: Context,showComplete: Boolean){
context.taskConfigDataStore.edit { pre->pre[SHOW_COMPLETED] = showComplete
}
}
// other method
ProtoBuf DataStore
基本使用流程
接入protobuf,以最新的为准 详情信息可参考
protobuf-gradle-plugin
,想详细了解protobuf基础知识,可参考
Protobuf 终极教程
- 在xxx.build中加入:
plugins {//other...id "com.google.protobuf" version "0.8.16"}
- dependencies
// protobuf
def protobufVersion = "3.10.0"
// 3.0.0后Android建议使用javalite
implementation "com.google.protobuf:protobuf-javalite:$protobufVersion"
- 增加protobuf 的块
protobuf {protoc {artifact = "com.google.protobuf:protoc:3.10.0"}
generateProtoTasks {all().each { task ->task.builtins {java {option 'lite'}}}}
}
- 在src/main/目录下建立proto文件,3.8.0以后自动识别此目录下的.proto文件
引入dataStore库
// dataStore
def dataStoreVersion = '1.0.0-beta01'
implementation "androidx.datastore:datastore:$dataStoreVersion"
建立proto文件后,进行rebuild
syntax = "proto3";
option java_package = "org.geekbang.aac";
option java_multiple_files = true;
message UserPreferences {
bool show_completed = 1;
enum SortOrder {UNSPECIFIED = 0;NONE = 1;BY_DEADLINE = 2;BY_PRIORITY = 3;BY_DEADLINE_AND_PRIORITY = 4;
}
SortOrder sort_order = 2;
}
创建Serializer的实现,告诉框架如何读写,这个接口明确规定要有默认值,以便在尚未创建任何文件时使用,这是必要流程,基本是固定写法,用编译器生成的Java类对应api即可
object UserPreferencesSerializer : Serializer<UserPreferences> {override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()@Suppress("BlockingMethodInNonBlockingContext")override suspend fun readFrom(input: InputStream): UserPreferences {try {return UserPreferences.parseFrom(input)} catch (exception: InvalidProtocolBufferException) {throw CorruptionException("Cannot read proto.", exception)}}@Suppress("BlockingMethodInNonBlockingContext")override suspend fun writeTo(t: UserPreferences, output: OutputStream) = t.writeTo(output)
}
定义创建DataStore对象
//老的sp的文件名
private const val USER_PREFERENCES_NAME = "user_preferences"
//新的文件名,对应目录 /data/data/com.codelab.android.datastore/files/datastore/user_prefs.pb
private const val DATA_STORE_FILE_NAME = "user_prefs.pb"
//老的对应的key
private const val SORT_ORDER_KEY = "sort_order"
// Build the DataStore
private val Context.userPreferencesStore: DataStore<UserPreferences> by dataStore(fileName = DATA_STORE_FILE_NAME,serializer = UserPreferencesSerializer,produceMigrations = { context ->listOf(SharedPreferencesMigration(context,USER_PREFERENCES_NAME) { sharedPrefs: SharedPreferencesView, currentData: UserPreferences ->// 定义从SharedPreferences到UserPreference的映射if (currentData.sortOrder == SortOrder.UNSPECIFIED) {currentData.toBuilder().setSortOrder(SortOrder.valueOf(sharedPrefs.getString(SORT_ORDER_KEY, SortOrder.NONE.name)!!)).build()} else {currentData}})}
)
存储
//必须是挂起函数,决定其要在协程中使用
suspend fun updateShowCompleted(completed: Boolean) {
//Proto DataStore 提供了一个updateData() 函数,
//用于以事务方式更新存储的对象
//为您提供数据的当前状态,作为数据类型的一个实例,并在原子读-写-修改操作中以事务方式更新数据userPreferencesStore.updateData { currentPreferences ->//当前文件对应的对象currentPreferences.toBuilder().setShowCompleted(completed).build()//对当前对象进行修改}}
读取
val userPreferencesFlow: Flow<UserPreferences> = userPreferencesStore.data.catch { exception ->// dataStore.data throws an IOException when an error is encountered when reading > dataif (exception is IOException) {Log.e(TAG, "Error reading sort order preferences.", exception)emit(UserPreferences.getDefaultInstance())} else {throw exception}}
//单独获取时是阻塞的,在实际使用中建议是异步的,在Kotlin项目中可以使用协程异步实现
suspend fun getUserPreferencesFlowData() = userPreferencesFlow.first()
使用总结
这个是面向相对复杂的对象结构(例如用户信息的本地缓存)的场景下使用,一般以一个proto文件为单位,相关定义,方法做好整体分类即可。
DataStore的优势
- DataStore 是基于 Flow 实现的,所以保证了在主线程的安全性
- 以事务方式处理更新数据,事务有四大特性(原子性、一致性、 隔离性、持久性)
- 没有 apply() 和 commit() 等等数据持久的方法
- 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏
- 可以监听到操作成功或者失败结果