一、MMKV 的含义与概述
MMKV 是腾讯开源的一款基于 mmap 内存映射的键值存储组件,专门为移动端设计,具有高性能的特性,核心定位是替代传统的轻量级键值存储方案。其名字来源于 “Memory-Mapped Key-Value” 的缩写。
对于追求极致性能的 Android 与 iOS 开发者而言,理解其原理并熟练应用,能显著提升应用的响应速度和存储稳定性。
二、MMKV 的作用与优势
- 高性能:基于 mmap 内存映射,读写性能远超 SharedPreferences。SharedPreferences 的 IO 操作基于文件流,频繁读写会触发大量磁盘 IO,而 MMKV 基于 mmap 实现 “内存 - 磁盘” 映射,读写几乎无磁盘 IO 开销。
- 增量更新:MMKV 不会像 SP 那样每次修改都重写整个文件,而是在内存映射区域中找到对应键的位置,直接修改数据,仅当内存不足时才扩容文件并重新映射,大幅减少磁盘写入开销。
- 多进程安全:内置文件锁机制,支持多进程读写。SharedPreferences 多进程模式存在数据丢失、死锁风险,MMKV 内置文件锁,保证多进程安全。
- 易用性:API 设计简洁,与 SharedPreferences 兼容,迁移成本极低。
- 轻量:编译后体积仅几十 KB。
- 数据加密:支持 AES CFB-128 加密保护敏感数据。
三、使用场景
1. 用户配置存储
// 替代 SharedPreferences 存储用户设置
MMKV.defaultMMKV().encode("theme_mode", "dark")
val theme = MMKV.defaultMMKV().decodeString("theme_mode")
2. 轻量级缓存
// 存储临时数据或缓存
val mmkv = MMKV.mmkvWithID("user_cache")
mmkv.encode("last_login_time", System.currentTimeMillis())
mmkv.encode("user_profile", jsonString)
3. 跨进程数据共享
// 多进程应用中的数据同步
val mmkv = MMKV.mmkvWithID("inter_process_data", MMKV.MULTI_PROCESS_MODE)
mmkv.encode("global_config", configData)
4. 敏感数据加密存储
// 加密存储用户凭证等敏感信息
val cryptKey = "My-Encryption-Key".toByteArray()
val mmkv = MMKV.mmkvWithID("encrypted_data", MMKV.SINGLE_PROCESS_MODE, cryptKey)
mmkv.encode("auth_token", sensitiveToken)
四、核心原理详解
1. 内存映射(mmap)机制
MMKV 的核心是使用 mmap 将文件直接映射到内存空间:
// C++ 核心实现(简化)
void MMKV::initializeMMKV(const std::string &path) {
// 打开或创建文件
m_fd = open(path.c_str(), O_RDWR | O_CREAT, S_IRWXU);
// 获取文件大小并调整
struct stat st = {};
fstat(m_fd, &st);
m_size = st.st_size;
// 内存映射
m_ptr = (char *)mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
// 文件长度不够时扩容
if (m_actualSize > m_size) {
do {
m_size *= 2;
} while (m_actualSize > m_size);
ftruncate(m_fd, m_size);
munmap(m_ptr, m_size);
m_ptr = (char *)mmap(nullptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
}
}
mmap 优势:mmap 是操作系统提供的内存映射文件机制,将磁盘文件的一部分 / 全部映射到进程的虚拟内存空间,后续对该内存区域的读写操作,会由操作系统自动同步到磁盘,无需手动调用 read()/write()。
相比传统文件 IO:
- 传统 IO:数据需经过 “磁盘 → 内核缓冲区 → 用户缓冲区” 两次拷贝;
- mmap:数据直接映射到用户内存,仅一次拷贝,且读写操作无系统调用开销。
2. 数据序列化与存储格式
MMKV 采用 Protobuf(Protocol Buffers)作为数据序列化方式,而非 SP 的 XML 格式,核心优势:
- 二进制编码,体积远小于 XML;
- 解析速度快,无需像 XML 那样解析字符串;
- 支持增量更新,仅修改变化的字段,无需重写整个文件。
// 数据存储结构
message KV {
optional string key = 1;
optional bytes value = 2;
optional sint32 value_size = 3;
}
// 文件格式
+----------------+----------------+----------------+
| Total Size | Actual Size | CRC Check |
+----------------+----------------+----------------+
| | | |
| Key-Value | Key-Value | Key-Value |
| Pairs | Pairs | Pairs |
| | | |
+----------------+----------------+----------------+
3. 多进程同步机制
MMKV 使用文件锁实现进程间同步:
// 进程锁实现
bool MMKV::lock() {
struct flock lock = {};
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
return fcntl(m_fd, F_SETLKW, &lock) == 0;
}
void MMKV::unlock() {
struct flock lock = {};
lock.l_type = F_UNLCK;
lock.l_whence = SEEK_SET;
fcntl(m_fd, F_SETLK, &lock);
}
4. 数据加密实现
采用 AES CFB-128 加密算法:
// 加密解密流程
void MMKV::crypt(const void *input, void *output, size_t length) {
if (!m_crypter) {
memcpy(output, input, length);
return;
}
if (m_crypter->getCryptTag() != m_metaInfo->m_cryptTag) {
// 需要重新计算 CRC
recalcCRCDigest();
}
m_crypter->crypt(input, output, length);
}
5. 数据淘汰与压缩机制
当存储空间不足时,MMKV 会执行数据清理:
void MMKV::trim() {
// 收集所有有效数据的指针
std::vector<MMBuffer> validBuffers;
// 重新整理数据,丢弃过期数据
size_t newSize = 0;
for (auto &pair : m_dic) {
if (!pair.second.isStale()) {
validBuffers.push_back(pair.second);
newSize += pair.second.length();
}
}
// 如果节省空间超过阈值,执行压缩
if (m_actualSize - newSize > m_size * 0.5) {
fullWriteback();
}
}
五、Android 集成与使用
1. 添加依赖
dependencies {
implementation 'com.tencent:mmkv:2.3.0'
}
2. 初始化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 初始化 MMKV
val rootDir = MMKV.initialize(this)
println("MMKV root: $rootDir")
// 或指定自定义路径
// MMKV.initialize(cacheDir.absolutePath + "/mmkv")
}
}
3. 基本操作
// 获取默认实例
val kv = MMKV.defaultMMKV()
// 存储数据
kv.encode("bool", true)
kv.encode("int", 123)
kv.encode("string", "Hello, MMKV")
kv.encode("float", 3.14f)
kv.encode("bytes", byteArrayOf(1, 2, 3))
// 读取数据
val bValue = kv.decodeBool("bool")
val iValue = kv.decodeInt("int")
val sValue = kv.decodeString("string")
// 删除数据
kv.removeValueForKey("bool")
kv.removeValuesForKeys(arrayOf("int", "float"))
// 批量更新
kv.edit().apply {
putString("name", "John")
putInt("age", 30)
commit()
}
4. 高级特性使用
// 1. 多实例管理
val userKV = MMKV.mmkvWithID(“user_data”)
val settingsKV = MMKV.mmkvWithID(“app_settings”)
// 2. 数据变化监听
val listener = object : MMKV.OnContentChangeListener {
override fun onContentChangedByOuterProcess(mmkvID: String?) {
// 处理跨进程数据变更
}
}
kv.addContentChangeListener(listener)
// 3. 备份与恢复
val backupPath = filesDir.absolutePath + “/backup”
val backupKey = “backup_key”
kv.backupOneToDirectory(backupPath, backupKey)
// 4. 数据迁移(从 SharedPreferences)
val sharedPreferences = getSharedPreferences(“old_data”, MODE_PRIVATE)
kv.importFromSharedPreferences(sharedPreferences)
sharedPreferences.edit().clear().apply()
六、性能对比
以下是 MMKV 与 SharedPreferences 的性能对比数据:
| 操作类型 |
MMKV |
SharedPreferences |
提升倍数 |
| 写入 1000 条数据 |
~50ms |
~10000ms |
200倍 |
| 读取 1000 条数据 |
~10ms |
~2000ms |
200倍 |
| 跨进程读取 |
~20ms |
不支持 |
N/A |
七、最佳实践
1. 存储策略选择
// 根据场景选择存储模式
enum class StorageStrategy {
// 单进程频繁读写
SINGLE_PROCESS_FREQUENT {
override fun getMMKV(): MMKV {
return MMKV.mmkvWithID(“frequent_data”)
}
},
// 跨进程共享
MULTI_PROCESS_SHARED {
override fun getMMKV(): MMKV {
return MMKV.mmkvWithID(“shared_data”, MMKV.MULTI_PROCESS_MODE)
}
},
// 加密存储
ENCRYPTED_SENSITIVE {
override fun getMMKV(): MMKV {
val key = “your-256-bit-encryption-key”.toByteArray()
return MMKV.mmkvWithID(“sensitive”, MMKV.SINGLE_PROCESS_MODE, key)
}
};
abstract fun getMMKV(): MMKV
}
2. 数据大小控制
// 避免存储过大数据
object MMKVManager {
private const val MAX_VALUE_SIZE = 1024 * 1024 // 1MB
fun safeEncode(key: String, value: ByteArray): Boolean {
if (value.size > MAX_VALUE_SIZE) {
Log.w(“MMKV”, “Value too large for key: $key”)
return false
}
return MMKV.defaultMMKV().encode(key, value)
}
}
3. 异常处理
// 健壮的数据访问
fun safeDecodeString(key: String, defaultValue: String = ""): String {
return try {
MMKV.defaultMMKV().decodeString(key, defaultValue) ?: defaultValue
} catch (e: Exception) {
Log.e(“MMKV”, “Failed to decode key: $key”, e)
defaultValue
}
}
八、注意事项
- 数据量限制:适合存储中小规模数据,不建议存储超过 1MB 的单个值。
- 版本兼容:升级时注意数据格式兼容性。
- 内存使用:大数据量时注意内存占用监控。
MMKV 作为一款优秀的 开源实战 项目,其设计思想和实现细节值得深入研读。在实际项目中运用时,我们可以将其核心原理作为一份优秀的 技术文档 来参考,从而更好地理解内存映射、序列化等底层技术。如果你想了解更多类似的移动端优化实践,欢迎在 云栈社区 进行交流探讨。