-
概述
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会更标准一些,不过开发成本会高一点,这个可以自由取舍。
-