DataStore之Preferences

  • 概述

    Preferences DataStore 使用键存储和访问数据。

    因为同是键值对存储,所以优点应该和SharedPreferences比较,它是线程安全、非阻塞的,解决了SharedPreferences的设计缺陷。

    同为DataStore,它的内部读取和存储和ProtoDataStore是完全一样的实现,拥有相同的优点,但它不需要预定义的架构,也不确保类型安全。

  • 使用

    首先需要添加依赖项:

    implementation("androidx.datastore:datastore-preferences:1.0.0")
    

    创建实例:

    val Context.demo1Preference by preferencesDataStore(name = "Demo1")
    

    其中name是文件名。

    读取和写入和Proto方式类似:

    /**
     * PreferencesDataStore读取
     */
    private fun testPreferenceDataStore() {
        val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
        val exampleCounterFlow : Flow = demo1Preference.data.map { preferences -> // No type safety.
            preferences[EXAMPLE_COUNTER] ?: 0
        }
    }
    /**
     * PreferencesDataStore写入
     */
    suspend fun incrementCounter() {
        val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
        demo1Preference.edit { settings ->
            val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
            settings[EXAMPLE_COUNTER] = currentCounterValue + 1
        }
    }
    
  • 源码分析

    • 构造

      public fun preferencesDataStore(
          name: String,
          corruptionHandler: ReplaceFileCorruptionHandler? = null,
          produceMigrations: (Context) -> List> = { listOf() },
          scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
      ): ReadOnlyProperty> {
          return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)
      }
      

      这里使用的是PreferenceDataStoreSingletonDelegate委托,它的getValue如下:

      override fun getValue(thisRef: Context, property: KProperty<*>): DataStore {
          return INSTANCE ?: synchronized(lock) {
              if (INSTANCE == null) {
                  val applicationContext = thisRef.applicationContext
      
                  INSTANCE = PreferenceDataStoreFactory.create(
                      corruptionHandler = corruptionHandler,
                      migrations = produceMigrations(applicationContext),
                      scope = scope
                  ) {
                      applicationContext.preferencesDataStoreFile(name)
                  }
              }
              INSTANCE!!
          }
      }
      

      PreferenceDataStoreFactory.create如下:

      @JvmOverloads
      public fun create(
          corruptionHandler: ReplaceFileCorruptionHandler? = null,
          migrations: List> = listOf(),
          scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
          produceFile: () -> File
      ): DataStore {
          val delegate = DataStoreFactory.create(
              serializer = PreferencesSerializer,
              corruptionHandler = corruptionHandler,
              migrations = migrations,
              scope = scope
          ) {
              val file = produceFile()
              check(file.extension == PreferencesSerializer.fileExtension) {
                  "File extension for file: $file does not match required extension for" +
                      " Preferences file: ${PreferencesSerializer.fileExtension}"
              }
              file
          }
          return PreferenceDataStore(delegate)
      }
      

      PreferenceDataStore是一个名义类,内部方法其实委托给delegate来完成:

      internal class PreferenceDataStore(private val delegate: DataStore) :
          DataStore by delegate {
          override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences):
              Preferences {
                  return delegate.updateData {
                      val transformed = transform(it)
                      // Freeze the preferences since any future mutations will break DataStore. If a user
                      // tunnels the value out of DataStore and mutates it, this could be problematic.
                      // This is a safe cast, since MutablePreferences is the only implementation of
                      // Preferences.
                      (transformed as MutablePreferences).freeze()
                      transformed
                  }
              }
      }
      

      delegate又通过DataStoreFactory.create来完成:

      @JvmOverloads // Generate constructors for default params for java users.
      public fun  create(
          serializer: Serializer,
          corruptionHandler: ReplaceFileCorruptionHandler? = null,
          migrations: List> = listOf(),
          scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
          produceFile: () -> File
      ): DataStore =
          SingleProcessDataStore(
              produceFile = produceFile,
              serializer = serializer,
              corruptionHandler = corruptionHandler ?: NoOpCorruptionHandler(),
              initTasksList = listOf(DataMigrationInitializer.getInitializer(migrations)),
              scope = scope
          )
      

      到这里再往下就和Proto的构造是完全一样的代码了。之后的代码可以看另一篇《DataStore之Preferences》。

    • 读取和写入

      通过上面读取写入的使用代码我们可以看出,是通过“[]”来获取和赋值的,这是kotlin的特殊语法,实际上就是调用的get和set方法,注意读取时map代码块中的参数是Preferences类型,这是DataStore的泛型指定的,而写入时edit代码块中的参数则是其子类MutablePreferences:

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

      edit代码块中的逻辑会在这里交给PreferenceDataStore中的updateData,从而交给SingleProcessDataStore。

      这里会把Preferences转化成MutablePreferences,所以我们读取的也是MutablePreferences,只不过用多态接收的,所以看一下MutablePreferences的get和set方法的实现:

      override operator fun  get(key: Key): T? {
          @Suppress("UNCHECKED_CAST")
          return preferencesMap[key] as T?
      }
      
      public operator fun  set(key: Key, value: T) {
          setUnchecked(key, value)
      }
      
      /**
       * Private setter function. The type of key and value *must* be the same.
       */
      internal fun setUnchecked(key: Key<*>, value: Any?) {
          checkNotFrozen()
      
          when (value) {
              null -> remove(key)
              // Copy set so changes to input don't change Preferences. Wrap in unmodifiableSet so
              // returned instances can't be changed.
              is Set<*> -> preferencesMap[key] = Collections.unmodifiableSet(value.toSet())
              else -> preferencesMap[key] = value
          }
      }
      

      可以看到,数据会被保存在preferencesMap中,读取的时候也是从这里拿,那它的数据又是怎么保存到文件的,又是怎么从文件中加载的呢?

      it.toMutablePreferences()会创建MutablePreferences,此时会创造一个空的preferencesMap:

      public fun toMutablePreferences(): MutablePreferences {
          return MutablePreferences(asMap().toMutableMap(), startFrozen = false)
      }
      
      public class MutablePreferences internal constructor(
          internal val preferencesMap: MutableMap, Any> = mutableMapOf(),
          startFrozen: Boolean = true
      ) : Preferences() { ... }
      

      根据前面构造的源码实现我们发现,PreferencesDataStore和ProtoDataStore的构造区别关键就是serializer的不同,这里传入的serializer是PreferencesSerializer,这个类不需要我们自己实现,已经有了。那根据《DataStore之Preferences》中的原理,我们来看看PreferencesSerializer实现的readFrom和writeTo方法:

      @Throws(IOException::class, CorruptionException::class)
      override suspend 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 suspend fun writeTo(t: Preferences, output: OutputStream) {
          //拿到preferencesMap
          val preferences = t.asMap()
          val protoBuilder = PreferenceMap.newBuilder()
      
          for ((key, value) in preferences) {
              protoBuilder.putPreferences(key.name, getValueProto(value))
          }
      
          protoBuilder.build().writeTo(output)
      }
      

      可以看到,这里分别操作输入流和输出流进行了读取和写入操作,数据就是和preferencesMap进行传递。

      读取的时候通过addProtoEntryToPreferences方法把数据设置到mutablePreferences中:

      private fun addProtoEntryToPreferences(
          name: String,
          value: Value,
          mutablePreferences: MutablePreferences
      ) {
          return when (value.valueCase) {
              Value.ValueCase.BOOLEAN ->
                  mutablePreferences[booleanPreferencesKey(name)] =
                      value.boolean
              Value.ValueCase.FLOAT -> mutablePreferences[floatPreferencesKey(name)] = value.float
              Value.ValueCase.DOUBLE -> mutablePreferences[doublePreferencesKey(name)] = value.double
              Value.ValueCase.INTEGER -> mutablePreferences[intPreferencesKey(name)] = value.integer
              Value.ValueCase.LONG -> mutablePreferences[longPreferencesKey(name)] = value.long
              Value.ValueCase.STRING -> mutablePreferences[stringPreferencesKey(name)] = value.string
              Value.ValueCase.STRING_SET ->
                  mutablePreferences[stringSetPreferencesKey(name)] =
                      value.stringSet.stringsList.toSet()
              Value.ValueCase.VALUE_NOT_SET ->
                  throw CorruptionException("Value not set.")
              null -> throw CorruptionException("Value case is null.")
          }
      }
      

      可以看到,这里调用了mutablePreferences的set方法,也就是会把数据保存到preferencesMap中。

      此外写入的时候也会调用getValueProto方法对保存的数据进行赋值:

      private fun getValueProto(value: Any): Value {
          return when (value) {
              is Boolean -> Value.newBuilder().setBoolean(value).build()
              is Float -> Value.newBuilder().setFloat(value).build()
              is Double -> Value.newBuilder().setDouble(value).build()
              is Int -> Value.newBuilder().setInteger(value).build()
              is Long -> Value.newBuilder().setLong(value).build()
              is String -> Value.newBuilder().setString(value).build()
              is Set<*> ->
                  @Suppress("UNCHECKED_CAST")
                  Value.newBuilder().setStringSet(
                      StringSet.newBuilder().addAllStrings(value as Set)
                  ).build()
              else -> throw IllegalStateException(
                  "PreferencesSerializer does not support type: ${value.javaClass.name}"
              )
          }
      }
      

      可以看到,只支持基本的类型,也就是说PreferencesDataStore只能保存基本的数据类型,像自定义类型是无法保存的,这和SharedPreferences是一致的限制,不过我们可以通过将自定义类对象转换成json字符串作为string存储。

      但是规范来讲,如果是自定义类型存储,用proto会更标准一些,不过开发成本会高一点,这个可以自由取舍。

你可能感兴趣的:(DataStore之Preferences)