赞
踩
1、协程原理---->很好的博客介绍,一个小故事讲明白进程、线程、Kotlin 协程到底啥关系?
2、Channel知识点---->Android—kotlin-Channel超详细讲解
3、Coroutines : CompletableDeferred and structured concurrency
封装的DataStoreUtils工具—>gitHub
公司使用SharedPreferences容易导致ANR,调研能否使用DataStore替换公司目前的SharedPreferences解决ANR问题,所以需要先研究一下源码
implementation "androidx.datastore:datastore-preferences:1.0.0"
既然是迁移数据,那么需要将SharedPreferences已存储的数据迁移到dataStore,所以需要先构建dataStore。
目前网上构建迁移DataStore的案例Demo如下
//迁移使用 private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = "userSharePreFile", produceMigrations = { context -> listOf( SharedPreferencesMigration( context, "userSharePreFile" ) ) } ) //或 //这种构建DataStore写法是alpha版本有的,在1.0.0版本就找不到了 var dataStore: DataStore<Preferences> = context.createDataStore( name = "userSharePreFile" ) //或 //直接构建 private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = "userSharePreFile" )
上面3种写法都是对Context进行扩展创建的DataStore,所以上面创建的方式,都有一个缺点,就是需要提前知道name才能创建,如果你之前创建SharedPreferences的方式,是通过外部传递进来name构建的话,上面直接创建DataStore方式就显然不适合你了。
//alpha版本构建方式 var dataStore: DataStore<Preferences> = context.createDataStore( name = "userSharePreFile" ) 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 )
可以明显看到是使用PreferenceDataStoreFactory.create返回DataStore
① 是构建需要迁移SharedPreferences文件名称
② 指明协程是在IO运行
③ 新文件存储的位置
再看看另外一种通过 by preferencesDataStore 创建DataStore方式
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore( name = "userSharePreFile" ) public fun preferencesDataStore( name: String, corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null, //① produceMigrations: (Context) -> List<DataMigration<Preferences>> = { listOf() }, //② scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) ): ReadOnlyProperty<Context, DataStore<Preferences>> { return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope) } internal class PreferenceDataStoreSingletonDelegate internal constructor( private val name: String, private val corruptionHandler: ReplaceFileCorruptionHandler<Preferences>?, private val produceMigrations: (Context) -> List<DataMigration<Preferences>>, private val scope: CoroutineScope ) : ReadOnlyProperty<Context, DataStore<Preferences>> { private val lock = Any() @GuardedBy("lock") @Volatile private var INSTANCE: DataStore<Preferences>? = null override fun getValue(thisRef: Context, property: KProperty<*>): DataStore<Preferences> { 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!! } } } //文件存储位置 public fun Context.preferencesDataStoreFile(name: String): File = this.dataStoreFile("$name.preferences_pb")
题外话:这里有利用kotlin委托属性by关键字语法
① 需要迁移的SharedPreferences文件
② 协程运行在IO
可以看出旧版本(alpha) 与 by preferencesDataStore 2种方案,都最终通过PreferenceDataStoreFactory.create,返回DataStore,我们就继续再看看PreferenceDataStoreFactory.kt的具体实现逻辑
//PreferenceDataStoreFactory.kt public fun create( corruptionHandler: ReplaceFileCorruptionHandler<Preferences>? = null, //迁移的share文件集合 migrations: List<DataMigration<Preferences>> = listOf(), //IO scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()), //dataStore文件存储的目录位置 produceFile: () -> File ): DataStore<Preferences> { val delegate = DataStoreFactory.create(//创建SingleProcessDataStore serializer = PreferencesSerializer, corruptionHandler = corruptionHandler, migrations = migrations, scope = scope ) { //省略代码 } //传入SingleProcessDataStore return PreferenceDataStore(delegate) } //这里有主动的去调用updateData 方法,如果不去主动调用,就不会触发迁移的逻辑 //下文的扩展函数DataStore<Preferences>.edit会说到这里 internal class PreferenceDataStore(private val delegate: DataStore<Preferences>) : DataStore<Preferences> by delegate { override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences { return delegate.updateData { val transformed = transform(it) (transformed as MutablePreferences).freeze() transformed } } }
继续看DataStoreFactory.create
//DataStoreFactory.kt 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返回DataStore。
下面我们通过一张图片来小结一下,旧版本alpha版本的创建与新版本 by preferencesDataStore的调用逻辑链
好,已经知道这么多了,那么我们就开始动态构建DataStore
fun preferencesMigrationDataStore(sharedPreferName: String) { val dataStore = PreferenceDataStoreFactory.create( corruptionHandler = ReplaceFileCorruptionHandler<Preferences>( produceNewData = { emptyPreferences() } ), //需要迁移的sharePrefer文件的名称 migrations = listOf(SharedPreferencesMigration(mContext, sharedPreferName)), //IO scope = CoroutineScope(Dispatchers.IO + SupervisorJob())) { //dataStore文件名称 mContext.preferencesDataStoreFile(sharedPreferName) } runBlocking { //必须要执行这行代码,否是不会走迁移的逻辑 dataStore.updateData { it.toPreferences() } } }
migrations:表示你要迁移的sharedPreference文件
scope :表示写数据是在IO
执行完上述代码后,.xml就会消失,然后会在files目录下多出一个/datastore/xxx.preferences_pb文件
切勿重复对某个SharedPreferences执行文件迁移方案,否则会报错。比如你前一秒在执行迁移,后一秒又继续执行迁移
####存储参数
/** * @key 参数 * @value 具体的值 */ private fun putInt(key:String, value: Int) { runBlocking { dataStore.edit {//① it[intPreferencesKey(key)] = value } } } //类似的还有如下,这些都是google提供的参数 intPreferencesKey doublePreferencesKey stringPreferencesKey ....
看①详情,点击edit,发现他是一个扩展函数
public suspend fun DataStore<Preferences>.edit(
transform: suspend (MutablePreferences) -> Unit
): Preferences {
return this.updateData {//调用的是PreferenceDataStore.updateData()
//it.toMutablePreferences() 返回类似map
it.toMutablePreferences().apply { transform(this) }
}
}
transform 就是调用者{}里面的内容,接下来我们看看 PreferenceDataStore 类的代码
//由前部分的代码,可以得知,delegate = SingleProcessDataStore
internal class PreferenceDataStore(private val delegate: DataStore<Preferences>) :
DataStore<Preferences> by delegate {
override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences):
Preferences {
return delegate.updateData {//调用SingleProcessDataStore.updateData
//返回给上一个{}也就是 it.toMutablePreferences().apply { transform(this) }
val transformed = transform(it)
(transformed as MutablePreferences).freeze()
transformed //拿到用户的需要更改的内容数据
}
}
}
代码里调用了delegate.updateData(), 所以继续看SingleProcessDataStore的updateData
SingleProcessDataStore.kt
override suspend fun updateData(transform: suspend (t: T) -> T): T {
val ack = CompletableDeferred<T>()
val currentDownStreamFlowState = downstreamFlow.value
//协程体封装进Message.Update,coroutineContext 是协程的上下文,就是我们的 runBlocking 启动的线程,我这里是main
val updateMsg = Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
//对消息进行分发,他的类是 SimpleActor
actor.offer(updateMsg)
//这里会拿到Preferences,如何拿?后面会有一个update.ack.completeWith方法,会返回回来
return ack.await()
}
internal class SimpleActor<T>( private val scope: CoroutineScope,//Dispatchers.IO + SupervisorJob() onComplete: (Throwable?) -> Unit, onUndeliveredElement: (T, Throwable?) -> Unit, private val consumeMessage: suspend (T) -> Unit ) { private val messageQueue = Channel<T>(capacity = UNLIMITED) private val remainingMessages = AtomicInteger(0) //...... 省去 //这里就是将刚刚封装的消息体,添加进这里 fun offer(msg: T) { check( //发送封装的消息体 messageQueue.trySend(msg) .onClosed { throw it ?: ClosedSendChannelException("Channel was closed normally") } .isSuccess ) if (remainingMessages.getAndIncrement() == 0) { scope.launch { check(remainingMessages.get() > 0) do { // scope = Dispatchers.IO + SupervisorJob() scope.ensureActive() //取出封装的消息体,然后进行任务处理 consumeMessage(messageQueue.receive()) } while (remainingMessages.decrementAndGet() != 0) } } } }
tip:这里有利用Channel进行协程通信,Channel是可以处理并发的情况
到这里,我们可以知道,我们由runBlocking(main主线程) 协程 到 Dispatchers.IO的任务分发
private val actor = SimpleActor<Message<T>>( scope = scope,// CoroutineScope(Dispatchers.IO + SupervisorJob()) onComplete = {//.....省略}, onUndeliveredElement = { msg, ex -> //.....省略 ) { msg -> //处理分发的任务,msg 为刚刚封装的updateMsg when (msg) { is Message.Read -> {//读取 handleRead(msg) } is Message.Update -> {//更新 handleUpdate(msg) } } }
private suspend fun handleUpdate(update: Message.Update<T>) { update.ack.completeWith( runCatching { when (val currentState = downstreamFlow.value) { is Data -> { //写数据到file transformAndWrite(update.transform, update.callerContext) } is ReadException, is UnInitialized -> { if (currentState === update.lastState) { //读取file文件 ① readAndInitOrPropagateAndThrowFailure() //写数据到file ② transformAndWrite(update.transform, update.callerContext) } else { throw (currentState as ReadException).readException } } is Final -> throw currentState.finalException // won't happen } } ) }
第一次使用 downstreamFlow.value = UnInitialized 。
这里要注意一下update.ack.completeWith这个函数,他是拿到结果成功返回
这里再次展示出来,是告诉大家,在哪里会等待结果返回
override suspend fun updateData(transform: suspend (t: T) -> T): T {
val ack = CompletableDeferred<T>()
val currentDownStreamFlowState = downstreamFlow.value
val updateMsg =
Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)
actor.offer(updateMsg)
return ack.await() //这里就是等待 update.ack.completeWith的结果返回,所以如果不加这行,是不会卡主线程的
}
所以使用runBlocking是会卡主线程的,如果你还有UI刷新情况,严重的情况会导致ANR问题
不扯之前的了,我们继续继续,看① 的读取
private suspend fun readAndInitOrPropagateAndThrowFailure() { try { readAndInit() } catch (throwable: Throwable) { downstreamFlow.value = ReadException(throwable) throw throwable } } private suspend fun readAndInit() { check(downstreamFlow.value == UnInitialized || downstreamFlow.value is ReadException) //这个是锁,协程里面专有的,详情可以看 https://www.kotlincn.net/docs/reference/coroutines/shared-mutable-state-and-concurrency.html val updateLock = Mutex() //读取dataStore文件 var initData = readDataOrHandleCorruption() var initializationComplete: Boolean = false //这里就是shareprefence转dataStore val api = object : InitializerApi<T> { override suspend fun updateData(transform: suspend (t: T) -> T): T { return updateLock.withLock() { if (initializationComplete) { throw IllegalStateException( "InitializerApi.updateData should not be " + "called after initialization is complete." ) } //transform里面就是去迁移数据的方法 val newData = transform(initData) //这里有做,新 旧值比较,如果不同,就去写入 if (newData != initData) { //写文件 writeData(newData) initData = newData } initData } } } //initTasks 里面装的就是需要转换的 SharedPreferences集合 initTasks?.forEach { it(api) } initTasks = null updateLock.withLock { initializationComplete = true } //这里有将迁移完成后的数据,存储在flow.value里面 downstreamFlow.value = Data(initData, initData.hashCode()) } //读取dataStore文件 private suspend fun readDataOrHandleCorruption(): T { try { return readData() } catch (ex: CorruptionException) { val newData: T = corruptionHandler.handleCorruption(ex) try { writeData(newData) } catch (writeEx: IOException) { ex.addSuppressed(writeEx) throw ex } return newData } } private suspend fun readData(): T { try { FileInputStream(file).use { stream -> return serializer.readFrom(stream) } } catch (ex: FileNotFoundException) { if (file.exists()) { throw ex } return serializer.defaultValue } }
file就是我们存储的dataStore,目录是在 “datastore/$name.preferences_pb”
看完了①,再来看看② 写入数据到file,写数据的方法是 transformAndWrite()
//.... transformAndWrite(update.transform, update.callerContext) //... private suspend fun transformAndWrite( //来源于 Message.Update.transform封装 transform: suspend (t: T) -> T, //来源于 Message.Update.callerContext封装 callerContext: CoroutineContext ): T { val curDataAndHash = downstreamFlow.value as Data<T> curDataAndHash.checkHashCode() val curData = curDataAndHash.value //这里callerContext 就是我们的 runBlocking,main(主线程) //这里是将旧的值给回调用者,然后从调用者获取到新参数 val newData = withContext(callerContext) { transform(curData) } curDataAndHash.checkHashCode() //这里有做数据比较 return if (curData == newData) { curData } else { //写入数据 writeData(newData) //保存到flow.value里面 downstreamFlow.value = Data(newData, newData.hashCode()) newData } } private val SCRATCH_SUFFIX = ".tmp" //写入数据 internal suspend fun writeData(newData: T) { file.createParentDirectories() //这里创建出来的文件是"datastore/$name.preferences_pb.tmp" val scratchFile = File(file.absolutePath + SCRATCH_SUFFIX) try { FileOutputStream(scratchFile).use { stream -> serializer.writeTo(newData, UncloseableOutputStream(stream)) stream.fd.sync() } //重新命名回去file,这里的file是我们目标的文件dataStore名称 if (!scratchFile.renameTo(file)) { //重新命名失败,抛出异常 throw IOException( "Unable to rename $scratchFile." + "This likely means that there are multiple instances of DataStore " + "for this file. Ensure that you are only creating a single instance of " + "datastore for this file." ) } } catch (ex: IOException) { if (scratchFile.exists()) { scratchFile.delete() } throw ex } }
到此,更新值的操作,我们已经全部走完了流程
1、文件的写入是发生在IO层面
2、使用runBlocking是会卡主线程,如果此时存在需要刷新UI的情况,严重会ANR
/** * @key 参数 * @value 具体的值 */ private fun putInt(key:String, value: Int) { runBlocking { dataStore.edit { it[intPreferencesKey(key)] = value } } } public suspend fun DataStore<Preferences>.edit( transform: suspend (MutablePreferences) -> Unit ): Preferences { return this.updateData { it.toMutablePreferences().apply { transform(this) } } } //更新逻辑 private suspend fun handleUpdate(update: Message.Update<T>) { update.ack.completeWith(//通知结果回调 //.....省去 ) } //transform 就是上面的{}里面的内容 override suspend fun updateData(transform: suspend (t: T) -> T): T { val ack = CompletableDeferred<T>() val currentDownStreamFlowState = downstreamFlow.value val updateMsg = Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext) actor.offer(updateMsg) return ack.await() //这里就是等待 update.ack.completeWith的结果返回,所以如果不加这行,是不会卡主线程的 //题外话不加ack.await() 代码也会执行 }
所以,可以考虑使用withContext(IO){读取/更新等待操作}
3、更新参数的时候,是会跟旧的值比较,如果值相同就不写入了,否则就写入到文件里面,并且更新flow.value的值
return if (curData == newData) {
curData
} else {
writeData(newData)
downstreamFlow.value = Data(newData, newData.hashCode())
newData
}
4、解决并发问题,使用channel解决协程之间沟通与并发,单线程的IO更新文件与并发
5、如果已将SharedPreference迁移到DataStore,你就不要继续使用SharedPreferences了,如果继续使用SharedPreferences,会与DataStore的值不同了
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。