news 2026/7/22 15:52:45

Android随笔-MMKV

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android随笔-MMKV

一、MMKV 是什么

MMKV 是微信团队开源的一款基于 mmap 内存映射的高性能 key-value 存储组件,底层序列化采用 Protobuf,支持加密、多进程共享、匿名内存等特性。它在微信内部从 2015 年使用至今,后移植到 Android、iOS、macOS、Windows、POSIX 及 HarmonyOS 平台。
与 Android 原生的 SharedPreferences 相比,MMKV 的核心优势在于:

维度SharedPreferencesMMKV
写入方式全量 XML 写入磁盘增量 append 到 mmap 内存
同步机制apply()异步 /commit()同步阻塞无需调用,写入即生效
序列化格式XML(文本,体积大)Protobuf(二进制,体积小)
崩溃安全可能丢失未持久化数据mmap 由 OS 保证刷盘,crash 不丢
多进程不支持支持(文件锁机制)
性能写 1000 次 int 约 233ms写 1000 次 int 约 4ms

二、核心架构分层

MMKV 的架构可分为三层:Java 层 → JNI 层 → Native 核心层,右侧通过 mmap 与文件系统交互。

1. Java 层

MMKV.java 实现了 SharedPreferences 和 SharedPreferences.Editor 接口,对外暴露 encode() / decode() 等方法。所有操作最终通过 nativeHandle(Native 层 MMKV 对象的地址)透传到 C++ 层。

// Java 层示例MMKVkv=MMKV.defaultMMKV();kv.encode("int",Integer.MIN_VALUE);intiValue=kv.decodeInt("int");

2. JNI 层

mmkv_jni.cpp 负责 Java 与 C++ 的桥接,将 jstring 转为 std::string,再调用 MMKV::mmkvWithID() 获取或创建 Native 对象。

// JNI 方法签名extern"C"JNIEXPORT jlong JNICALLJava_com_tencent_mmkv_MMKV_getMMKVWithID(JNIEnv*env,jobject obj,jstring mmapID,jint mode,jstring cryptKey,jstring relativePath){// ... 转换为 std::string 后调用 MMKV::mmkvWithID()}

3. Native 核心层(C++)

这是 MMKV 的核心,包含以下模块:

  • MMKV 类:持有 m_dic(std::unordered_map),维护内存中的键值对。
  • Protobuf 编解码:MiniPBCoder 负责将 m_dic 序列化为二进制,或从二进制反序列化。
  • AES 加密:可选的 AESCrypt 模块,对数据进行 AES 加密。
  • 进程锁:通过 flock() 或 fcntl() 实现跨进程文件锁,保证多进程安全。
  • 内存管理:ensureMemorySize() 负责空间检查、扩容和内存重整。

三、核心机制详解

1. mmap 内存映射:为什么快?

mmap 是 Linux 提供的系统调用,将磁盘文件映射到进程的虚拟地址空间。此后对内存的读写就等价于对文件的读写,无需再经过 read() / write() 系统调用和内核态-用户态的数据拷贝

// MMKV 中的 mmap 调用m_ptr=(char*)mmap(nullptr,m_size,PROT_READ|PROT_WRITE,MAP_SHARED,m_fd,0);

关键点:

  • PROT_READ | PROT_WRITE:映射区域可读可写。
  • MAP_SHARED:修改会同步回写到文件,且对其他进程可见。
  • 崩溃安全:数据写入 m_ptr 指向的内存后,即使 App 立即 crash,操作系统也会负责将"脏页"刷回磁盘,数据不会丢失。

2. 文件加载与校验:loadFromFile()

首次获取 MMKV 实例时,会调用 loadFromFile() 加载已有数据:

voidMMKV::loadFromFile(){// 1. 打开文件,获取大小m_fd=open(m_path.c_str(),O_RDWR|O_CREAT,S_IRWXU);// 2. 文件大小对齐到页大小整数倍(mmap 基于页机制)if(m_size<DEFAULT_MMAP_SIZE||(m_size%DEFAULT_MMAP_SIZE!=0)){m_size=((m_size/DEFAULT_MMAP_SIZE)+1)*DEFAULT_MMAP_SIZE;ftruncate(m_fd,m_size);// 扩容文件zeroFillFile(m_fd,oldSize,m_size-oldSize);// 补零}// 3. mmap 映射m_ptr=(char*)mmap(nullptr,m_size,PROT_READ|PROT_WRITE,MAP_SHARED,m_fd,0);// 4. 读取前 4 字节作为 actualSize(实际数据长度)memcpy(&m_actualSize,m_ptr,Fixed32Size);// 5. CRC 校验,失败则根据策略恢复或丢弃if(checkFileCRCValid()){// 6. 解密(如需要)→ Protobuf 反序列化 → 填充 m_dicMMBufferinputBuffer(m_ptr+Fixed32Size,m_actualSize,MMBufferNoCopy);if(m_crypter)decryptBuffer(*m_crypter,inputBuffer);MiniPBCoder::decodeMap(m_dic,inputBuffer);}// 7. 构造 CodedOutputData,指向可写入区域m_output=newCodedOutputData(m_ptr+Fixed32Size+m_actualSize,m_size-Fixed32Size-m_actualSize);}

文件结构(前几个字节):

| 4 bytes: actualSize | 4 bytes: CRC32 | protobuf 数据 ... |

3. 写入流程:增量追加(Append)

MMKV 最大的设计亮点是增量更新。修改一个已有 key 时,它不会修改文件中原有位置,而是直接在文件末尾 append 一条新记录。读取时只认最后一条。

boolMMKV::setDataForKey(MMBuffer&&data,conststd::string&key){SCOPEDLOCK(m_lock);SCOPEDLOCK(m_exclusiveProcessLock);checkLoadData();// 确保数据已加载// 更新内存中的 mapm_dic[key]=std::move(data);m_hasFullWriteback=false;returnappendDataWithKey(m_dic[key],key);// 追加到文件}

appendDataWithKey() 的核心逻辑:

boolMMKV::appendDataWithKey(constMMBuffer&data,conststd::string&key){// 计算 key + value 在 protobuf 中占用的总字节数size_t size=keyLength+pbRawVarint32Size(keyLength);size+=data.length()+pbRawVarint32Size(data.length());// 确保空间足够(不够则触发内存重整)if(!ensureMemorySize(size))returnfalse;if(m_actualSize==0){// 空文件:全量写入整个 mapautoallData=MiniPBCoder::encodeDataWithObject(m_dic);writeAcutalSize(allData.length());m_output->writeRawData(allData);recaculateCRCDigest();}else{// 非空文件:增量追加writeAcutalSize(m_actualSize+size);m_output->writeString(key);m_output->writeData(data);// 加密 & 更新 CRCif(m_crypter)m_crypter->encrypt(ptr,ptr,size);updateCRCDigest(ptr,size,KeepSequence);}returntrue;}

4. 内存重整:ensureMemorySize()

不断 append 会导致文件膨胀、冗余数据堆积。当剩余空间不足时,ensureMemorySize() 会触发内存重整(Memory Reclamation)

boolMMKV::ensureMemorySize(size_t newSize){if(newSize>=m_output->spaceLeft()){// 1. 将当前 m_dic 全量序列化MMBuffer data=MiniPBCoder::encodeDataWithObject(m_dic);size_t lenNeeded=data.length()+offset+newSize;// 2. 预测未来空间需求,按 2 倍扩容size_t futureUsage=avgItemSize*std::max<size_t>(8,(m_dic.size()+1)/2);if(lenNeeded+futureUsage>=m_size){do{m_size*=2;}while(lenNeeded+futureUsage>=m_size);ftruncate(m_fd,m_size);// 扩大文件zeroFillFile(m_fd,oldSize,m_size-oldSize);munmap(m_ptr,oldSize);// 解除旧映射m_ptr=(char*)mmap(m_ptr,m_size,...);// 重新映射}// 3. 全量回写(清理冗余)writeAcutalSize(data.length());deletem_output;m_output=newCodedOutputData(m_ptr+offset,m_size-offset);m_output->writeRawData(data);recaculateCRCDigest();m_hasFullWriteback=true;}returntrue;}

扩容策略:文件大小按 2 倍 增长(类似 ArrayList),同时预测未来使用量,避免频繁扩容。

5. 多进程支持:文件锁

MMKV 通过文件锁实现多进程并发访问:

  • 读-读:多个进程可同时持有读锁,并发读取。
  • 读-写 / 写-写:通过互斥锁保证串行化。

锁的实现基于 flock() 或 fcntl(),在 loadFromFile() 和写入操作前后加锁/解锁。

四、与 SharedPreferences 的对比总结

特性SharedPreferencesMMKV
存储格式XML 文本Protobuf 二进制
写入方式全量写入磁盘增量 append 到 mmap
同步调用apply()/commit()无需调用,即时生效
崩溃安全apply()可能丢失mmap 由 OS 保证
多进程❌ 不支持✅ 支持
加密❌ 不支持✅ AES 可选
体积增量约 50KB / ABI
读取速度解析 XML直接读内存 map

五、使用注意事项

  1. 不要存储过大对象:MMKV 适合小数据高频读写,单文件过大(如 > 100MB)会影响重整性能。
  2. key 不宜过长:每个 key 都会随 value 一起序列化存储,长 key 会浪费空间。
  3. 多进程模式有开销:跨进程锁会带来一定性能损耗,非必要不开启。
  4. 注意内存重整时机:频繁修改大量 key 会频繁触发 ensureMemorySize(),导致全量序列化开销。
  5. 迁移成本:MMKV 提供了 MMKV.migrateFromSharedPreferences() 方法,可平滑迁移旧数据。

六、总结

MMKV = mmap(零拷贝内存映射)+ Protobuf(高效二进制序列化)+ 增量追加(避免全量写)+ 内存重整(自动清理冗余)+文件锁(多进程安全)

它用"操作内存"的方式替代了"操作文件",用"增量追加"替代了"全量覆盖",用"操作系统刷盘"替代了"手动同步",从而在性能、安全和易用性上全面超越了原生 SharedPreferences。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/22 15:52:36

综述救星[特殊字符]再也不用写流水账!

写论文最煎熬的板块&#xff0c;绝对是文献综述&#xff01; 熬几天看几十篇文献&#xff0c;结果写出来全是&#xff1a;XX认为…XX研究了… 全篇流水账、杂乱无章、没有逻辑、缺乏前沿视角&#xff0c;导师一句&#xff1a;只有堆砌&#xff0c;没有综述&#xff0c;重写&a…

作者头像 李华
网站建设 2026/7/22 15:45:33

EhViewer最新版下载安装教程(官网正版apk安装包,亲测有效)

EhViewer 是一款为动漫爱好者设计的漫画阅读器 APP。简单来说&#xff0c;EhViewer 本身不提供漫画内容&#xff0c;它是通过聚合网络上的漫画索引&#xff0c;为使用者提供极致阅读体验。 EhViewer 目前只支持 Android 安卓系统。 EhViewer 帮助各位漫迷们浏览、查找、下载各…

作者头像 李华
网站建设 2026/7/22 15:45:12

房地产激励不足人才流失?北京华恒智信薪酬优化案例

【客户行业】房地产行业【问题类型】薪酬激励【客户背景及现状分析】某房地产置业公司成立于2004年&#xff0c;随着公司规模不断扩大&#xff0c;目前现有职工数百人&#xff0c;业务范围涵盖房地产建设、物业管理、商超管理、公寓管理、会展中心管理等&#xff0c;多次组织举…

作者头像 李华
网站建设 2026/7/22 15:44:12

HarmonyOS应用开发实战:萌宠日记 - 活动横幅卡片设计

HarmonyOS应用开发实战&#xff1a;萌宠日记 - 活动横幅卡片设计 前言 活动横幅卡片 是社区页面的 推广区域&#xff0c;用于展示 萌宠活动、专题内容 等推广信息。在 萌宠日记 的 CommunityPage 中&#xff0c;活动横幅采用 卡片式设计&#xff0c;包含 标题、文案、行动按钮…

作者头像 李华
网站建设 2026/7/22 15:42:50

深入解析C2000 SCI模块:中断、DMA与低功耗模式实战指南

1. 项目概述与核心价值串行通信接口&#xff08;SCI&#xff09;模块&#xff0c;对于任何一个在嵌入式领域摸爬滚打过的工程师来说&#xff0c;都像是一位熟悉的老朋友。它本质上是基于UART协议实现的异步串行通信硬件外设&#xff0c;是连接微控制器与外部世界最经典、最可靠…

作者头像 李华