赞
踩
突然对 MMKV 的实现非常感兴趣,因此写下此文。由于本人经验不足,如果文中内容有误请路过的大佬加以指正,下面开始正文:
MMKV 是腾讯于 2018 年 9 月 20 日开源的一个 K-V 组件,下面是官方对它的介绍:
MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Windows 平台,一并开源。
想学习更多Android知识,或者获取MMKV相关视频资料请加入Android技术开发交流2群:862625886。 有面试资源系统整理分享,Java语言进阶和Kotlin语言与Android相关技术内核,APP开发框架知识, 360°Android App全方位性能优化。Android前沿技术,高级UI、Gradle、RxJava、小程序、Hybrid、 移动架构师专题项目实战环节、React Native、等技术教程!架构师课程、NDK模块开发、 Flutter等全方面的 Android高级实践技术讲解。还有在线答疑
从上面的介绍,可以发现它与 Android 中的 SharedPreferences 是极其相似的,但是它的性能却远超于 SharedPreferences。根据官方的宣传,写入随机 int 1000次,下面是它们两者的性能对比:
可以发现,它相比 SP 的性能提升不是一点半点,接近了 100 倍!那么它是如何达到这么高的效率的呢?相信很多人已经从上面官方对它的介绍中找到了原因——mmap。
没错,又是 mmap,Binder 的底层原理用到了它,腾讯和美团的日志库用到了它,如今腾讯的 K-V 组件也用到了它,关于 mmap 的介绍及基本使用可以看我之前的一篇笔记:内存映射—— mmap() 函数的使用
那么今天就让我们来研究一下 MMKV 的具体实现。
注意:
1. MMKV 的核心内容使用 C++ 编写,同时涉及了很多 JNI 的使用,因此这篇文章并不是纯 Java。
2. 由于笔者还是大二学生,C++ 功底一般,本文纯属因兴趣开始的研究,因此只能尽量以理解具体流程为主,如果有误请路过的大佬们指出。
如果你还不知道什么是 JNI 和 NDK,可以看我之前的另一篇博客:NDK的基本使用,这一篇就够了
MMKV 在使用前,需要在 Application 中进行初始化:
- protected void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- String rootDir = MMKV.initialize(this);
-
- }
那么我们先进入 initialize 方法看看具体做了什么:
- public static String initialize(Context context) {
- String root = context.getFilesDir().getAbsolutePath() + "/mmkv";
- return initialize(root);
- }
可以看到,这里指定 rootDir 为 context.getFilesDir().getAbsolutePath() + "/mmkv";
,然后调用了 initialize(rootDir)
下面我们看到 initialize(rootDir):
- public static String initialize(String rootDir) {
-
- MMKV.rootDir = rootDir;
-
- jniInitialize(MMKV.rootDir);
-
- return rootDir;
-
- }
它调用了一个 Native 方法 jniInitialize,下面我们看到它的具体实现:
- extern "C" JNIEXPORT JNICALL void
-
- Java_com_tencent_mmkv_MMKV_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);
-
- }
-
- }
可以看到,首先它将 Java 中的 rootDir 转换为了一个 char* kstr,在不为 null 的情况下调用了 MMKV 类的 static 方法 initializeMMKV。下面来到 initializeMMKV:
- void MMKV::initializeMMKV(const std::string &rootDir) {
-
- static pthread_once_t once_control = PTHREAD_ONCE_INIT;
-
- pthread_once(&once_control, initialize);
-
-
- g_rootDir = rootDir;
-
- char *path = strdup(g_rootDir.c_str());
- mkPath(path);
-
- free(path);
-
-
- MMKVInfo("root dir: %s", g_rootDir.c_str());
-
- }
这里可以看到,这里首先将 rootDir 赋值给了这个 static 变量 g_rootDir,之后将 rootDir 复制了一份 path,通过 path 来将 rootDir 这个目录在硬盘中创建出来。
为什么要复制一份而不直接将 rootDir 传进去呢?我们不妨看看 mkPath 的代码:
- bool mkPath(char *path) {
- 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) {
- 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 = '/';
- }
-
- return true;
- }
可以看到,它从前向后一层层进行判断是否目录是否存在,若不存在则调用 mkdir 函数来进行目录的创建,在这个过程中会将传入的路径的一些字段置为 '\0' ,因此为了保证原来的 rootDir 不会被改变,因此复制了一份 char * 传入。
那么到这里,初始化的过程就结束了,主要的步骤就是一些变量的赋值以及目录的创建。
MMKV 中有一种叫做 mmapID 的 String 用于区别存储,类似于 SP 中的 name。通过 MMKV.mmkvWithId 即可获取对应 MMKV 对象,下面我们可以看一下其具体实现:
这里以 mmkvWithID(String mmapID, int mode)
进行举例,其实其他的重载都是类似的,都调用了 getMMKVWithID 这个 Native 方法,只是传入的参数不同。
- private native static long
-
- getMMKVWithID(String mmapID, int mode, String cryptKey, String relativePath);
上面是它的 Java 定义,下面我们看到它的具体实现:
- extern "C" JNIEXPORT JNICALL jlong Java_com_tencent_mmkv_MMKV_getMMKVWithID(
- JNIEnv *env, jobject obj, jstring mmapID, jint mode, jstring cryptKey, jstring relativePath) {
- MMKV *kv = nullptr;
- if (!mmapID) {
- return (jlong) kv;
- }
- string str = jstring2string(env, mmapID);
- bool done = false;
- if (cryptKey) {
- string crypt = jstring2string(env, cryptKey);
- if (crypt.length() > 0) {
- if (relativePath) {
- string path = jstring2string(env, relativePath);
- kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt, &path);
- } else {
- kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt, nullptr);
- }
- done = true;
- }
- }
- if (!done) {
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。