干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方法,也就是SingleProcessDataStore的update的方法,来继续看一看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试一试吧