Android 敏捷开发助手

  1. Lottie动画 轻松使用
  2. PNG、JPG等普通图片高保真转SVG图
  3. Android 完美的蒙层方案
  4. Android MMKV框架引入使用
  5. 强大无匹的自定义下拉列表

MMKV框架引入使用

  • 前言
  • MMKV 原理
  • 功能特性
  • 使用实践
  • MMKV 、 SharedPreferences、SQLite 对比
  • SharedPreferences 迁移
  • 总结

博客创建时间:2022.10.04
博客更新时间:2022.10.06

以Android studio build=7.0.0,SDKVersion 31来分析讲解。如图文和网上其他资料不一致,可能是别的资料版本较低而已。


前言

项目常见的轻量级存储一般使用的是SharedPreferences,虽然 SP 兼容性极好, 但其低性能一直被诟病,线上也常出现一些SP导致的ANR。

鹅厂的开源框架MMKV能完美解决SP现有缺点并保持原有的优点。MMKV是基于 mmap 内存映射+ protobuf 序列化两者优势于一体的框架,其具有更高性能高,更强的稳定性。

框架源码:https://github.com/tencent/mmkv


MMKV 原理

内存准备
通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

数据组织
数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。

写入优化
考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。

空间增长
使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

其中对于Android系统,增加了 文件锁 来保证多进程的调用。


功能特性

多进程访问
基于mmap技术,支持多进程数据共享

Android 平台第一个想到的就是 ContentProvider:一个单独进程管理数据,数据同步不易出错,简单好用易上手。然而它的问题也很明显,就是一个字慢:启动慢,访问也慢。这个可以说是 Android 下基于Binder的CS 架构组件的通用痛点。

再考虑到 MMKV 底层使用 mmap 实现,采用去中心化的架构是很自然的选择。我们只需要将文件 mmap 到每个访问进程的内存空间,加上合适的进程锁,再处理好数据的同步,就能够实现多进程并发访问。

匿名内存
对于敏感数据保存在文件中不适合,使用Android 特有的Ashmem 匿名共享内存技术,达到数据保密效果。

数据加密
在Android中 MMKV 使用了 AES CFB-128 算法来加密/解密。选择 CFB 而不是常见的 CBC 算法,主要是因为 MMKV 使用 append-only 实现插入/更新操作,流式加密算法更加合适。


使用实践

MMKV 的使用非常简单,所有变更立马生效,无需调用 sync、apply。

1. 依赖

implementation 'com.tencent:mmkv:1.0.10'
implementation 'com.tencent:mmkv-static:1.0.23'

2. 初始化
MMKV的初始化可以指定保存位置,也可以默认保存位置。

// 初始化,默认的保存地址是: /data/user/0/应用包名/files/mmkv
String rootDir = MMKV.initialize(context);// 指定保存位置初始初始化
String rootDir = MMKV.initialize(dir);// 根据业务区别存储, 附带一个自己的 ID
MMKV kv2 = MMKV.mmkvWithID("MyID");

MMKV的实例可以默认生成,也可以根据不同的业务需求实例化,

// 默认MMKV 实例化
MMKV kv = MMKV.defaultMMKV();
// 根据业务区别存储, 附带一个自己的 ID
MMKV kv2 = MMKV.mmkvWithID("MyID");
// 多进程同步支持
MMKV kv3 = MMKV.mmkvWithID("MyID", MMKV.MULTI_PROCESS_MODE);

3. 迁移
MMKV支持完美无损的将SP中保存的数据迁移到MMKV中

    private void importSharedPreferences(Context context) {MMKV mmkv = MMKV.mmkvWithID("myData");SharedPreferences old_man = context.getSharedPreferences("myData", Context.MODE_PRIVATE);// 迁移旧数据mmkv.importFromSharedPreferences(old_man);// 清空旧数据old_man.edit().clear().commit();}

4. 代码MMKVUtils
MMKV使用非常简单,已封装成一个Utils工具,大家可以复制拿去使用

import android.content.Context;
import android.content.SharedPreferences;import com.tencent.mmkv.MMKV;/*** @author ken.luo*/
public class MMKVUtils {private static MMKV kv;/*** 初始化* @param context  上下文对象*/public static void init(Context context) {String rootDir = MMKV.initialize(context);
//        System.out.println("mmkv root: " + rootDir);kv = MMKV.defaultMMKV();// 根据业务区别存储, 附带一个自己的 IDMMKV kv2 = MMKV.mmkvWithID("MyID");// 多进程同步支持MMKV kv3 = MMKV.mmkvWithID("MyID", MMKV.MULTI_PROCESS_MODE);}/*** 指定保存位置初始化* @param dir   dir = getFilesDir().getAbsolutePath() + "/mmkv_2";*/public static void init(String dir) {String rootDir = MMKV.initialize(dir);kv = MMKV.defaultMMKV();}/*** Ashmem匿名内存* @param context 上下文*/public static void initAshmem(Context context){kv = MMKV.mmkvWithAshmemID(context, "Ashmem", 1024,MMKV.MULTI_PROCESS_MODE, "553646546546");}/*** 迁移SP 到 MMKV*/private void importSharedPreferences(Context context) {MMKV mmkv = MMKV.mmkvWithID("myData");SharedPreferences old_man = context.getSharedPreferences("myData", Context.MODE_PRIVATE);// 迁移旧数据mmkv.importFromSharedPreferences(old_man);// 清空旧数据old_man.edit().clear().commit();}public static void putBoolean(String key, boolean value) {kv.encode(key, value);}public static Boolean getBoolean(String key) {return kv.decodeBool(key, false);}public static Boolean getBoolean(String key, boolean defValue) {return kv.decodeBool(key, defValue);}public static void putInteger(String key, int value) {kv.encode(key, value);}public static int getInteger(String key) {return kv.decodeInt(key, 1);}public static int getInteger(String key, int defValue) {return kv.decodeInt(key, defValue);}public static void putString(String key, String value) {kv.encode(key, value);}public static String getString(String key) {return kv.decodeString(key, "");}public static String getString(String key, String defaultValue) {return kv.decodeString(key, defaultValue);}/*** 移除数据Key* @param key  数据Key*/public static void removeKey(String key) {// 删除数据kv.removeValueForKey(key);
//        mmkv.removeValueForKey("String")
//        mmkv.removeValuesForKeys(arrayOf("int","bool"))
//        mmkv.containsKey("String")}
}

MMKV 、 SharedPreferences、SQLite 对比

单进程性能
可见,MMKV 在写入性能上远远超越 SharedPreferences & SQLite,在读取性能上也有相近或超越的表现。

多进程性能
MMKV 无论是在写入性能还是在读取性能,都远远超越 MultiProcessSharedPreferences & SQLite & SQLite, MMKV 在 Android 多进程 key-value 存储组件上是不二之选。

优点

  1. MMKV使用了protobuf作为序列化的数据结构,相比json和xml效率更快,空间占用更低。
  2. 使用了mmap和匿名Ashmem共享内存,减少了用户空间数据到内核空间的拷贝,提高存储效率和安全性
  3. 需要磁盘空间替换内存空间时,就可以使用MMKV
  4. 支持多进程,多进程安全
  5. MMKV包含进程锁、编码/解码帮助程序和mmap逻辑等等代码,新增MMKV框架一般只会带来60K左右的大小。

缺点:

  1. 在某些情况下读操作会比SP慢(查询数据时存在ProtocolBuffer解码;首次实例化会进行数据的复写剔除重复数据)
  2. 在随机写很多的情况下,会导致随机IO操作,导致效率下降。
  3. 不建议太大文件数据存储,会比较快消耗虚拟内存。尽量保证每一个文件存储的数据较小。

MMKV使用注意

  1. 保证每一个文件存储的数据都比较小,也就说需要把数据根据业务线存储分散。这要就不会把虚拟内存消耗过快。

  2. 还需要在适当的时候释放一部分内存数据,比如在App中监听onTrimMemory方法,在Java内存吃紧的情况下进行MMKV的trim操作(不准确,我们暂时以此为信号,最好自己监听进程中内存使用情况)。

  3. 适当的时候释放一部分内存数据,在不需要使用的时候,最好把MMKV给close掉,甚至调用exit方法。


SharedPreferences 迁移

MMKV 提供了 importFromSharedPreferences() 函数,可以比较方便地迁移数据过来,在前面已有迁移代码示例。

MMKV 还额外实现了一遍 SharedPreferences、SharedPreferences.Editor 这两个 interface,在迁移的时候只需两三行代码即可,其他 CRUD 操作代码都不用改。

SharedPreferences缺点:

  1. 跨进程不安全 就算使用了MODE_MULTI_PROCESS,频繁的写入还是会会造成数据丢失。

  2. 加载缓慢 SharedPreferences使用异步加载,由于线程没有设置优先级,按照默认的线程优先级会造成时间片抢占机会小导致主线程长时间的等待。

  3. 全量写入无论是调用 commit() 还是 apply(),即使我们只改动其中的一个条目,都会把整个内容全部写到文件,写入效率低下。

  4. 每次都需要将所有的数据加载到内存,如果存储大量数据,会占用很多的内存。

  5. 卡顿由于提供了异步落盘的 apply 机制,在崩溃或者其他一些异常情况可能会导致数据丢失。所以当应用收到系统广播,或者被调用 onPause 等一些时机,系统会强制把所有的 SharedPreferences 对象数据落地到磁盘。如果没有落地完成,这时候主线程会被一直阻塞。这样非常容易造成卡顿,甚至是 ANR,从线上数据来看 SP卡顿占比一般会超过 5%

  6. 通过 getSharedPreferences 可以获取 SP 实例,从首次初始化到读到数据会存在延迟,因为读文件的操作阻塞调用的线程直到文件读取完毕,如果在主线程调用,可能会对 UI 流畅度造成影响。(线程阻塞)

  7. 将数据写入文件需要将数据拷贝两次,再写入到文件中,如果数据量过大,也会有很大的性能损耗。(二次写入)


总结

MMKV作为一种高性能大量数据的存储组件,对比Android传统的存储方式SharedPreferences和SQLite确实有不少优势。核心是使用mmap内存映射文件,对比传统IO,在性能上有很大优势,并且将读写文件的操作变得和操作内存一样简单。

MMKV引入增量写入,重整内存,通过文件大小校验对多进程操作感知,多进程读写锁等等。但它的缺点是可能造成内存的浪费,因为必须映射内存页的整数倍,如果只存储很少量的数据,则显得大材小用。因此,可以作为一种数据存储的选择方案,在一些需要大量存储数据场景时,替代SharedPreferences。

项目 评价 描述
正确性 支持多进程安全, 使用 mmap, 由操作系统保证数据回写的正确性
时间开销 使用 mmap 实现, 减少了用户空间数据到内核空间的拷贝
空间开销 使用 protocl buffer 存储数据, 同样的数据会比 xml 和 json 消耗空间小 使用的是数据追加到末尾的方式, 只有到达一定阈值之后才会触发键值合并, 不合并之前会导致同一个 key 存在多份
安全 使用 crc 校验, 甄别文件系统和操作系统不稳定导致的异常数据
开发成本 使用方式较为简单
兼容性 各个安卓版本都前后兼容

相关链接

  1. Lottie动画 轻松使用
  2. PNG、JPG等普通图片高保真转SVG图
  3. Android 完美的蒙层方案
  4. Android MMKV框架引入使用
  5. 强大无匹的自定义下拉列表

扩展链接:

  1. Android CameraX 使用入门
  2. Android 今日头条屏幕适配详细使用攻略
  3. Android 史上最新最全的ADB及命令百科,没有之一

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

Android MMKV框架引入使用相关推荐

  1. Android MVVM框架搭建(三)MMKV + Room + RxJava2

    Android MVVM框架搭建(三)MMKV + Room + RxJava2 前言 正文 一.添加依赖 二.MMKV 1. 初始化 2. 数据存取 3. 使用 三.Room 1. @Entity ...

  2. Android MVVM框架搭建(十)Hilt、ViewBinding、Activity Result API

    Android MVVM框架搭建(十)Hilt.ViewBinding.Activity Result API 前言 正文 一.依赖 二.Hilt使用 1. Hilt 应用类 2. ViewModel ...

  3. GitHub标星5.6K,2020腾讯又一力作开源的Android UI框架——QMUI Android

    各位同学,早上好,我是你们的老朋友D_clock爱吃葱花,前些天忙着发版本,最近也在看各种各样的新知识,有好多东西想写啊啊啊啊啊.嗯,先冷静捋一下,卖个关子.扯回正题,今天继续为大家推荐一个Githu ...

  4. Android MVVM框架搭建(八)高德地图定位、天气查询、BottomSheetDialog

    Android MVVM框架搭建(八)高德地图定位.天气查询.BottomSheetDialog 前言 正文 一.集成SDK 二.基础配置 ① 权限配置 ② 配置Key 三.显示地图 ① MapFra ...

  5. android图形框架之surfaceflinger分析(一)

    1. 概念 surfaceflinger作用是接受多个来源的图形显示数据,将它们合成,然后发送到显示设备.比如打开应用,常见的有三层显示,顶部的statusbar底部或者侧面的导航栏以及应用的界面,每 ...

  6. Android ORM框架GreenDao用法

    Android ORM框架GreenDao用法 0 简介 android 上的ORM框架主要有两个:ormlite 和 GreenDAO,本篇主要介绍GreenDao的用法,原因是GreenDAO的速 ...

  7. android 常用框架整理

    文章目录 UI 框架 WebView 框架 具体内容 UI 卫星菜单 节选器 下拉刷新 模糊效果 HUD与Toast 进度条 UI其他 动画 网络相关 网络连接 网络测试 图像获取 响应式编程 地图 ...

  8. 2017年Android百大框架排行榜

    >随意转载,标注作者"金诚"即可 >本文已经开源到Github上,欢迎各位共同编辑.参与到榜单的建设中来 >Github 地址 一.榜单介绍 排行榜包括四大类: ...

  9. android vitamio封装,Android视频框架Vitamio构建自己的通用播放器

    Vitamio是Android和iOS平台上的全方位多媒体开发框架,完全支持硬件解码和GPU渲染. Vitamio功能强大手机上的万能播放器,可以平滑播放720P或1080P HD MKV,FLV手机 ...

  10. android wi-fi框架,Android Wi-Fi 网络选择

    本页将介绍 Android 11 中用于选择和切换 Wi-Fi 网络的算法和流程.Android 会不断评估已连接的网络以及其他可用网络的质量. 注意:本页上提供的详细信息并不全面,并且在将来的 An ...

最新文章

  1. php析构函数的用法
  2. 【编程题】简单的四则运算
  3. 进程间通信IPC(二)(共享内存、信号、信号量)
  4. markdown绘图插件----mermaid简介
  5. 领域驱动设计:浅析 VO、DTO、DO、PO 概念、区别、用处
  6. Android笔记 notification
  7. 枚举数据库中所有表的列名(转)
  8. Julia语言:让高性能科学计算人人可用(转)
  9. 程序员的英文简历编写参考
  10. SSM框架超市进销存出库入库仓库管理系统(idea开发javaweb-javaee-j2ee-springboot) 退货管理 销售管理 供应商管理 客户管理 员工管理 以及库存统计和盘存统计
  11. OLED有哪些优劣势?
  12. eclipse命令大全
  13. ros3。3教程 入门到高级
  14. 书——《你的生命有什么可能》
  15. vue 实现无限轮播_vue实现匀速轮播效果
  16. IdentityServer4总结
  17. 单位根检验urdf_ADF单位根检验_具体操作
  18. flutter: A value of type ‘dynamic‘ can‘t be assigned to a variable of type ‘int‘.
  19. 步进电机驱动器的教程
  20. 免费开源的客服系统 Linux 服务器环境安装部署过程

热门文章

  1. 2010 年下半年系统分析师案例分析真题
  2. oracle qmon,10g QMON Architecture及AQ_TM_PROCESSES
  3. linux php adodb,【原创】Linux下php使用adodb对sql Server访问配置
  4. 频谱分析仪查看时域波形
  5. psp记忆棒测试软件,psp记忆棒修复工具 ms-format
  6. lisp visual 开根号_分享Visual Lisp程序第二波|文件的读与写-lsp文件
  7. 用Hexo制作自己的静态博客
  8. 统计学基础知识梳理(一)
  9. 上那学计算机,从零开始学电脑(学电脑从哪开始)
  10. 锋利jquery第三章案例 总结