Android 存储优化 —— MMKV 集成与原理

本文分析了 Android 存储优化中的 MMKV 框架,对比了 MMKV、SharedPreferences 和 SQLite 的性能,并详细探讨了 MMKV 的初始化、实例化、数据编码过程。MMKV 通过内存映射文件提供高性能的读写,尤其在初始化和数据操作上优于 SharedPreferences。文章还介绍了 MMKV 的 Native 层初始化、目录创建、文件映射和数据载入的细节,展示了其高效性能的原因。
摘要由CSDN通过智能技术生成
数据测试

以下是 MMKV、SharedPreferences 和 SQLite 同步写入 1000 条数据的测试结果

// MMKV
MMKV: MMKV write int: loop[1000]: 12 ms
MMKV: MMKV read int: loop[1000]: 3 ms

MMKV: MMKV write String: loop[1000]: 7 ms
MMKV: MMKV read String: loop[1000]: 4 ms

// SharedPreferences
MMKV: SharedPreferences write int: loop[1000]: 119 ms
MMKV: SharedPreferences read int: loop[1000]: 3 ms

MMKV: SharedPreferences write String: loop[1000]: 187
MMKV: SharedPreferences read String: loop[1000]: 2 ms

// SQLite
MMKV: sqlite write int: loop[1000]: 101 ms
MMKV: sqlite read int: loop[1000]: 136 ms

MMKV: sqlite write String: loop[1000]: 29 ms
MMKV: sqlite read String: loop[1000]: 93 ms

可以看到 MMKV 无论是对比 SP 还是 SQLite, 在性能上都有非常大的优势, 官方提供的数据测试结果如下

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

更详细的性能测试见 wiki

了解 MMKV 的使用方式和测试结果, 让我对其实现原理产生了很大的好奇心, 接下来便看看它是如何将性能做到这个地步的, 这里对主要对 MMKV 的基本操作进行剖析

  • 初始化
  • 实例化
  • encode
  • decode
  • 进程读写的同步

我们从初始化的流程开始分析

二. 初始化

public class MMKV implements SharedPreferences, SharedPreferences.Editor {

// call on program start
public static String initialize(Context context) {
String root = context.getFilesDir().getAbsolutePath() + “/mmkv”;
return initialize(root, null);
}

static private String rootDir = null;

public static String initialize(String rootDir, LibLoader loader) {
… // 省略库文件加载器相关代码
// 保存根目录
MMKV.rootDir = rootDir;
// Native 层初始化
jniInitialize(MMKV.rootDir);
return rootDir;
}

private static native void jniInitialize(String rootDir);

}

MMKV 的初始化, 主要是将根目录通过 jniInitialize 传入了 Native 层, 接下来看看 Native 的初始化操作

// native-bridge.cpp
namespace mmkv {

MMKV_JNI void jniInitialize(JNIEnv *env, jobject obj, jstring rootDir) {
if (!rootDir) {
return;
}
const char *kstr = env->GetStringUTFChars(rootDir, nullptr);
if (kstr) {
MMKV::initializeMMKV(kstr);
env->ReleaseStringUTFChars(rootDir, kstr);
}
}

}

// MMKV.cpp

static unordered_map<std::string, MMKV *> *g_instanceDic;
static ThreadLock g_instanceLock;
static std::string g_rootDir;

void initialize() {
// 1.1 获取一个 unordered_map, 类似于 Java 中的 HashMap
g_instanceDic = new unordered_map<std::string, MMKV *>;
// 1.2 初始化线程锁
g_instanceLock = ThreadLock();

}

void MMKV::initializeMMKV(const std::string &rootDir) {
// 由 Linux Thread 互斥锁和条件变量保证 initialize 函数在一个进程内只会执行一次
// https://blog.csdn.net/zhangxiao93/article/details/51910043
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
// 1. 进行初始化操作
pthread_once(&once_control, initialize);
// 2. 将根目录保存到全局变量
g_rootDir = rootDir;
// 拷贝字符串
char *path = strdup(g_rootDir.c_str());
if (path) {
// 3. 根据路径, 生成目标地址的目录
mkPath(path);
// 释放内存
free(path);
}
}

可以看到 initializeMMKV 中主要任务是初始化数据, 以及创建根目录

  • pthread_once_t: 类似于 Java 的单例, 其 initialize 方法在进程内只会执行一次
  • 创建 MMKV 对象的缓存散列表 g_instanceDic
  • 创建一个线程锁 g_instanceLock
  • mkPath: 根据字符串创建文件目录

接下来我们看看这个目录创建的过程

目录的创建

// MmapedFile.cpp
bool mkPath(char *path) {
// 定义 stat 结构体用于描述文件的属性
struct stat sb = {};
bool done = false;
// 指向字符串起始地址
char *slash = path;
while (!done) {
// 移动到第一个非 “/” 的下标处
slash += strspn(slash, “/”);
// 移动到第一个 “/” 下标出处
slash += strcspn(slash, “/”);

done = (*slash == ‘\0’);
*slash = ‘\0’;

if (stat(path, &sb) != 0) {
// 执行创建文件夹的操作, C 中无 mkdirs 的操作, 需要一个一个文件夹的创建
if (errno != ENOENT || mkdir(path, 0777) != 0) {
MMKVWarning(“%s : %s”, path, strerror(errno));
return false;
}
}
// 若非文件夹, 则说明为非法路径
else if (!S_ISDIR(sb.st_mode)) {
MMKVWarning(“%s: %s”, path, strerror(ENOTDIR));
return false;
}

*slash =

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值