当前位置: 首页 > news >正文

中咨城建设计南京网站昆明网站推广专员

中咨城建设计南京网站,昆明网站推广专员,小型旅游网站建设方案,晋中企业网站建设公司目录介绍 01.整体概述说明 1.1 项目背景介绍1.2 遇到问题记录1.3 基础概念介绍1.4 设计目标1.5 产生收益分析 02.市面存储方案 2.1 缓存存储有哪些2.2 缓存策略有哪些2.3 常见存储方案2.4 市面存储方案说明2.5 存储方案的不足 03.存储方案原理 3.1 Sp存储原理分析3.2 MMKV存储…目录介绍 01.整体概述说明 1.1 项目背景介绍1.2 遇到问题记录1.3 基础概念介绍1.4 设计目标1.5 产生收益分析 02.市面存储方案 2.1 缓存存储有哪些2.2 缓存策略有哪些2.3 常见存储方案2.4 市面存储方案说明2.5 存储方案的不足 03.存储方案原理 3.1 Sp存储原理分析3.2 MMKV存储原理分析3.3 LruCache考量分析3.4 DiskLru原理分析3.5 DataStore分析3.6 HashMap存储分析3.7 Sqlite存储分析3.8 使用存储的注意点3.9 各种数据存储文件 04.通用缓存方案思路 4.1 如何兼容不同缓存4.2 打造通用缓存Api4.3 切换不同缓存方式4.4 缓存的过期处理4.5 缓存的阀值处理4.6 缓存的线程安全性4.7 缓存数据的迁移4.8 缓存数据加密处理4.9 缓存效率的对比 05.方案基础设计 5.1 整体架构图5.2 UML设计图5.3 关键流程图5.4 模块间依赖关系 06.其他设计说明 6.1 性能设计说明6.2 稳定性设计6.3 灰度设计6.4 降级设计6.5 异常设计说明6.6 兼容性设计6.7 自测性设计 07.通用Api设计 7.1 如何依赖该库7.2 初始化缓存库7.3 切换各种缓存方案7.4 数据的存和取7.5 线程安全考量7.6 查看缓存文件数据7.7 如何选择合适方案 08.其他说明介绍 8.1 遇到的坑分析8.2 遗留的问题8.3 未来的规划8.4 参考链接记录 01.整体概述说明 1.1 项目背景介绍 项目中很多地方使用缓存方案 有的用sp有的用mmkv有的用lru有的用DataStore有的用sqlite如何打造通用api切换操作不同存储方案 缓存方案众多且各自使用场景有差异如何选择合适的缓存方式 针对不同场景选择什么缓存方式同时思考如何替换之前老的存储方案而不用花费很大的时间成本 针对不同的业务场景不同的缓存方案。打造一套通用的方案 屏蔽各种缓存方式的差异性暴露给外部开发者统一的API外部开发者简化使用提高开发效率和使用效率…… 1.2 遇到问题记录 记录几个常见的问题 问题1各种缓存方案分别是如何保证数据安全的其内部使用到了哪些锁由于引入锁给效率上带来了什么影响问题2各种缓存方案进程不安全是否会导致数据丢失如何处理数据丢失情况如何处理脏数据其原理大概是什么问题3各种缓存方案使用场景是什么有什么缺陷为了解决缺陷做了些什么比如sp存在缺陷的替代方案是DataStore为何这样问题4各种缓存方案他们的缓存效率是怎样的如何对比接入该库后如何做数据迁移如何覆盖操作 思考一个K-V框架的设计 问题1-线程安全使用K-V存储一般会在多线程环境中执行因此框架有必要保证多线程并发安全并且优化并发效率问题2-内存缓存由于磁盘 IO 操作是耗时操作因此框架有必要在业务层和磁盘文件之间增加一层内存缓存问题3-事务由于磁盘 IO 操作是耗时操作因此框架有必要将支持多次磁盘 IO 操作聚合为一次磁盘写回事务减少访问磁盘次数问题4-事务串行化由于程序可能由多个线程发起写回事务因此框架有必要保证事务之间的事务串行化避免先执行的事务覆盖后执行的事务问题5-异步或同步写回由于磁盘 IO 是耗时操作因此框架有必要支持后台线程异步写回有时候又要求数据读写是同步的问题6-增量更新由于磁盘文件内容可能很大因此修改 K-V 时有必要支持局部修改而不是全量覆盖修改问题7-变更回调由于业务层可能有监听 K-V 变更的需求因此框架有必要支持变更回调监听并且防止出现内存泄漏问题8-多进程由于程序可能有多进程需求那么框架如何保证多进程数据同步问题9-可用性由于程序运行中存在不可控的异常和 Crash因此框架有必要尽可能保证系统可用性尽量保证系统在遇到异常后的数据完整性问题10-高效性性能永远是要考虑的问题解析、读取、写入和序列化的性能如何提高和权衡问题11-安全性如果程序需要存储敏感数据如何保证数据完整性和保密性问题12-数据迁移如果项目中存在旧框架如何将数据从旧框架迁移至新框架并且保证可靠性问题13-研发体验是否模板代码冗长是否容易出错。各种K—V框架使用体验如何 常见存储框架设计思考导图 1.3 基础概念介绍 最初缓存的概念 提及缓存可能很容易想到Http的缓存机制LruCache其实缓存最初是针对于网络而言的也是狭义上的缓存广义的缓存是指对数据的复用。 缓存容量就是缓存的大小 每一种缓存总会有一个最大的容量到达这个限度以后那么就须要进行缓存清理了框架。这个时候就需要删除一些旧的缓存并添加新的缓存。 1.4 设计目标 打造通用存储库 设计一个缓存通用方案其次它的结构需要很简单因为很多地方需要用到再次它得线程安全。灵活切换不同的缓存方式使用简单。 内部开源该库 作为技术沉淀当作专项来推动进展。高复用低耦合便于拓展可快速移植解决各个项目使用内存缓存spmmkvsqllruDataStore的凌乱。抽象一套统一的API接口。 1.5 产生收益分析 统一缓存API兼容不同存储方案 打造通用api抹平了spmmkvsqllrudataStore等各种方案的差异性。简化开发者使用功能强大而使用简单 02.市面存储方案 2.1 缓存存储有哪些 比较常见的是内存缓存以及磁盘缓存。 内存缓存这里的内存主要指的存储器缓存磁盘缓存这里主要指的是外部存储器手机的话指的就是存储卡。 内存缓存 通过预先消耗应用的一点内存来存储数据便可快速的为应用中的组件提供数据是一种典型的以空间换时间的策略。 磁盘缓存 读取磁盘文件要比直接从内存缓存中读取要慢一些而且需要在一个UI主线程外的线程中进行因为磁盘的读取速度是不能够保证的磁盘文件缓存显然也是一种以空间换时间的策略。 二级缓存 内存缓存和磁盘缓存结合。比如LruCache将图片保存在内存存取速度较快退出APP后缓存会失效而DiskLruCache将图片保存在磁盘中下次进入应用后缓存依旧存在它的存取速度相比LruCache会慢上一些。 2.2 缓存策略有哪些 缓存的核心思想主要是什么呢 一般来说缓存核心步骤主要包含缓存的添加、获取和删除这三类操作。那么为什么还要删除缓存呢不管是内存缓存还是硬盘缓存它们的缓存大小都是有限的。当缓存满了之后再想其添加缓存这个时候就需要删除一些旧的缓存并添加新的缓存。这个跟线程池满了以后的线程处理策略相似 缓存的常见的策略有那些 FIFO(first in first out)先进先出策略相似队列。LFU(less frequently used)最少使用策略RecyclerView的缓存采用了此策略。LRU(least recently used):最近最少使用策略Glide在进行内存缓存的时候采用了此策略。 2.3 常见存储方案 内存缓存存储在内存中如果对象销毁则内存也会跟随销毁。如果是静态对象那么进程杀死后内存会销毁。 MapLruCache等等 磁盘缓存后台应用有可能会被杀死那么相应的内存缓存对象也会被销毁。当你的应用重新回到前台显示时你需要用到缓存数据时这个时候可以用磁盘缓存。 SharedPreferencesMMKVDiskLruCacheSqlLiteDataStoreRoomRealmGreenDao等等 2.4 市面存储方案说明 内存缓存 Map内存缓存一般用HashMap存储一些数据主要存储一些临时的对象LruCache内存淘汰缓存内部使用LinkedHashMap会淘汰最长时间未使用的对象 磁盘缓存 SharedPreferences轻量级磁盘存储一般存储配置属性线程安全。建议不要存储大数据不支持跨进程MMKV腾讯开源存储库内部采用mmap。DiskLruCache磁盘淘汰缓存写入数据到file文件SqlLite移动端轻量级数据库。主要是用来对象持久化存储。DataStore旨在替代原有的 SharedPreferences支持SharedPreferences数据的迁移Room/Realm/GreenDao支持大型或复杂数据集 其他开源缓存库 ACache一款高效二级存储库采用内存缓存和磁盘缓存 2.5 存储方案的不足 存储方案SharedPreferences的不足 1.SP用内存层用HashMap保存磁盘层则是用的XML文件保存。每次更改都需要将整个HashMap序列化为XML格式的报文然后整个写入文件。2.SP读写文件不是类型安全的且没有发出错误信号的机制缺少事务性API3.commit() / apply()操作可能会造成ANR问题 存储方案MMKV的不足 1.没有类型信息不支持getAll。由于没有记录类型信息MMKV无法自动反序列化也就无法实现getAll接口。2.需要引入so增加包体积引入MMKV需要增加的体积还是不少的。3.文件只增不减MMKV的扩容策略还是比较激进的而且扩容之后不会主动trim size。 存储方案DataStore的不足 1.只是提供异步API没有提供同步API方法。在进行大量同步存储的时候使用runBlocking同步数据可能会卡顿。2.对主线程执行同步 I/O 操作可能会导致 ANR 或界面卡顿。可以通过从 DataStore 异步预加载数据来减少这些问题。 03.存储方案原理 3.1 Sp存储原理分析 SharedPreferences它是一个轻量级的存储类特别适合用于保存软件配置参数。 轻量级以键值对的方式进行存储。采用的是xml文件形式存储在本地程序卸载后会也会一并被清除不会残留信息。线程安全的。 它有一些弊端如下所示 对文件IO读取因此在IO上的瓶颈是个大问题因为在每次进行get和commit时都要将数据从内存写入到文件中或从文件中读取。多线程场景下效率较低在get操作时会锁定SharedPreferences对象互斥其他操作而当putcommit时则会锁定Editor对象使用写入锁进行互斥在这种情况下效率会降低。不支持跨进程通讯由于每次都会把整个文件加载到内存中不建议存储大的文件内容比如大json。 有一些使用上的建议如下 建议不要存储较大数据频繁修改的数据修改后统一提交而不是修改过后马上提交在跨进程通讯中不去使用键值对不宜过多 读写操作性能分析 第一次通过Context.getSharedPreferences()进行初始化时对xml文件进行一次读取并将文件内所有内容即所有的键值对缓到内存的一个Map中接下来所有的读操作只需要从这个Map中取就可以 3.2 MMKV存储原理分析 早期微信的需求 微信聊天对话内容中的特殊字符所导致的程序崩溃是一类很常见、也很需要快速解决的问题而哪些字符会导致程序崩溃是无法预知的。只能等用户手机上的微信崩溃之后再利用类似时光倒流的回溯行为看看上次软件崩溃的最后一瞬间用户收到或者发出了什么消息再用这些消息中的文字去尝试复现发生过的崩溃最终试出有问题的字符然后针对性解决。 该需求对应的技术考量 考量1把聊天页面的显示文字写到手机磁盘里才能在程序崩溃、重新启动之后通过读取文件的方式来查看。但这种方式涉及到io流读写且消息多会有性能问题。考量2App程序都崩溃了如何保证要存储的内容都写入到磁盘中呢考量3保存聊天内容到磁盘的行为这个做成同步还是异步呢如果是异步如何保证聊天消息的时序性考量4如何存储数据是同步行为针对群里聊天这么多消息如何才能避免卡顿呢考量5存储数据放到主线程中用户在群聊天页面猛滑消息如何爆发性集中式对磁盘写入数据 MMKV存储框架介绍 MMKV 是基于 mmap 内存映射的 key-value 组件底层序列化/反序列化使用 protobuf 实现性能高稳定性强。 MMKV设计的原理 内存准备通过 mmap 内存映射文件提供一段可供随时写入的内存块App 只管往里面写数据由操作系统负责将内存回写到文件不必担心 crash 导致数据丢失。数据组织数据序列化方面我们选用 protobuf 协议pb 在性能和空间占用上都有不错的表现。写入优化考虑到主要使用场景是频繁地进行写入更新需要有增量更新的能力。考虑将增量 kv 对象序列化后append 到内存末尾。空间增长使用 append 实现增量更新带来了一个新的问题就是不断 append 的话文件大小会增长得不可控。需要在性能和空间上做个折中。 MMKV诞生的背景 针对该业务高频率同步大量数据写入磁盘的需求。不管用sp还是store还是disk还是数据库只要在主线程同步写入磁盘会很卡。解决方案就是使用内存映射mmap的底层方法相当于系统为指定文件开辟专用内存空间内存数据的改动会自动同步到文件里。用浅显的话说MMKV就是实现用「写入内存」的方式来实现「写入磁盘」的目标。内存的速度多快呀耗时几乎可以忽略这样就把写磁盘造成卡顿的问题解决了。 3.3 LruCache考量分析 在LruCache的源码中关于LruCache有这样的一段介绍 cache对象通过一个强引用来访问内容。每次当一个item被访问到的时候这个item就会被移动到一个队列的队首。当一个item被添加到已经满了的队列时这个队列的队尾的item就会被移除。 LruCache核心思想 LRU是近期最少使用的算法它的核心思想是当缓存满时会优先淘汰那些近期最少使用的缓存对象。采用LRU算法的缓存有两种LrhCache和DiskLruCache分别用于实现内存缓存和硬盘缓存其核心思想都是LRU缓存算法。 LruCache使用是计数or计量 使用计数策略1、Message 消息对象池最多缓存 50 个对象2、OkHttp 连接池默认最多缓存 5 个空闲连接3、数据库连接池使用计量策略1、图片内存缓存2、位图池内存缓存那么思考一下如何理解 计数 or 计量 针对计数策略使用Lru仅仅只统计缓存单元的个数针对计量则要复杂一点。 LruCache策略能否增加灵活性 在缓存容量满时淘汰除了这个策略之外能否再增加一些辅助策略例如在 Java 堆内存达到某个阈值后对 LruCache 使用更加激进的清理策略。比如Glide 除了采用 LRU 策略淘汰最早的数据外还会根据系统的内存紧张等级 onTrimMemory(level) 及时减少甚至清空 LruCache。 /*** 这里是参考glide中的lru缓存策略低内存的时候清除* param level level级别*/ public void trimMemory(int level) {if (level android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {clearMemory();} else if (level android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN || level android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {trimToSize(maxSize() / 2);} }关于Lru更多的原理解读可以看AppLruCache 3.4 DiskLru原理分析 DiskLruCache 用于实现存储设备缓存即磁盘缓存它通过将缓存对象写入文件系统从而实现缓存的效果。 DiskLruCache最大的特点就是持久化存储所有的缓存以文件的形式存在。在用户进入APP时它根据日志文件将DiskLruCache恢复到用户上次退出时的情况日志文件journal保存每个文件的下载、访问和移除的信息在恢复缓存时逐行读取日志并检查文件来恢复缓存。 DiskLruCache缓存基础原理流程图 关于DiskLruCache更多的原理解读可以看AppLruDisk 3.5 DataStore分析 为何会有DataStore DataStore 被创造出来的目标就是替代 Sp而它解决的 SharedPreferences 最大的问题有两点一是性能问题二是回调问题。 DataStore优势是异步Api DataStore 的主要优势之一是异步API所以本身并未提供同步API调用但实际上可能不一定始终能将周围的代码更改为异步代码。 提出一个问题和思考 如果使用现有代码库采用同步磁盘 I/O或者您的依赖项不提供异步API那么如何将DataStore存储数据改成同步调用 使用阻塞式协程消除异步差异 使用 runBlocking() 从 DataStore 同步读取数据。runBlocking()会运行一个新的协程并阻塞当前线程直到内部逻辑完成所以尽量避免在UI线程调用。 频繁使用阻塞式协程会有问题吗 要注意的一点是不用在初始读取时调用runBlocking会阻塞当前执行的线程因为初始读取会有较多的IO操作耗时较长。更为推荐的做法则是先异步读取到内存后后续有需要可直接从内存中拿而非运行同步代码阻塞式获取。 3.6 HashMap存储分析 内存缓存的场景 比如 SharedPreferences 存储中就做了内存缓存的操作。 3.7 Sqlite存储分析 注意缓存的数据库是存放在/data/data/databases/目录下是占用内存空间的如果缓存累计容易浪费内存需要及时清理缓存。 3.8 使用缓存注意点 在使用内存缓存的时候须要注意防止内存泄露使用磁盘缓存的时候注意确保缓存的时效性针对SharedPreferences使用建议有 因为 SharedPreferences 虽然是全量更新的模式但只要把保存的数据用合适的逻辑拆分到多个不同的文件里全量更新并不会对性能造成太大的拖累。它设计初衷是轻量级建议当存储文件中key-value数据超过30个如果超过30个这个只是一个假设则开辟一个新的文件进行存储。建议不同业务模块的数据分文件存储…… 针对MMKV使用建议有 如果项目中有高频率同步存储数据使用MMKV更加友好。 针对DataStore使用建议有 建议在初始化的时候使用全局上下文Context给DataStore设置存储路径。 针对LruCache缓存使用建议 如果你使用“计量”淘汰策略需要重写 SystemLruCache#sizeOf() 测量缓存单元的内存占用量否则缓存单元的大小默认视为 1相当于 maxSize 表示的是最大缓存数量。 3.9 各种数据存储文件 SharedPreferences 存储文件格式如下所示?xml version1.0 encodingutf-8 standaloneyes ? mapstring namename杨充/stringint nameage value28 /boolean namemarried valuetrue / /mapMMKV 存储文件格式如下所示 MMKV的存储结构分了两个文件一个数据文件一个校验文件crc结尾。大概如下所示这种设计最直接问题就是占用空间变大了很多举一个例子只存储了一个字段但是为了方便MMAP映射磁盘直接占用了8k的存储。 LruDiskCache 存储文件格式如下所示 DataStore 存储文件格式如下所示 04.通用缓存方案思路 4.1 如何兼容不同缓存 定义通用的存储接口 不同的存储方案由于api不一样所以难以切换操作。要是想兼容不同存储方案切换就必须自己制定一个通用缓存接口。定义接口然后各个不同存储方案实现接口重写抽象方法。调用的时候获取接口对象调用api这样就可以统一Api 定义一个接口这个接口有什么呢 主要是存和取各种基础类型数据比如saveInt/readIntsaveString/readString等通用抽象方法 4.2 打造通用缓存Api 通用缓存Api设计思路 通用一套api 不同接口实现 代理类 工厂模型 定义缓存的通用API接口这里省略部分代码interface ICacheable {fun saveXxx(key: String, value: Int)fun readXxx(key: String, default: Int 0): Intfun removeKey(key: String)fun totalSize(): Longfun clearData() }基于接口而非实现编程的设计思想 将接口和实现相分离封装不稳定的实现暴露稳定的接口。上游系统面向接口而非实现编程不依赖不稳定的实现细节这样当实现发生变化的时候上游系统的代码基本上不需要做改动以此来降低耦合性提高扩展性。 4.3 切换不同缓存方式 传入不同类型方便创建不同存储方式 隐藏存储方案创建具体细节开发者只需要关心所需产品对应的工厂无须关心创建细节甚至无须知道具体存储方案的类名。需要符合开闭原则 那么具体该怎么实现呢 看到下面代码是不是有种很熟悉的感觉没错正是使用了工厂模式灵活切换不同的缓存方式。但针对应用层调用api却感知不到影响。 public static ICacheable getCacheImpl(Context context, CacheConstants.CacheType int type) {if (type CacheConstants.CacheType.TYPE_DISK) {return DiskFactory.create().createCache(context);} else if (type CacheConstants.CacheType.TYPE_LRU) {return LruCacheFactory.create().createCache(context);} else if (type CacheConstants.CacheType.TYPE_MEMORY) {return MemoryFactory.create().createCache(context);} else if (type CacheConstants.CacheType.TYPE_MMKV) {return MmkvFactory.create().createCache(context);} else if (type CacheConstants.CacheType.TYPE_SP) {return SpFactory.create().createCache(context);} else if (type CacheConstants.CacheType.TYPE_STORE) {return StoreFactory.create().createCache(context);} else {return MmkvFactory.create().createCache(context);} }4.4 缓存的过期处理 说一个使用场景 比如你准备做WebView的资源拦截缓存针对模版页面为了提交加载速度。会缓存cssjs图片等资源到本地。那么如何选择存储方案如何处理过期问题 思考一下该问题 比如WebView缓存方案是数据库存储db文件。针对缓存数据猜想思路可能是Lru策略或者标记时间清除过期文件。 那么缓存过期处理的策略有哪些 定时过期每个设置过期时间的key都需要创建⼀个定时器到过期时间就会立即清除。惰性过期只有当访问⼀个 key 时才会判断该key是否已过期过期则清除。定期过期每隔⼀定的时间会扫描⼀定数量的数据库的 expires 字典中⼀定数量的key是随机的 并 清除其中已过期的key 。分桶策略定期过期的优化将过期时间点相近的 key 放在⼀起按时间扫描分桶。 4.5 缓存的阀值处理 淘汰一个最早的节点就足够吗以Lru缓存为案例做分析…… 标准的 LRU 策略中每次添加数据时最多只会淘汰一个数据但在 LRU 内存缓存中只淘汰一个数据单元往往并不够。例如在使用 “计量” 的内存图片缓存中在加入一个大图片后只淘汰一个图片数据有可能依然达不到最大缓存容量限制。 那么在LRUCache该如何做呢 在复用 LinkedHashMap 实现 LRU 内存缓存时前文提到的 LinkedHashMap#removeEldestEntry() 淘汰判断接口可能就不够看了因为它每次最多只能淘汰一个数据单元。 LruCache是如何解决这个问题 这个地方就需要重写LruCache中的sizeOf()方法然后拿到key和value对象计算其内存大小。 4.6 缓存的线程安全性 为何要强调缓存方案线程安全性 缓存虽好用起来很快捷方便但在使用过程中大家一定要注意数据更新和线程安全不要出现脏数据。 针对LruCache中使用LinkedHashMap读写不安全情况 保证LruCache的线程安全在putget等核心方法中添加synchronized锁。这里主要是synchronized (this){ put操作 } 针对DiskLruCache读写不安全的情况 DiskLruCache 管理多个 Entrykey-values因此锁粒度应该是 Entry 级别的。get 和 edit 方法都是同步方法保证内部的 Entry Map 的安全访问是保证线程安全的第一步。 4.7 缓存数据的迁移 如何将Sp数据迁移到DataStore 通过属性委托的方式创建DataStore基于已有的SharedPreferences文件进行创建DataStore。将sp文件名以参数的形式传入preferencesDataStoreDataStore会自动将该文件中的数据进行转换。 val Context.dataStore: DataStorePreferences by preferencesDataStore(name user_info,produceMigrations { context -listOf(SharedPreferencesMigration(context, sp_file_name))})如何将sp数据迁移到MMKV MMKV 提供了 importFromSharedPreferences() 函数可以比较方便地迁移数据过来。MMKV 还额外实现了一遍 SharedPreferences、SharedPreferences.Editor 这两个 interface。 MMKV preferences MMKV.mmkvWithID(myData); // 迁移旧数据 {SharedPreferences old_man getSharedPreferences(myData, MODE_PRIVATE);preferences.importFromSharedPreferences(old_man);old_man.edit().clear().commit(); }思考一下MMKV框架实现了sp的两个接口即磨平了数据迁移差异性 那么使用这个方式借鉴该思路你能否尝试用该方法去实现LruDiskCache方案的sp数据一键迁移。 4.8 缓存数据加密 思考一下如果让你去设计数据的加密你该怎么做 具体可以参考MMKV的数据加密过程。 4.9 缓存效率的对比 测试数据 测试写入和读取。注意分别使用不同的方式测试存储或获取相同的数据(数据为int类型数字还有String类型长字符串)。然后查看耗时时间的长短…… 比较对象 SharePreferences/DataStore/MMKV/LruDisk/Room。使用华为手机测试 测试数据案例1 在主线程中测试数据同步耗时时间(主线程还有其他的耗时)跟异步场景有较大差别。 测试数据案例2 测试1000组长字符串数据MMKV 就不具备优势了反而成了耗时最久的而这时候的冠军就成了 DataStore并且是遥遥领先。 最后思考说明 从最终的数据来看这几种方案都不是很慢。虽然这半秒左右的主线程耗时看起来很可怕但是要知道这是 1000 次连续写入的耗时。而在真正写程序的时候怎么会一次性做 1000 次的长字符串的写入所以真正在项目中的键值对写入的耗时不管你选哪个方案都会比这份测试结果的耗时少得多的都少到了可以忽略的程度这是关键。 05.方案基础设计 5.1 整体架构图 统一存储方案的架构图 5.2 UML设计图 通用存储方案UML设计图 5.3 代码说明图 项目中代码相关说明图 5.4 关键流程图 mmap的零拷贝流程图 5.5 模块间依赖关系 存储库依赖的关系 MMKV需要依赖一些腾讯开源库的服务DataStore存储需要依赖datastore相关的库LruDisk存储需要依赖disk库如果你要拓展其他的存储方案则需要添加其依赖。需要注意添加的库使用compileOnly。 06.其他设计说明 6.1 性能设计 关于基础库性能如何考量 具体性能可以参考测试效率的对比。 6.2 稳定性设计 针对多进程初始化 遇到问题对于多进程在Application的onCreate创建几次导致缓存存储库初始化了多次。问题分析该场景不是该库的问题建议判断是否是主进程如果是则进行初始化。如何解决思路是获取当前进程名并与主进程对比来判断是否为主进程。具体可以参考优雅判断是否是主进程 6.3 灰度设计 暂无灰度设计 6.4 降级设计 由于缓存方式众多在该库中配置了降级如何设置降级//设置是否是debug模式 CacheConfig cacheConfig builder.monitorToggle(new IMonitorToggle() {Overridepublic boolean isOpen() {//todo 是否降级如果降级则不使用该功能。留给AB测试开关return true;}})//创建.build(); CacheInitHelper.INSTANCE.init(this,cacheConfig);降级后的逻辑处理是 如果是降级逻辑则默认使用谷歌官方存储框架SharedPreferences。默认是不会降级的 if (CacheInitHelper.INSTANCE.isToggleOpen()){//如果是降级则默认使用spreturn SpFactory.create().createCache(); }6.5 异常设计说明 DataStore初始化遇到的坑 遇到问题不能将DataStore初始化代码写到Activity里面去否则重复进入Activity并使用Preferences DataStore时会尝试去创建一个同名的.preferences_pb文件。问题分析SingleProcessDataStore#check(!activeFiles.contains(it))该方法会检查如果判断到activeFiles里已经有该文件直接抛异常即不允许重复创建。如何解决在项目中只在顶层调用一次 preferencesDataStore 方法这样可以更轻松地将 DataStore 保留为单例。 MMKV遇到的坑说明 MMKV 是有数据损坏的概率的MMKV 的 GitHub wiki 页面显示微信的 iOS 版平均每天有 70 万次的数据校验不通过即数据损坏。 6.6 兼容性设计 MMKV数据迁移比较难 MMKV都是按字节进行存储的实际写入文件把类型擦除了这也是MMKV不支持getAll的原因虽然说getAll用的不多问题不大但是MMKV因此就不具备导出和迁移的能力。比较好的方案是每次存储多用一个字节来存储数据类型这样占用的空间也不会大很多但是具备了更好的可扩展性。 6.7 自测性设计 MMKV不太方便查看数据和解析数据 官方目前支持了5个平台Android、iOS、Win、MacOS、python但是没有提供解析数据的工具数据文件和crc都是字节码除了中文能看出一些内容直接查看还是存在大量乱码。比如线上出了问题把用户的存储文件捞上来还得替换到系统目录里通过代码断点去看这也太不方便了。 SpFastSpDiskCacheStore等支持查看文件解析数据 傻瓜式的查看缓存文件操作缓存文件。具体看该库MonitorFileLib磁盘查看工具 07.通用Api设计 7.1 如何依赖该库 依赖该库如下所示//通用缓存存储库支持spfastspmmkvlruCacheDiskLruCache等 implementation com.github.yangchong211.YCCommonLib:AppBaseStore:1.4.87.2 初始化缓存库 通用存储库初始化CacheConfig.Builder builder CacheConfig.Companion.newBuilder(); //设置是否是debug模式 CacheConfig cacheConfig builder.debuggable(BuildConfig.DEBUG)//设置外部存储根目录.extraLogDir(null)//设置lru缓存最大值.maxCacheSize(100)//内部存储根目录.logDir(null)//创建.build(); CacheInitHelper.INSTANCE.init(MainApplication.getInstance(),cacheConfig); //最简单的初始化 //CacheInitHelper.INSTANCE.init(CacheConfig.Companion.newBuilder().build());7.3 切换各种缓存方案 如何调用api切换各种缓存方案//这里可以填写不同的type val cacheImpl CacheFactoryUtils.getCacheImpl(CacheConstants.CacheType.TYPE_SP)7.4 数据的存和取 存储数据和获取数据//存储数据 dataCache.saveBoolean(cacheKey1,true); dataCache.saveFloat(cacheKey2,2.0f); dataCache.saveInt(cacheKey3,3); dataCache.saveLong(cacheKey4,4); dataCache.saveString(cacheKey5,doubi5); dataCache.saveDouble(cacheKey6,5.20);//获取数据 boolean data1 dataCache.readBoolean(cacheKey1, false); float data2 dataCache.readFloat(cacheKey2, 0); int data3 dataCache.readInt(cacheKey3, 0); long data4 dataCache.readLong(cacheKey4, 0); String data5 dataCache.readString(cacheKey5, ); double data6 dataCache.readDouble(cacheKey5, 0.0);也可以通过注解的方式存储数据class NormalCache : DataCache() {BoolCache(KeyConstant.HAS_ACCEPTED_PARENT_AGREEMENT, false)var hasAcceptParentAgree: Boolean by this }//如何使用 object CacheHelper {//常规缓存数据,记录一些重要的信息,慎重清除数据private val normal: NormalCache by lazy {NormalCache().apply {setCacheImpl(DiskCache.Builder().setFileId(NormalCache).build())}}fun normal() normal }//存数据 CacheHelper.normal().hasAcceptParentAgree true //取数据 val hasAccept CacheHelper.normal().hasAcceptParentAgree7.5 查看缓存文件数据 android缓存路径查看方法有哪些呢 将手机打开开发者模式并连接电脑在pc控制台输入cd /data/data/目录使用adb主要是方便测试(删除查看导出都比较麻烦)。如何简单快速傻瓜式的查看缓存文件操作缓存文件那么该项目小工具就非常有必要呢采用可视化界面读取缓存数据方便操作直观也简单。 一键接入该工具 FileExplorerActivity.startActivity(this);开源项目地址https://github.com/yangchong211/YCAndroidTool 查看缓存文件数据如下所示 7.6 如何选择合适方案 比如常见的缓存、浏览器缓存、图片缓存、线程池缓存、或者WebView资源缓存等等 那就可以选择LRU缓存淘汰算法。它的核心思想是当缓存满时会优先淘汰那些近期最少使用的缓存对象。 比如针对高频率同步存储或者跨进程等存储数据的场景 那就可以选择MMKV这种存储方案。它的核心思想就是高速存储数据且不会阻塞主线程卡顿。 比如针对存储表结构或者一对多这类的数据 那就可以选择DataStoreRoomGreenDao等存储库方案。 比如针对存储少量用户类数据 其实也可以将json转化为字符串然后选择spmmkvlruDisk等等都可以。 08.其他说明介绍 8.1 遇到的坑分析 Sp存储数据commit() / apply()操作可能会造成ANR问题 commit()是同步提交会在UI主线程中直接执行IO操作当写入操作耗时比较长时就会导致UI线程被阻塞进而产生ANRapply()虽然是异步提交但异步写入磁盘时如果执行了Activity / Service中的onStop()方法那么一样会同步等待SP写入完毕等待时间过长时也会引起ANR问题。首先分析一下SharedPreferences源码中apply方法 SharedPreferencesImpl#apply()这个方法主要是将记录的数据同步写到Map集合中然后在开启子线程将数据写入磁盘 SharedPreferencesImpl#enqueueDiskWrite()这个会将runnable被写入了队列然后在run方法中写数据到磁盘 QueuedWork#queue()这个将runnable添加到sWork(LinkedList链表)中然后通过handler发送处理队列消息MSG_RUN 然后再看一下ActivityThread源码中的handlePauseActivity()、handleStopActivity()方法。 ActivityThread#handlePauseActivity()/handleStopActivity()Activity在pause和stop的时候会调用该方法 ActivityThread#handlePauseActivity()#QueuedWork.waitToFinish()这个是等待QueuedWork所有任务处理完的逻辑 QueuedWork#waitToFinish()这个里面会通过handler查询MSG_RUN消息是否有如果有则会waiting等待 那么最后得出的结论是 handlePauseActivity()的时候会一直等待 apply() 方法将数据保存成功否则会一直等待从而阻塞主线程造成 ANR。但普通存储的场景这种可能性很小。 8.2 项目开发分享 通用缓存存储库开源代码 https://github.com/yangchong211/YCCommonLib/tree/master/AppBaseStore
http://www.tj-hxxt.cn/news/132010.html

相关文章:

  • 西部数码网站管理助手 mysql保存路径php网站开发实例教程书
  • 成品网站源码1688免费推荐昌乐网站设计
  • 网站开发招标技术规范书创网站多少钱
  • 自贡网站优化手机上的软件网站建设
  • 网站开发工具与环境好域名推荐
  • 网站运营与规划wordpress文章字体大小插件
  • 做什麽网站有前景自做网站教程
  • 杭州网站优化推荐实战营销型网站建设
  • 网站不备案支付宝接口上海史特信息技术有限公司
  • 营销策划好的网站广州市手机网站建设服务机构
  • 有部分网站打不开asp网站如何做伪静态
  • 上饶建设网站世界羽联巡回赛总决赛
  • 前十强排名家装公司抚州网站seo
  • app 设计网站建设wordpress如何调用作品集
  • 从域名到网站如何给网站添加icon
  • 网站推广具体内容数据 导入 wordpress
  • 网站到期只续域名不续空间能打开吗wordpress如何更域名
  • 网站开发需求说明广州网站开发广州亦客网络解答
  • 基于php的网站开发英文文献江阴企业网站建设哪家好
  • 网站收录软件南京seo网站管理
  • 建设网站需要的编程招聘网站设计论文
  • 优化公司流程大连优化公司
  • 企业网站建设的开发方式深圳松岗做网站的
  • 网站常用布局黄骅海边旅游景区
  • 之路网站建设温州免费建站
  • 咸宁做网站哪家好WordPress二维码动态
  • 做网站的IT行业商城app开发费用多少钱
  • 自己可以模拟装修app杭州百度首页优化
  • 怎么提升网站的排名淮北网站三合一建设公司
  • 网站建设如何添加咨询免费制作封面网站