乡镇网站建设工作计划,网站建设优化服务特色,新媒体培训,个人音乐分享网站源码涉及到的知识点
1、协程原理----很好的博客介绍#xff0c;一个小故事讲明白进程、线程、Kotlin 协程到底啥关系#xff1f; 2、Channel知识点----Android—kotlin-Channel超详细讲解 3、Coroutines : CompletableDeferred and structured concurrency
封装的DataS…涉及到的知识点
1、协程原理----很好的博客介绍一个小故事讲明白进程、线程、Kotlin 协程到底啥关系 2、Channel知识点----Android—kotlin-Channel超详细讲解 3、Coroutines : CompletableDeferred and structured concurrency
封装的DataStoreUtils工具—gitHub
本篇博客目的
公司使用SharedPreferences容易导致ANR调研能否使用DataStore替换公司目前的SharedPreferences解决ANR问题所以需要先研究一下源码
目录
版本引入迁移SharedPreferences数据到dataStore动态创建DataStore存储参数总结
版本引入
implementation androidx.datastore:datastore-preferences:1.0.0迁移SharedPreferences数据到dataStore
既然是迁移数据那么需要将SharedPreferences已存储的数据迁移到dataStore所以需要先构建dataStore。 目前网上构建迁移DataStore的案例Demo如下
//迁移使用
private val Context.dataStore: DataStorePreferences by preferencesDataStore(name userSharePreFile,produceMigrations { context -listOf(SharedPreferencesMigration(context,userSharePreFile))}
)//或
//这种构建DataStore写法是alpha版本有的在1.0.0版本就找不到了
var dataStore: DataStorePreferences context.createDataStore(name userSharePreFile)
//或
//直接构建
private val Context.dataStore: DataStorePreferences by preferencesDataStore(name userSharePreFile
)上面3种写法都是对Context进行扩展创建的DataStore所以上面创建的方式都有一个缺点就是需要提前知道name才能创建如果你之前创建SharedPreferences的方式是通过外部传递进来name构建的话上面直接创建DataStore方式就显然不适合你了。
翻阅旧版本(alpha版本)源码一探究竟如何构建DataStore
//alpha版本构建方式
var dataStore: DataStorePreferences context.createDataStore(name userSharePreFile)fun Context.createDataStore(name: String,corruptionHandler: ReplaceFileCorruptionHandlerPreferences? null,//①migrations: ListDataMigrationPreferences listOf(),//②scope: CoroutineScope CoroutineScope(Dispatchers.IO SupervisorJob())
): DataStorePreferences 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: DataStorePreferences by preferencesDataStore(name userSharePreFile
)public fun preferencesDataStore(name: String,corruptionHandler: ReplaceFileCorruptionHandlerPreferences? null,//①produceMigrations: (Context) - ListDataMigrationPreferences { listOf() },//②scope: CoroutineScope CoroutineScope(Dispatchers.IO SupervisorJob())
): ReadOnlyPropertyContext, DataStorePreferences {return PreferenceDataStoreSingletonDelegate(name, corruptionHandler, produceMigrations, scope)
}internal class PreferenceDataStoreSingletonDelegate internal constructor(private val name: String,private val corruptionHandler: ReplaceFileCorruptionHandlerPreferences?,private val produceMigrations: (Context) - ListDataMigrationPreferences,private val scope: CoroutineScope
) : ReadOnlyPropertyContext, DataStorePreferences {private val lock Any()GuardedBy(lock)Volatileprivate var INSTANCE: DataStorePreferences? nulloverride fun getValue(thisRef: Context, property: KProperty*): DataStorePreferences {return INSTANCE ?: synchronized(lock) {if (INSTANCE null) {val applicationContext thisRef.applicationContextINSTANCE 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.ktpublic fun create(corruptionHandler: ReplaceFileCorruptionHandlerPreferences? null,//迁移的share文件集合migrations: ListDataMigrationPreferences listOf(),//IOscope: CoroutineScope CoroutineScope(Dispatchers.IO SupervisorJob()),//dataStore文件存储的目录位置produceFile: () - File ): DataStorePreferences {val delegate DataStoreFactory.create(//创建SingleProcessDataStoreserializer PreferencesSerializer,corruptionHandler corruptionHandler,migrations migrations,scope scope) {//省略代码} //传入SingleProcessDataStorereturn PreferenceDataStore(delegate)}//这里有主动的去调用updateData 方法如果不去主动调用就不会触发迁移的逻辑
//下文的扩展函数DataStorePreferences.edit会说到这里
internal class PreferenceDataStore(private val delegate: DataStorePreferences) :DataStorePreferences 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: SerializerT,corruptionHandler: ReplaceFileCorruptionHandlerT? null,migrations: ListDataMigrationT listOf(),scope: CoroutineScope CoroutineScope(Dispatchers.IO SupervisorJob())): DataStoreT //找到最终创建的类SingleProcessDataStore(produceFile produceFile,serializer serializer,corruptionHandler corruptionHandler ?: NoOpCorruptionHandler(),initTasksList listOf(DataMigrationInitializer.getInitializer(migrations)),scope scope)到目前为止已经知道真相了最终是通过SingleProcessDataStore返回DataStore。
下面我们通过一张图片来小结一下旧版本alpha版本的创建与新版本 by preferencesDataStore的调用逻辑链 好已经知道这么多了那么我们就开始动态构建DataStore
动态创建DataStore fun preferencesMigrationDataStore(sharedPreferName: String) {val dataStore PreferenceDataStoreFactory.create(corruptionHandler ReplaceFileCorruptionHandlerPreferences(produceNewData { emptyPreferences() }),//需要迁移的sharePrefer文件的名称migrations listOf(SharedPreferencesMigration(mContext, sharedPreferName)),//IOscope 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 DataStorePreferences.edit(transform: suspend (MutablePreferences) - Unit
): Preferences {return this.updateData {//调用的是PreferenceDataStore.updateData()//it.toMutablePreferences() 返回类似mapit.toMutablePreferences().apply { transform(this) }}
}transform 就是调用者{}里面的内容接下来我们看看 PreferenceDataStore 类的代码
//由前部分的代码可以得知delegate SingleProcessDataStore
internal class PreferenceDataStore(private val delegate: DataStorePreferences) :DataStorePreferences 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.ktoverride suspend fun updateData(transform: suspend (t: T) - T): T {val ack CompletableDeferredT()val currentDownStreamFlowState downstreamFlow.value//协程体封装进Message.UpdatecoroutineContext 是协程的上下文就是我们的 runBlocking 启动的线程我这里是mainval updateMsg Message.Update(transform, ack, currentDownStreamFlowState, coroutineContext)//对消息进行分发他的类是 SimpleActoractor.offer(updateMsg)//这里会拿到Preferences如何拿后面会有一个update.ack.completeWith方法会返回回来return ack.await()}internal class SimpleActorT(private val scope: CoroutineScope,//Dispatchers.IO SupervisorJob()onComplete: (Throwable?) - Unit,onUndeliveredElement: (T, Throwable?) - Unit,private val consumeMessage: suspend (T) - Unit
) {private val messageQueue ChannelT(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 SimpleActorMessageT(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.UpdateT) {update.ack.completeWith(runCatching {when (val currentState downstreamFlow.value) {is Data - {//写数据到filetransformAndWrite(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 // wont happen}})}第一次使用 downstreamFlow.value UnInitialized 。 这里要注意一下update.ack.completeWith这个函数他是拿到结果成功返回
这里再次展示出来是告诉大家在哪里会等待结果返回override suspend fun updateData(transform: suspend (t: T) - T): T {val ack CompletableDeferredT()val currentDownStreamFlowState downstreamFlow.valueval 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.htmlval updateLock Mutex()//读取dataStore文件var initData readDataOrHandleCorruption()var initializationComplete: Boolean false//这里就是shareprefence转dataStoreval api object : InitializerApiT {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 nullupdateLock.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 DataTcurDataAndHash.checkHashCode()val curData curDataAndHash.value//这里callerContext 就是我们的 runBlockingmain(主线程)//这里是将旧的值给回调用者然后从调用者获取到新参数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.tmpval 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 DataStorePreferences.edit(transform: suspend (MutablePreferences) - Unit
): Preferences {return this.updateData {it.toMutablePreferences().apply { transform(this) }}
}//更新逻辑private suspend fun handleUpdate(update: Message.UpdateT) {update.ack.completeWith(//通知结果回调//.....省去)}//transform 就是上面的{}里面的内容override suspend fun updateData(transform: suspend (t: T) - T): T {val ack CompletableDeferredT()val currentDownStreamFlowState downstreamFlow.valueval 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的值不同了