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() 等方法。所有操作最终通过 nativeHandleNative 层 MMKV 对象的地址透传到 C 层。// Java 层示例MMKVkvMMKV.defaultMMKV();kv.encode(int,Integer.MIN_VALUE);intiValuekv.decodeInt(int);2. JNI 层mmkv_jni.cpp 负责 Java 与 C 的桥接将 jstring 转为 std::string再调用 MMKV::mmkvWithID() 获取或创建 Native 对象。// JNI 方法签名externCJNIEXPORT 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_dicstd::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_fdopen(m_path.c_str(),O_RDWR|O_CREAT,S_IRWXU);// 2. 文件大小对齐到页大小整数倍mmap 基于页机制if(m_sizeDEFAULT_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_ptrFixed32Size,m_actualSize,MMBufferNoCopy);if(m_crypter)decryptBuffer(*m_crypter,inputBuffer);MiniPBCoder::decodeMap(m_dic,inputBuffer);}// 7. 构造 CodedOutputData指向可写入区域m_outputnewCodedOutputData(m_ptrFixed32Sizem_actualSize,m_size-Fixed32Size-m_actualSize);}文件结构前几个字节| 4 bytes: actualSize | 4 bytes: CRC32 | protobuf 数据 ... |3. 写入流程增量追加AppendMMKV 最大的设计亮点是增量更新。修改一个已有 key 时它不会修改文件中原有位置而是直接在文件末尾 append 一条新记录。读取时只认最后一条。boolMMKV::setDataForKey(MMBufferdata,conststd::stringkey){SCOPEDLOCK(m_lock);SCOPEDLOCK(m_exclusiveProcessLock);checkLoadData();// 确保数据已加载// 更新内存中的 mapm_dic[key]std::move(data);m_hasFullWritebackfalse;returnappendDataWithKey(m_dic[key],key);// 追加到文件}appendDataWithKey() 的核心逻辑boolMMKV::appendDataWithKey(constMMBufferdata,conststd::stringkey){// 计算 key value 在 protobuf 中占用的总字节数size_t sizekeyLengthpbRawVarint32Size(keyLength);sizedata.length()pbRawVarint32Size(data.length());// 确保空间足够不够则触发内存重整if(!ensureMemorySize(size))returnfalse;if(m_actualSize0){// 空文件全量写入整个 mapautoallDataMiniPBCoder::encodeDataWithObject(m_dic);writeAcutalSize(allData.length());m_output-writeRawData(allData);recaculateCRCDigest();}else{// 非空文件增量追加writeAcutalSize(m_actualSizesize);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 ReclamationboolMMKV::ensureMemorySize(size_t newSize){if(newSizem_output-spaceLeft()){// 1. 将当前 m_dic 全量序列化MMBuffer dataMiniPBCoder::encodeDataWithObject(m_dic);size_t lenNeededdata.length()offsetnewSize;// 2. 预测未来空间需求按 2 倍扩容size_t futureUsageavgItemSize*std::maxsize_t(8,(m_dic.size()1)/2);if(lenNeededfutureUsagem_size){do{m_size*2;}while(lenNeededfutureUsagem_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_outputnewCodedOutputData(m_ptroffset,m_size-offset);m_output-writeRawData(data);recaculateCRCDigest();m_hasFullWritebacktrue;}returntrue;}扩容策略文件大小按 2 倍 增长类似 ArrayList同时预测未来使用量避免频繁扩容。5. 多进程支持文件锁MMKV 通过文件锁实现多进程并发访问读-读多个进程可同时持有读锁并发读取。读-写 / 写-写通过互斥锁保证串行化。锁的实现基于 flock() 或 fcntl()在 loadFromFile() 和写入操作前后加锁/解锁。四、与 SharedPreferences 的对比总结特性SharedPreferencesMMKV存储格式XML 文本Protobuf 二进制写入方式全量写入磁盘增量 append 到 mmap同步调用apply()/commit()无需调用即时生效崩溃安全apply()可能丢失mmap 由 OS 保证多进程❌ 不支持✅ 支持加密❌ 不支持✅ AES 可选体积增量无约 50KB / ABI读取速度解析 XML直接读内存 map五、使用注意事项不要存储过大对象MMKV 适合小数据高频读写单文件过大如 100MB会影响重整性能。key 不宜过长每个 key 都会随 value 一起序列化存储长 key 会浪费空间。多进程模式有开销跨进程锁会带来一定性能损耗非必要不开启。注意内存重整时机频繁修改大量 key 会频繁触发 ensureMemorySize()导致全量序列化开销。迁移成本MMKV 提供了 MMKV.migrateFromSharedPreferences() 方法可平滑迁移旧数据。六、总结MMKV mmap零拷贝内存映射 Protobuf高效二进制序列化 增量追加避免全量写 内存重整自动清理冗余文件锁多进程安全它用操作内存的方式替代了操作文件用增量追加替代了全量覆盖用操作系统刷盘替代了手动同步从而在性能、安全和易用性上全面超越了原生 SharedPreferences。

相关新闻

DOM编程核心原理与性能优化实战指南

DOM编程核心原理与性能优化实战指南

1. DOM编程基础概念解析作为前端开发的核心技能之一,DOM操作是每个工程师必须掌握的看家本领。DOM(Document Object Model)本质上是浏览器将HTML文档解析成的树状结构,它让JavaScript能够动态访问和更新页面内容。在实际项目中&am…

2026/7/22 15:52:47阅读更多 →
TI MibSPI DMA接口深度解析:实现SPI高速数据传输的硬件加速方案

TI MibSPI DMA接口深度解析:实现SPI高速数据传输的硬件加速方案

1. 项目概述与核心价值在嵌入式系统开发中,尤其是涉及高速数据采集、实时通信或大容量存储的场景,SPI(串行外设接口)总线上的数据传输效率往往是系统性能的瓶颈。传统的SPI通信,无论是查询还是中断方式,都需…

2026/7/22 15:50:47阅读更多 →
NOC总线架构拓扑介绍

NOC总线架构拓扑介绍

NOC总线架构拓扑介绍 在开篇里,我们对芯片PPA有了初步的认识。下面,让我们从访存这个简单的问题开始展开介绍芯片基础概念。 CPU是怎样访问内存的?简单的答案是,CPU执行一条访存指令,把读写请求发往内存管理单元。内存管理单元进行虚实转换,把命令发往总线。总线…

2026/7/22 15:50:47阅读更多 →
多接口联动实战:本地数据引擎如何构建完整量化分析体系

多接口联动实战:本地数据引擎如何构建完整量化分析体系

多接口联动实战:本地数据引擎如何构建完整量化分析体系 做量化投资的人都明白,单一数据源远不足以支撑完整的策略分析。你需要实时行情看当前价格,L2指标看资金动向,五档盘口看买卖力量,分时成交看日内趋势&#xff0c…

2026/7/22 16:44:55阅读更多 →
I2C控制器高级功能实战:中断、DMA与唤醒机制详解

I2C控制器高级功能实战:中断、DMA与唤醒机制详解

1. I2C控制器高级功能:从轮询到事件驱动的跨越 在嵌入式开发里,I2C总线就像连接MCU和各种传感器、存储芯片的“神经系统”。刚开始玩单片机那会儿,我写I2C驱动基本都是轮询——CPU像个监工一样,不停地去查“数据发完了没&#xff…

2026/7/22 16:44:55阅读更多 →
QtScrcpy:无需Root的终极Android投屏解决方案

QtScrcpy:无需Root的终极Android投屏解决方案

QtScrcpy:无需Root的终极Android投屏解决方案 【免费下载链接】QtScrcpy Android real-time display control software 项目地址: https://gitcode.com/GitHub_Trending/qt/QtScrcpy 还在为Android设备与电脑之间的协作而烦恼吗?想要在电脑大屏幕…

2026/7/22 16:44:55阅读更多 →
ESP32-C61-WROOM-1-N8R2:Wi-Fi 6与RISC-V融合的中坚力量

ESP32-C61-WROOM-1-N8R2:Wi-Fi 6与RISC-V融合的中坚力量

做物联网硬件的朋友最近应该都注意到乐鑫新出的ESP32-C61系列了。这颗芯片定位很有意思,刚好卡在C3和C6之间,既保留了C3的高性价比,又下放了C6的Wi-Fi 6特性。今天拿到模组型号是ESP32-C61-WROOM-1-N8R2,把实际测下来的数据和感受…

2026/7/22 16:44:55阅读更多 →
沿海抗风门窗怎么选?十大门窗品牌汇总

沿海抗风门窗怎么选?十大门窗品牌汇总

在建筑工程设计中,抗风压是门窗核心物理性能指标之一,主要用于衡量门窗在风力荷载作用下的结构稳定能力。高层、沿海、季风频繁区域的建筑,对门窗结构强度与抗风压设计有着更高的适配要求。门窗的抗风压表现,主要由型材材质、腔体…

2026/7/22 16:44:55阅读更多 →
AXI中的burst有几种?都用在什么场景中

AXI中的burst有几种?都用在什么场景中

AXI中的burst有几种?都用在什么场景中 摘要:在 AXI (Advanced eXtensible Interface) 协议中,Burst 传输是数据传输的基本方式,用于提高传输效率。AXI 定义了三种 Burst 类型:FIXED、INCR 和 WRAP。每种 Burst 类型适用于不同的场景,其行为和地址计算方式也不同。…

2026/7/22 16:42:55阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 0:53:59阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 0:53:59阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 0:53:59阅读更多 →
中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业小程序开发公司怎么选:预算、上手和售后避坑指南

中小企业做小程序,最常见的矛盾是预算有限,但又不希望功能太单薄;没有技术团队,但又希望后续能自己运营;想快速上线,又担心隐性收费和售后失联。选型时如果只看“低价套餐”或“案例数量”,很容…

2026/7/22 0:01:17阅读更多 →
GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

GEO优化如何沉淀长期内容资产?广拓时代谈AI搜索时代的内容ROI

企业做营销,最怕钱花完了,资产没有留下。 效果广告能带来一段时间的曝光,但预算停止后,流量往往也随之停止。短视频内容可能在几天内冲高,也可能很快沉下去。AI搜索时代,企业需要重新思考一个问题&#xff…

2026/7/22 0:01:17阅读更多 →
Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复

Agent 终态判定:何时该停止思考、给出最终回复 一、你的 Agent 在"再想想"的循环里绕了 12 轮,用户已经关窗口了 Agent 与人最大的区别是:人知道什么时候该停下来给答案,Agent 会一直"想"下去。你给 Agent 接…

2026/7/22 0:01:17阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/21 22:53:50阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/21 18:53:30阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/21 18:53:30阅读更多 →