返回顶部
首页 > 资讯 > 移动开发 >Android上的基于协程的存储框架
  • 871
分享到

Android上的基于协程的存储框架

android协程MMKVFlowKotlin 2023-09-10 17:09:08 871人浏览 安东尼
摘要

在Android上,经常会需要持久化本地数据,比如我们需要缓存用户的配置信息、用户的数据、缓存数据、离线缓存数据等等。我们通常使用的工具为SharePreference、MMKV、DataStore、Room、文件等等。通过使用现有的存储框

Android上,经常会需要持久化本地数据,比如我们需要缓存用户的配置信息、用户的数据、缓存数据、离线缓存数据等等。我们通常使用的工具为SharePreference、MMKV、DataStore、Room、文件等等。通过使用现有的存储框架,结合协程,我们可以方便地实现一个轻量级的响应式存储框架。

在使用的场景上,我们使用Key-Value的场景很多,而且我们往往不仅仅是存储数据、获取数据,经常还有需要序列化存储、加密存储、订阅数据的变化的功能。

订阅数据的变化,常见的就是使用发布/订阅模式来实现。

但是使用类如EventBus和RxBus并不是一个好的实践,EventBus没有做适当的封装被滥用的话,会导致逻辑混乱,难以跟踪,并且调试起来也相当困难。

谷歌的DataStore就是一个很好的实现。除了DataStore,我们其实也可以使用基于现有的SharePreference、MMKV通过协程等来实现我们的响应式存储框架。

下面我们就来设计这个存储框架。

首先我们基于我们的功能来定义我们的接口

我们的功能如下

  1. 1.支持存储和读取
  2. 2.支持加密和解密
  3. 3.支持序列化和反序列化
  4. 4.支持多“仓库"

由此我们定义了3组接口

  1. 1.Storage 存储器
  2. 2.Serializer 序列化器
  3. 3.CryptoHandler 加密和解密处理器

在清洁架构的分层中,存储(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

猜你喜欢
  • Android上的基于协程的存储框架
    在Android上,经常会需要持久化本地数据,比如我们需要缓存用户的配置信息、用户的数据、缓存数据、离线缓存数据等等。我们通常使用的工具为SharePreference、MMKV、DataStore、Room、文件等等。通过使用现有的存储框...
    99+
    2023-09-10
    android 协程 MMKV Flow Kotlin
  • 基于go的网络存储协议是什么
    这篇“基于go的网络存储协议是什么”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“基于go的网络存储协议是什么”文章吧。一.网...
    99+
    2023-07-05
  • LSPatch —— 一款基于Android的免root框架
    前言 LSPosed(百度百科) 是一个基于 Riru 的 ART hook 框架 (最初用于 Android Pie) ,提供与原版 Xposed 相同的 API, 使用 YAHFA (或 SandHook) 进行 hook, 支...
    99+
    2023-09-03
    android adb
  • Android存储访问框架的使用小结
    目录打开系统文件选择器与文件过滤打开指定文件夹文件夹权限申请创建文件夹存储访问框架API获取文件夹文件和MediaStore API的不同存储访问框架,简称:SAF, 就是系统文件选...
    99+
    2024-04-02
  • 基于业务分离的Android开发框架MVB
    传统的安卓开发 传统的安卓开发,对于同一个界面上的功能,往往都是把功能的实现代码写到同一个Activity类中,这样子,各种各样的业务功能实现代...
    99+
    2022-06-06
    android开发 Android
  • 基于MFS高可用的分布式存储架构
    MFS是一个具有容错性的网络分布式文件系统,它把数据分散存放在多个物理服务器上,而呈现给用户的则是一个统一的资源;分布式文件系统就是把一些分散在多台计算机上的共享文件夹,集合到一个共享文件夹内,用户要访问这些文件夹的时候,只要打开一个文件夹...
    99+
    2023-06-03
  • 基于python3.5+的web框架s
    简介 sanic是一款用python3.5+写的web framework,用法和flask类似,sanic的特点是非常快github官网:https://github.com/channelcat... 速度比较 框架 实现基础 每...
    99+
    2023-01-31
    框架 web
  • 基于SQL2005的CLR存储过程是怎样的
    基于SQL2005的CLR存储过程是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。在 SQL Server 2005 中,可以在SQL Server实例中...
    99+
    2023-06-17
  • 基于vue的框架有哪些
    基于vue的框架有:1.Vux;2.mint-ui;3.elementui;4.Iviewui;5.vant等。框架简介:Vux,是基于WeUI和vue(2.x)开发的移动端UI组件库,主要服务于微信页面。mint-ui,它依托了vue.j...
    99+
    2024-04-02
  • 基于Laravel5框架的PHP程序怎么优化
    这篇文章主要讲解了“基于Laravel5框架的PHP程序怎么优化”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“基于Laravel5框架的PHP程序怎么优化”吧!1. 配置信息缓存使用以下 A...
    99+
    2023-07-04
  • 关于Android SDCard存储的问题
    使用Activit的openFileOutput()方法保存文件,文件是放在手机内在上;注:模拟器中创建SDCard卡镜像文件,可以在创建模拟器是创建,也可以在Dos窗口中进行...
    99+
    2022-06-06
    Android
  • 基于mybatis的java代码生成存储过程
     问题:   项目中目前使用mybatis操作数据库,使用插件(mybatis-generator)自动生成代码,对于增改查,使用存储过程实现了一版本,方便使用。         insert代码生成器用法: insert_code_g...
    99+
    2020-03-08
    基于mybatis的java代码生成存储过程
  • 基于asyncio 异步协程框架实现收集B站直播弹幕
    前言 虽然标题是全站,但目前只做了等级 top 100 直播间的全天弹幕收集。 弹幕收集系统基于之前的B 站直播弹幕姬 Python 版修改而来。具体协议分析可以看上一篇文章。 直播弹幕协议是直接基于...
    99+
    2022-06-04
    站直 框架 弹幕
  • 基于python的scrapy框架爬取豆
    1.Scrapy框架介绍 主要介绍,spiders,engine,scheduler,downloader,Item pipeline scrapy常见命令如下:   对应在scrapy文件中有,自己增加爬虫文件,系统生成items,...
    99+
    2023-01-30
    框架 python scrapy
  • 基于fastapi框架的异步解读
    目录使用fastapi框架开发web项目1、为什么要用fastapi?2、什么是异步?3、在django中是怎么实现异步的呢?4、fastapi中的异步4.1 简单介绍一下协程的概念...
    99+
    2023-03-15
    fastapi框架 fastapi框架的异步 fastapi异步
  • 基于python的REST框架eve测试
    Eve是一款Python的REST API框架,用于构建和部署高可定制的、全功能的RESTful的Web服务。Eve是一个开源项目,遵循BSD开源协议,已在Python 2.6、2.7以及Python 3.3版本下进行了非常全面的测试。特色...
    99+
    2023-01-31
    框架 测试 python
  • Android基于Http协议实现文件上传功能的方法
    本文实例讲述了Android基于Http协议实现文件上传功能的方法。分享给大家供大家参考,具体如下: 注意一般使用Http协议上传的文件都比较小,一般是小于2M 这里示例是上传...
    99+
    2022-06-06
    HTTP 方法 文件上传 http协议 Android
  • 你知道哪些适用于go的存储框架和容器吗?
    在现代化的应用程序中,数据存储是不可避免的。Go语言是一种流行的编程语言,它提供了一些可靠的存储框架和容器,以使数据管理更加便捷。在本文中,我们将介绍一些适用于Go语言的存储框架和容器。 存储框架 1. GORM GORM是Go语言中一个流...
    99+
    2023-10-07
    存储 框架 容器
  • 关于MySQL的存储过程与存储函数
    目录初识存储过程存储过程语法存储过程调用存储函数的使用语法函数的调用对比存储函数和存储过程初识存储过程 理解:含义: 存储过程(Stored Procedure)是在大型数据库系统中...
    99+
    2023-05-19
    MySQL存储过程 MySQL存储函数
  • java协程框架quasar和kotlin中的协程对比分析
    目录前言快速体验添加依赖添加java agent线程VS协程协程代码多线程代码协程完胜后记前言 早就听说Go语言开发的服务不用任何架构优化,就可以轻松实现百万级别的qps。这得益于G...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作