【Android】MMKV—高性能轻量化存储组件

news2024/11/29 15:07:51

【Android】MMKV—高性能轻量化存储组件

本文参考以及学习文档:

Android存储:轻松掌握MMKV通过学习本文,轻松掌握腾讯开发的 MMKV 组件,尽早在项目中替换掉SharedPr - 掘金

MMKV——Android上的使用(替换SP存储)MMKV 是基于 mmap 内存映射的 key-value 组件,底层 - 掘金

Android:MMKV 组件入门 - 简书

MMKV/README_CN.md at master · Tencent/MMKV

资深Android研发大佬详解MMKV:谷歌都推荐的轻量级存储方案 - 知乎

MMKV是什么

在官方文档里

MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Windows / POSIX / HarmonyOS NEXT 等平台,一并开源。

简单来说

它就是一个【可以用于替代SP】,操作与SP类似的存储组件。

为什么要使用MMKV

SharedPreference缺陷

1. 读写效率低

主要原因是其本身的读写方式导致的:

  • 读写方式:I/O
  • 数据格式:xml
  • 写入方式:全量更新

即每当需要更新一项数据,SharedPreferences的整个读写过程都是:将**「所有数据」**转化成xml格式 -> 通过I/O方式写入。

2. 容易导致ANR

主要是由于同步提交(commit)、异步提交(Apply) 和 获取数据getXX()导致的。

/*
 * 1. 同步提交commit
 * commit提交是同步的,直到磁盘操作成功后才会完成
 * 所以当数据量比较大时,使用commit很可能引起ANR
 */
  public boolean commit() {
    MemoryCommitResult mcr = commitToMemory();
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
    try {
        mcr.writtenToDiskLatch.await();
    } catch (InterruptedException e) {
        return false;
  }

  /*
   * 回调的时机:
   * 1. commit是在内存和硬盘操作均结束时回调
   * 2. apply是内存操作结束时就进行回调
   */
   notifyListeners(mcr);
   return mcr.writeToDiskResult;

}

/*
 * 2. 异步提交apply
 * 当数据量比较大时,使用apply也可能引起ANR
 */
  public void apply() {
      final long startTime = System.currentTimeMillis();

      final MemoryCommitResult mcr = commitToMemory();
      final Runnable awaitCommit = new Runnable() {
              @Override
              public void run() {

                  // 启用等待
                  mcr.writtenToDiskLatch.await(); 
                  ......
              }
          };

      // 将 awaitCommit 添加到队列 QueuedWork 中
      QueuedWork.addFinisher(awaitCommit);

      Runnable postWriteRunnable = new Runnable() {
              @Override
              public void run() {
                  awaitCommit.run();
                  QueuedWork.removeFinisher(awaitCommit);
              }
          };
      // 将写入任务加入到队列中,而写入任务在一个线程中执行
      SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

      // 为了保证异步任务及时完成,当生命周期处于 handleStopService() 、handlePauseActivity() 、handleStopActivity()时会调用QueuedWork.waitToFinish() 会等待写入任务执行完毕
      // waitToFinish() :会一直等待写入任务执行完毕,其它什么都不做。
      // 当有很多写入任务,会依次执行;当文件很大时,效率很低,则容易造成 ANR
      public static void waitToFinish() {
      Runnable toFinish;
      while ((toFinish = sPendingWorkFinishers.poll()) != null) {
          toFinish.run(); 
      }

/*
 * 3. 获取数据getXX()
 * 所有 getXXX() 方法都是同步的,在主线程调用 get 方法,必须等待 SP 加载完毕,也有可能导致ANR
 */

  // 使用getSharedPreferences() 最终会调用SharedPreferencesImpl#startLoadFromDisk()开启一个线程异步读取数据
  new Thread("SharedPreferencesImpl-load") {
      public void run() {
          loadFromDisk();
      }
  }.start();

  // 当我们正在读取一个比较大的数据,还没读取完,接着调用 getXXX()。
  public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}
  // 在同步方法内调用了wait(),会一直等待 getSharedPreferences() 开启的线程读取完数据才能继续往下执行
  // 如果读取一个大的文件,也很大可能会造成ANR
  private void awaitLoadedLocked() {
      while (!mLoaded) {
          try {
              mLock.wait();
          } catch (InterruptedException unused) {
          }
      }
  }
}

MMKV性能优势

写入随机int 1k次。

img

支持的数据类型

支持以下 Java 语言基础类型:

  • boolean、int、long、float、double、byte[]

支持以下 Java 类和容器:

  • String、Set<String>
  • 任何实现了Parcelable的类型

MMKV的使用

依赖引入

首先引入依赖:

dependencies {
    implementation 'com.tencent:mmkv:2.0.1'
    // replace "2.0.1" with any available version
}

初始化以及修改根目录

   MMKV.initialize(this)//(与下面的几选一,一般就使用这个就行)
   
   String dir = getFilesDir().getAbsolutePath() + "/mmkv";
   String rootDir = MMKV.initialize(dir);

获取MMKV实例

import com.tencent.mmkv.MMKV;
//……

//1. 获取默认全局实例 (一般就使用这个就行)
MMKV kv = MMKV.defaultMMKV();

//2. 也可以自定义MMKV对象,设置自定ID  (根据业务区分的存取实例)
MMKV kv = MMKV.mmkvWithID("ID");

//3. MMKV默认是支持单进程的,如果业务需要多进程访问,需要在初始化的时候添加多进程模式参数
MMKV kv = MMKV.mmkvWithID("ID", MMKV.MULTI_PROCESS_MODE); //多进程同步支持

存取数据

/** 添加/更新数据 **/
//存boolean类型
kv.encode("bool", true);
//存int类型
kv.encode("int", Integer.MIN_VALUE);
//存string类型
kv.encode("string", "MyiSMMKV");


/** 获取数据 **/
//获取boolean类型数据
boolean bValue = kv.decodeBool("bool");
//获取int类型数据
int iValue = kv.decodeInt("int");
//获取string类型数据
String str = kv.decodeString("string");
//...等类型的获取

// 删除数据
mmkv.removeValueForKey(key);

MMKV原理

内存准备

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

什么是MMAP?

mmap 是一种在 Linux 和其他类 Unix 操作系统中使用的系统调用,它允许程序将一个文件或者设备映射到内存中。这样做的好处是可以直接通过内存操作来访问文件内容,而不需要使用传统的 read 和 write 系统调用。mmap 提供了一种高效的方式来处理文件数据,特别是在需要频繁读写大文件的场景下。

内存映射mmap 将文件内容映射到进程的地址空间,使得文件内容看起来就像是内存中的一部分。

高效访问:由于文件内容被映射到内存,所以访问文件数据就像访问内存一样快。

共享内存:使用 mmap 可以创建共享内存区域,这对于进程间通信(IPC)非常有用。

自动同步:对映射区域的修改会自动同步回文件,无需显式调用 write 操作。

内存管理mmap 可以帮助操作系统更好地管理内存,因为它允许操作系统在需要时将文件内容从物理内存中换出到磁盘。

文件锁定mmap 可以用于文件锁定,防止其他进程修改文件。

支持匿名映射:除了文件映射外,mmap 还支持创建匿名映射,这种映射不与任何文件关联,通常用于动态内存分配。

啥是Crash

在Android开发中,"Crash"指的是应用程序在运行时遇到未处理的错误导致程序异常终止的现象,也就是我们常说的“崩溃”或“闪退”。Crash会严重影响用户体验,可能导致数据丢失、用户流失甚至声誉受损。

Crash可以分为两大类:

  1. 未捕获异常崩溃:通常是由于代码中的错误引发,如空指针异常(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。
  2. 资源问题引起的崩溃:包括内存溢出、网络连接问题等,这类问题会触发Android的ANR(Application Not Responding)对话框。

Crash和ANR的区别

Crash(崩溃)

  • 定义:指的是应用程序在运行时遇到了无法处理的异常,导致应用程序意外终止。这通常是由于代码中存在错误,如空指针引用、数组越界、类型转换错误等。
  • 表现:应用突然关闭,用户被强制返回到主屏幕或应用列表,可能会看到“应用程序无响应”的提示,但这个提示并不是ANR,而是Crash发生后系统给出的反馈。
  • 原因:通常是代码逻辑错误或资源管理不当(如内存泄漏)。

ANR(应用程序无响应)

  • 定义:指的是应用程序在一段时间内没有响应用户的输入事件(如按键、触摸等),系统会弹出一个对话框提示用户是否要关闭该应用。
  • 表现:用户界面冻结,用户操作没有响应,经过一定时间后(通常是5秒),系统会弹出ANR对话框。
  • 原因:通常是因为主线程(UI线程)被长时间占用,导致无法处理输入事件。常见的原因包括主线程中进行了耗时的I/O操作、复杂的计算、长时间的等待等。

数据组织

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

写入优化

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

空间增长

使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。
无法处理输入事件。常见的原因包括主线程中进行了耗时的I/O操作、复杂的计算、长时间的等待等。

数据组织

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

写入优化

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

空间增长

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2249820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python+django自动化平台(一键执行sql) 前端vue-element展示

一、开发环境搭建和配置 pip install mysql-connector-pythonpip install PyMySQL二、django模块目录 dbOperations ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-313.pyc │ ├── admin.cpython-313.pyc │ ├── apps.cpython-313.pyc │ …

arm Rk1126 编译Qt工程报错: Could not find qmake spec

首先修改qmake.conf文件&#xff0c;配置好正确的交叉编译工具&#xff1a; 然后执行编译&#xff1a; /opt/Rv1126/Rv1126-盒子代码/rv1126-qt5-sdk/bin/qmake untitled.pro 报错。 原因&#xff1a;中文路径。修改路径为英文路径即可

[保姆式教程]使用labelimg2软件标注定向目标检测数据和格式转换

定向目标检测是一种在图像或视频中识别和定位对象的同时&#xff0c;还估计它们方向的技术。这种技术特别适用于处理有一定旋转或方向变化的对象&#xff0c;例如汽车、飞机或文本。定向目标检测器的输出是一组旋转的边界框&#xff0c;这些框精确地包围了图像中的对象&#xf…

C语言刷题笔记3(7)

7.1 数组处理斐波那契数列 题目描述:用数组来处理Fibonacci数列并输出。 输入:一个不超过40且大于2的整数n&#xff0c;表示需要处理并输出的Fibonacci数个数。 输出:输出前n个Fibonacci数&#xff0c;每行输出5个值&#xff0c;按每12位向右对齐的方式输出。请注意不要在第…

PHP 去掉特殊不可见字符 “\u200e“

描述 最近在排查网站业务时&#xff0c;发现有数据匹配失败的情况 肉眼上完全看不出问题所在 当把字符串 【M24308/23-14F‎】复制出来发现 末尾有个不可见的字符 使用删除键或左右移动时才会发现 最后测试通过 var_dump 打印 发现这个"空字符"占了三个长度 &#xf…

构建 LLM (大型语言模型)应用程序——从入门到精通(第七部分:开源 RAG)

通过检索增强生成 (RAG) 应用程序的视角学习大型语言模型 (LLM)。 本系列博文 简介数据准备句子转换器矢量数据库搜索与检索大语言模型开源 RAG&#xff08;本帖&#xff09;评估服务LLM高级 RAG 1. 简介 我们之前的博客文章广泛探讨了大型语言模型 (LLM)&#xff0c;涵盖了其…

linux基础2

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

智能产品综合开发 - 手势识别

1 实训选题目的 本次实训选择的题目是“基于树莓派的手势识别系统”&#xff0c;旨在为人们提供一种便捷的交互方式&#xff0c;使用户能够通过手势控制智能设备&#xff0c;摆脱传统的物理按键操作。通过本项目&#xff0c;我们希望能实现快速、灵活的手势识别&#xff0c;提升…

Qt常用控件之显示类控件

目录 QLabel 文本格式 设置图片 文本对齐/自动换行/边距/缩进 设置伙伴 QLCDNumber 倒计时功能 QProgressBar 进度条 QCalendarWidget QLabel QLabel 同样是 QWidget 的子类&#xff0c;所以前面博客中 QWidget 中的属性方法也是适用的 QLabel可以用来显示文本和图…

架构-微服务-环境搭建

文章目录 前言一、案例准备1. 技术选型2. 模块设计3. 微服务调用 二、创建父工程三、创建基础模块四、创建用户微服务五、创建商品微服务六、创建订单微服务 前言 ‌微服务环境搭建‌ 使用的电商项目中的商品、订单、用户为案例进行讲解。 一、案例准备 1. 技术选型 maven&a…

【JTAG】1149.6协议总结

【JTAG】1149.6协议详解-CSDN博客 IEEE 1149.6标准的基本实现需要在信号路径驱动器中添加一个时脉产生器&#xff0c;它能发射单一脉冲或一列脉冲&#xff0c;这取决于被加载到 1149.1 指令暂存器中的 EXTEST_PULSE 或 EXTEST_TRAIN 指令。1149.6在克服信道中共模讯号干扰能力…

小程序 - 个人简历

为了让招聘人员快速地认识自己&#xff0c;可以做一个“个人简历”微信小程序&#xff0c; 展示自己的个人信息。 下面将对“个人简历”微信小程序进行详细讲解。 目录 个人简历 创建图片目录 页面开发 index.wxml index.wxss 功能实现截图 总结 个人简历 创建图片目录…

Tülu 3:重新定义开源大模型的后训练范式

一、引言 在大型语言模型&#xff08;LLM&#xff09;的发展历程中&#xff0c;预训练阶段往往受到最多关注&#xff0c;动辄需要数百万美元算力投入和数万亿token的训练数据。然而&#xff0c;一个鲜为人知但同样关键的事实是&#xff1a;预训练完成的模型实际上并不能直接投…

systemverilog约束中:=和:/的区别

“x dist { [100:102] : 1, 200 : 2, 300 : 5}” 意味着其值等于100或101或102或200或300其中之一&#xff0c; 其权重比例为1:1:1:2:5 “x dist { [100:102] :/ 1, 200 : 2, 300 : 5}” 意味着等于100&#xff0c;101&#xff0c;102或200&#xff0c;或300其…

用Pycharm安装manim

由于版本和工具的差异&#xff0c;manim的安装方式不尽相同。本文用Pycharm来安装manim. 一、准备工作&#xff1a;安装相应版本的python、pycharm和ffmpeg. 此处提供一种安装ffmpeg的方式 下载地址&#xff1a;FFmpeg 下载后&#xff0c;解压到指定目录。 配置环境变量&am…

云GPU——pycharm远程连接featurize实例

点击PyCharm远程连接会有详细的教程&#xff0c; 本文补充虚拟环境的创建以及包的下载。 1、虚拟环境的创建&#xff1a; 2、虚拟环境创建好之后&#xff0c;下载需要的包 &#xff08;这种方法比较快&#xff09; 可以在python interpreter点击go to tool window&#xff0c…

Fanuc法那科机器人维修之参考位置详解

参考位置是预先设定好的一个或多个特定点位&#xff0c;当启用这一功能时&#xff0c;系统会实时且精确地判断机器人的当前关节角度是否处于预设参考位置的一定范围之内&#xff08;这个范围区间是可以根据实际需求进行设置的&#xff09;&#xff0c;并据此输出指定的信号。 这…

混淆零碎知识点

minifyEnabled true //混淆开关 zipAlignEnabled true // Zipalign优化 shrinkResources true // 移除无用的resource文件 &#xff08;必须要混淆开了之后才才可以设置为true&#xff09; proguard-rules.pro 为混淆文件 //整个文件保留 不被混淆 -keep class com.cn…

ELK(Elasticsearch + logstash + kibana + Filebeat + Kafka + Zookeeper)日志分析系统

文章目录 前言架构软件包下载 一、准备工作1. Linux 网络设置2. 配置hosts文件3. 配置免密登录4. 设置 NTP 时钟同步5. 关闭防火墙6. 关闭交换分区7. 调整内存映射区域数限制8. 调整文件、进程、内存资源限制 二、JDK 安装1. 解压软件2. 配置环境变量3. 验证软件 三、安装 Elas…

【通信协议】CAN总线通信协议的学习(一)基础理论知识学习

目录 1、CAN基本概念 1.0、基本概念 1.1、与其他通信协议的区别 1.2、CAN硬件电路 1.3、CAN总线电平信号 1.4、CAN的差分信号 1.5、CAN总线工作原理 1.6、CAN协议物理层 2、数据帧结构 3、CAN参数配置&#xff0c;波特率计算 1、CAN基本概念 CAN&#xff1a;controll…