在Android上,经常会需要持久化本地数据,比如我们需要缓存用户的配置信息、用户的数据、缓存数据、离线缓存数据等等。我们通常使用的工具为SharePreference、MMKV、DataStore、Room、文件等等。通过使用现有的存储框
在Android上,经常会需要持久化本地数据,比如我们需要缓存用户的配置信息、用户的数据、缓存数据、离线缓存数据等等。我们通常使用的工具为SharePreference、MMKV、DataStore、Room、文件等等。通过使用现有的存储框架,结合协程,我们可以方便地实现一个轻量级的响应式存储框架。
在使用的场景上,我们使用Key-Value的场景很多,而且我们往往不仅仅是存储数据、获取数据,经常还有需要序列化存储、加密存储、订阅数据的变化的功能。
订阅数据的变化,常见的就是使用发布/订阅模式来实现。
但是使用类如EventBus和RxBus并不是一个好的实践,EventBus没有做适当的封装被滥用的话,会导致逻辑混乱,难以跟踪,并且调试起来也相当困难。
谷歌的DataStore就是一个很好的实现。除了DataStore,我们其实也可以使用基于现有的SharePreference、MMKV通过协程等来实现我们的响应式存储框架。
下面我们就来设计这个存储框架。
首先我们基于我们的功能来定义我们的接口
我们的功能如下
由此我们定义了3组接口
在清洁架构的分层中,存储(Storage)是属于一种"接口适配器",因为它为应用的内部业务逻辑(即领域层)提供了与外部世界(即数据库、网络、文件系统等)的接口。一般在Respository中和这些接口适配器进行通讯来获取和存储数据,所以在设计Storage的时候,我们应该遵循下面的概念。
Storage接口定义了一个抽象的存储协议,不关注具体的实现方式,例如使用SharedPreferences,MMKV,或者DataStore,这正是适配器层的职责。通过适配器层,我们可以使得业务逻辑从具体的技术细节中解耦,使其更关注于应用的业务规则,而不是底层的存储细节。
同时,我们的设计要允许我们根据需要,灵活地更换或者修改存储的具体实现,而无需改动业务逻辑或者其他部分的代码。
而这正是清洁架构的一个重要原则:独立性和隔离变化,即依赖抽象而不是具体实现。
基于此设计如下的存储器接口
interface Storage { fun put( key:String, obj:Any?) operator fun get( key: String, classOfT:Class):T? operator fun get( key: String, typeOfT: Type):T? fun contain( key: String):Boolean fun onKeyChanged( key:String): Flow fun remove( key: String) fun removeAllPrefix( prefixKey:String ) fun removeExcludePrefix( vararg prefixKey: String ) fun clear()}inline operator fun Storage.get(key: String): T? { return get(key, T::class.java)}
Storage接口设计将基本的存储操作抽象化,并通过onKeyChanged提供了数据变化的通知,这是一个非常有用的功能,使得可以对存储数据的改变进行反应。
此外,removeAllPrefix和removeExcludePrefix方法也为更精细的数据控制提供了可能性,这在处理具有特定前缀键值对的场景中非常有用。
Storage接口设计的目的是为了隐藏实现细节和提高代码的可读性、可维护性和可扩展性。
下面我们基于此继续扩展我们的Storage功能
首先,我们的数据我们希望是序列化存储的,并且可以支持加密。
因此我们继续定义接口:
interface Serializer { fun serialize(obj: Any): String fun deserialize(obj: String, classOfT: Class): T fun deserialize(obj: String, typeOfT: Type): T}inline fun Serializer.deserialize(obj: String): T = deserialize(obj, T::class.java)
然后是加密和解密接口:
interface CryptoHandler { fun encrypt(obj: String): String fun decrypt(obj: String): String}
接下来我们就可以使用这两个接口来执行序列化、反序列化,加密和解密的操作。
首先MMKV是支持加密的,但是MMKV使用的是AES CFB-128加密算法来做的。但是它并不是那么足够安全,它没有提供硬件级别的安全加密方法。所以可以考虑自己使用Android KeyStore 来实现硬件级别的加密。
使用Android Keystore来实现,一般大致思路就是拿使用Android的keystore 创建一组加密对密钥,然后使用AES算法来加密和解密。
序列化我们可以使用ProtoBuf或者是JSON来实现
下面简单使用gson来实现我们的序列化存储如下:
@Singletonopen class jsonSerializer(private val gson: Gson) : Serializer { override fun serialize(obj: Any): String { return gson.toJson(obj) } override fun deserialize(obj: String, classOfT: Class): T { return gson.fromJson(obj, classOfT) } override fun deserialize(obj: String, typeOfT: Type): T { return gson.fromJson(obj, typeOfT) }}
定义好了接口,实现起来就很简单了,只需要在修改key-value的时候,发送一个key被修改的消息到一个flow,对flow的订阅者就可以订阅数据的改变了。
接下来我们基于MMKV和SharePreference来实现这个存储接口
首先我们来使用SharePreference和MMKV来实现这个存储功能
class SharePreferenceStorage ( private val context: Context, private val storageType: StorageType, private val serializer: Serializer, private val eventLogger: StorageLogger?, private val cryptoHandler: CryptoHandler?):Storage{ private val sharedPreferences: SharedPreferences = context.getSharedPreferences(storageType.alias, Context.MODE_PRIVATE) private val keyChangedFlow = MutableSharedFlow(replay = 100) override fun put(key: String, obj: Any?) { obj?.let {data-> sharedPreferences.edit().let {editor-> editor.putString( key , serializer.serialize( data ).let { cryptoHandler?.encrypt( it )?:it } ) editor.apply() keyChangedFlow.tryEmit( key ) eventLogger?.trackEvent(StorageSaveEvent( getStorageName(),key, cryptoHandler != null)) } }?: run { remove(key) } } override fun get(key: String, classOfT: Class): T? { sharedPreferences.getString( key ,null )?.let { cryptoHandler?.decrypt( it )?:it }?.let { eventLogger?.trackEvent(StorageLoadEvent( getStorageName(),key, true)) serializer.deserialize( it ,classOfT) }?.let { return it }?:run{ return null } } override fun get(key: String, typeOfT: Type): T? { val serializeString = sharedPreferences.getString( key ,null )?.let { cryptoHandler?.decrypt( it )?:it } return serializeString?.let { serializer.deserialize( it ,typeOfT) } } override fun onKeyChanged(key: String): Flow { return keyChangedFlow.asSharedFlow() } override fun contains(key: String): Boolean { return sharedPreferences.contains( key ) } override fun remove(key: String) { if( contains( key ) ){ sharedPreferences.edit().let {editor-> editor.remove( key ) editor.apply() keyChangedFlow.tryEmit( key ) eventLogger?.trackEvent(StorageRemoveEvent( getStorageName(),key)) } } } override fun removeAllPrefix(prefixKey: String) { sharedPreferences.all?.let {allData-> allData.keys.filter { it.startsWith( prefixKey ) }.forEach { remove( it ) } } } override fun removeExcludePrefix(vararg prefixKey: String) { sharedPreferences.all?.let {allData-> val prefixSet = prefixKey.toSet() val allKeys = allData.keys allKeys.forEach { key -> if (prefixSet.none { key.startsWith(it) }) { remove(key) } } } } override fun clear() { sharedPreferences.edit().let {editor-> sharedPreferences.all.keys.forEach { remove( it ) } keyChangedFlow.tryEmit( CLEAR_CACHE ) eventLogger?.trackEvent(StorageClearEvent( getStorageName())) } } private fun getStorageName():String{ return "SharePreference-${storageType.alias}" }}
下面是基于MMKV的实现:
class MMKVStorage constructor( private val storageType: StorageType, private val serializer: Serializer, private val eventLogger: StorageLogger?, private val cryptoHandler: CryptoHandler?): Storage { private val mmkv: MMKV = MMKV.mmkvWithID( storageType.alias, MMKV.MULTI_PROCESS_MODE) private val keyChangedFlow = MutableSharedFlow(replay = 100) private val subscribeKeyList:MutableList = mutableListOf() override fun put(key: String, obj: Any?) { obj?.let { val serializerObj = serializer.serialize( obj ).let { cryptoHandler?.encrypt( it )?:it } mmkv.encode( key,serializerObj) keyChangedFlow.tryEmit(key) eventLogger?.trackEvent(StorageSaveEvent( getStorageName(),key, cryptoHandler != null)) } ?: run{ remove(key) } } override fun get(key: String, classOfT: Class): T? { return mmkv.decodeString( key )?.let{ jsonString-> eventLogger?.trackEvent(StorageLoadEvent( getStorageName(),key, true)) serializer.deserialize(jsonString.let { cryptoHandler?.decrypt(it)?:it },classOfT) } } override fun get(key: String, typeOfT: Type): T? { return mmkv.decodeString( key)?.let { jsonString-> eventLogger?.trackEvent(StorageLoadEvent( getStorageName(),key, true)) serializer.deserialize( jsonString.let { cryptoHandler?.decrypt(it)?:it }, typeOfT) } } override fun onKeyChanged(key: String): Flow { subscribeKeyList.add(key) return keyChangedFlow.asSharedFlow().filter { it == key } } override fun contains(key: String): Boolean { return mmkv.containsKey( key ) } override fun remove(key: String) { mmkv.remove(key).apply() eventLogger?.trackEvent(StorageRemoveEvent( getStorageName(),key)) keyChangedFlow.tryEmit( key ) } override fun removeAllPrefix( prefixKey:String ){ val allKeys = mmkv.allKeys()?.clone()?: emptyArray() allKeys.forEach { if( it.contains(prefixKey)) remove(it) } } override fun removeExcludePrefix(vararg prefixKey: String) { val allKeys = mmkv.allKeys()?.clone() ?: emptyArray() val prefixSet = prefixKey.toSet() allKeys.forEach { key -> if (prefixSet.none { key.startsWith(it) }) { remove(key) } } } override fun clear() { mmkv.allKeys()?.forEach { remove(it) } keyChangedFlow.tryEmit( Storage.CLEAR_CACHE ) mmkv.clearAll() eventLogger?.trackEvent(StorageClearEvent( getStorageName())) } private fun getStorageName():String { return "mmkv-${storageType.alias}" }}
通过上面的代码,我们就可以实现订阅数据的改变。
来源地址:https://blog.csdn.net/savelove911/article/details/132210535
--结束END--
本文标题: Android上的基于协程的存储框架
本文链接: https://lsjlt.com/news/402482.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-21
2023-10-28
2023-10-28
2023-10-27
2023-10-27
2023-10-27
2023-10-27
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0