是时候告别SharedPreferences,Jetpack组件库中的DataStore原理详解

干android的小伙伴们都知道SharedPreferences偏好参数的用法,它主要是用来存储用户的一些信息,比如用户账号、用户设置中的个人喜好,用起来还不错,但是如果偏好参数保存的过多的,加载的速度就会变慢,因为每次重新启动app都需要将文件中所有的键值对内容读到内存中,这就影响了速度,另一方面,改变偏好参数的时候是修改的本地内存,所以进程是不共享的。你想在偏好参数中保存大量数据吗?那么是时候将你的项目中的偏好参数去掉了,是时候换成Jetpack组件库中的DataStore如图:

再配合kotlin的协程那叫一个爽。

ok,现在来一起熟悉熟悉这个组件库的用法先声明一个object类,如下:

object StaticData {

     val name= preferencesKey<String>("my_name");
     val userInfo= preferencesKey<String>("user_info");
}

在传统的偏好参数的写法上,咱们知道不管是取值还是传值都是以key-value键值对的形式传值的,那么现在你所传的key值就是preferencesKey<String>("my_name"),现在key值是“my_name”,而value的类型是String,比如我再设置一个int类型的键值对val word= preferencesKey<Int>("keyWord") ,有key和value的泛型类型后接下来咱们就是存取值了,如图:


    private val repository=DataStoreRepository(application)

    //save string to data base
    fun  saveStringToDataBaseStore(myNameValue: String, key: Preferences.Key<String>) = viewModelScope.launch(Dispatchers.IO){
        repository.saveDataToDataStore(myNameValue,key)
    }

        //read string to data base
    fun readStringFromDataStore(keName: Preferences.Key<String>): LiveData<String> {
       return repository.readStringFromDataStore(keName).asLiveData()
    }


首先创建DataStoreRepository(application)这个类,然后调用saveDataToDataStore保存数据和readStringFromDataStore取值,先来看看DataStoreRepository这个类是干什么的,如下

import android.content.Context
import android.util.Log
import androidx.datastore.DataStore
import androidx.datastore.preferences.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import synergetic.devs.preferencesdatastore.BuildConfig
import java.io.IOException
//get package name
const val  PREFERENCE_NAME= BuildConfig.APPLICATION_ID

class DataStoreRepository(context:Context){

//init data store
    private val dataStore:DataStore<Preferences> = context.createDataStore(
        name = PREFERENCE_NAME
    )

    //save data to data store
    suspend fun saveDataToDataStore(name: String, key: Preferences.Key<String>){
        dataStore.edit { preference->
            preference[key]=name
        }
    }


    //read String data to from data store
    fun readStringFromDataStore(keyName: Preferences.Key<String>): Flow<String>  {

        return dataStore.data.catch { exception->
            if (exception is IOException){
                Log.e("TAG", "Data Store: "+exception.message.toString() )
                emit(emptyPreferences())
            }else{
                throw exception
            }

        }.map { preference->
            val myName:String = preference[keyName]?:"none"
            myName
        }
    }

首先根据名字创建  private val dataStore:DataStore<Preferences> = context.createDataStore(
        name = PREFERENCE_NAME
    )
一个偏好存储,然后就可以以愉快的操作数据了,存储操作 dataStore.edit { preference->
            preference[key]=name
       }

查询操作

dataStore.data.catch { exception->
            if (exception is IOException){
                Log.e("TAG", "Data Store: "+exception.message.toString() )
                emit(emptyPreferences())
            }else{
                throw exception
            }

        }.map { preference->
            val myName:String = preference[keyName]?:"none"
            myName
        }

接下来看一下DataStore怎么操作的,如图:

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
    )

这边创建了一个file文件用于将键值对写入磁盘,继续看

val delegate = DataStoreFactory.create(
            produceFile = {
                val file = produceFile()
                check(file.extension == PreferencesSerializer.fileExtension) {
                    "File extension for file: $file does not match required extension for" +
                            " Preferences file: ${PreferencesSerializer.fileExtension}"
                }
                file
            },
            serializer = PreferencesSerializer,
            corruptionHandler = corruptionHandler,
            migrations = migrations,
            scope = scope
        )
        return PreferenceDataStore(delegate)

fun <T> create(
        produceFile: () -> File,
        serializer: Serializer<T>,
        corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
        migrations: List<DataMigration<T>> = listOf(),
        scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    ): DataStore<T> =
        SingleProcessDataStore(
            produceFile = produceFile,
            serializer = serializer,
            corruptionHandler = corruptionHandler ?: NoOpCorruptionHandler(),
            initTasksList = listOf(DataMigrationInitializer.getInitializer(migrations)),
            scope = scope
        )
}

最后创建了一个SingleProcessDataStore,返回了一个包装类PreferenceDataStore,那么直接来看一下真正的存储和读取的操作

suspend fun DataStore<Preferences>.edit(
    transform: suspend (MutablePreferences) -> Unit
): Preferences {
    return this.updateData {
        // It's safe to return MutablePreferences since we make a defensive copy in
        // PreferencesDataStore.updateData()
        it.toMutablePreferences().apply { transform(this) }
    }

都是调用了update方法,也就是SingleProcessDataStoreupdate的方法,来继续看一看update方法做了什么

override suspend fun updateData(transform: suspend (t: T) -> T): T {
        val ack = CompletableDeferred<T>()
        val dataChannel = downstreamChannel()
        val updateMsg = Message.Update<T>(transform, ack, dataChannel)

        actor.send(updateMsg)

        // If no read has succeeded yet, we need to wait on the result of the next read so we can
        // bubble exceptions up to the caller. Read exceptions are not bubbled up through ack.
        if (dataChannel.valueOrNull == null) {
            dataChannel.asFlow().first()
        }

        // Wait with same scope as the actor, so we're not waiting on a cancelled actor.
        return withContext(scope.coroutineContext) { ack.await() }
    }
actor.send(updateMsg)用来发送一个消息,用来处理消息的代码如下
 private val actor: SendChannel<Message<T>> = scope.actor(
        capacity = UNLIMITED
    ) {
        try {
            messageConsumer@ for (msg in channel) {
                if (msg.dataChannel.isClosedForSend) {
                    // The message was sent with an old, now closed, dataChannel. This means that
                    // our read failed.
                    continue@messageConsumer
                }

                try {
                    readAndInitOnce(msg.dataChannel)
                } catch (ex: Throwable) {
                    resetDataChannel(ex)
                    continue@messageConsumer
                }

                // We have successfully read data and sent it to downstreamChannel.

                if (msg is Message.Update) {
                    msg.ack.completeWith(
                        runCatching {
                            transformAndWrite(msg.transform, downstreamChannel())
                        }
                    )
                }
            }
        } finally {
            // The scope has been cancelled. Cancel downstream in case there are any collectors
            // still active.
            downstreamChannel().cancel()
        }
    }

 override fun readFrom(input: InputStream): Preferences {
        val preferencesProto = PreferencesMapCompat.readFrom(input)

        val mutablePreferences = mutablePreferencesOf()

        preferencesProto.preferencesMap.forEach { (name, value) ->
            addProtoEntryToPreferences(name, value, mutablePreferences)
        }

        return mutablePreferences.toPreferences()
    }

    @Throws(IOException::class, CorruptionException::class)
    override fun writeTo(t: Preferences, output: OutputStream) {
        val preferences = t.asMap()
        val protoBuilder = PreferenceMap.newBuilder()

        for ((key, value) in preferences) {
            protoBuilder.putPreferences(key.name, getValueProto(value))
        }

        protoBuilder.build().writeTo(output)
    }

这里是kotlin的分布式Actor模型的用法,大家可以自行研究一下,这代码的意思是首先会读取数据,然后对边前后数据有无变化,变化的话更新本地文件数据,可以看出所有的处理都是在协程当中处理的,还有就是存储读写还用了高性能的编解码机制Protobuf,从而保证了存储和取值的高性能,大家可以研究一下。

好了,小伙伴抓紧换这个DataStore试一试吧

 

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值