初始化
1 | String dir = getFilesDir().getAbsolutePath() + "/mmkv"; |
首先从MMKV#initialize
开始
MMKV.initialize
1 | public static String initialize(Context context) { |
通过 jniInitialize 来到 JNI 层,找到native-bridge.cpp
中的mmkv::jniInitialize
->MMKV::initializeMMKV(kStr, logLevel)
1 | void MMKV::initializeMMKV(const MMKVPath_t &rootDir, MMKVLogLevel logLevel) { |
ThradLocal::ThreadOnce 就是对 pthread_once 的一次封装,pthread_once 会对首次调用此函数的线程拉起一个init_routine
调用,此处即 initialize 这个函数
1 | void initialize() { |
创建了一个全局 MMKV 映射表和一个全局 ThreadLock 对象,ThreadLock 就是对 pthread 的封装; 同时将系统的 PAGE_SIZE 记录在mmkv::DEFAULT_MMAP_SIZE
中
1 | ThreadLock::ThreadLock() { |
MMKV 写
典型调用如下:
1 | val mmkv = MMKV.mmkvWithID("testKotlin") |
MMKV.mmkvWithID
MMKV.mmkvWithID
本质调用的是getMMKVWithID
这个接口
1 | public static MMKV mmkvWithID(String mmapID) { |
我们注意到这里有几个参数
mmapID 比较容易理解,应该是一个全局 id,联想初始化时构造的 map,应该是在那里使用。mode 这个跟跨进程模式有关,后续分开分析, cryptKey 为加密用的 key,relativePath 则是表示相对与 rootDir 的路径, 通过 getMMKVWithID 创建的 native 对象通过句柄的方式被 Java 层的 MMKV 对象持有,进行 JNI 通信
JNI getMMKVWithID
通过native-bridge.cpp
找到 JNI 转发的实现MMKV::mmkvWithID
1 |
|
SCOPED_LOCK
SCOPED_LOCK
是一个宏展开后,得到是一个 ScopedLock 对象,通过 RAII 的方式实现在函数内上锁, 其中用到了一个特殊宏__COUNTER__
1 |
|
1 | __COUNTER__ |
OK,每次展开自动+1, 那么使用这种方式定义就做到了每个 ScopedLock 对象变量不重名
P.S remove_pointer
1 | Provides the member typedef type which is the type pointed to by T, or, if T is not a pointer, then type is the same as T. |
简单来说就是拿到指针的原类型,例如int*
->int
mmapedKVKey 和全局映射表绑定
接着上文分析,查看 mmapedKVKey 得知 MMKV 根据 mmapID 和 relativePath 的 md5,生成了一个 id,根据这个 id 在全局 g_instanceDic 进行了 putIfAbsent 操作,同时返回 new 出来的对象
MMKV 构造函数
1 | MMKV::MMKV(const string &mmapID, int size, MMKVMode mode, string *cryptKey, string *relativePath) |
一段段看
m_mmapID 应该和 mmap 中使用的 id 有关,这个值目前看应该和[mmapedKVKey 和全局映射表绑定]中提到的 id 一致
m_path
通过mappedKVPathWithID
得到
1 | MMKVPath_t mappedKVPathWithID(const string &mmapID, MMKVMode mode, MMKVPath_t *relativePath) { |
看来跟匿名内存模式有关,做了一层 Ashmem 的兼容
m_crcPath
则类似 mappedKVPathWithID 的实现,只是对路径名做了特殊符号 md5 后,再加上了一个.crc 后缀, 暂时还不知道什么作用,先放着
m_file
则是一个 MemoryFile 对象,m_metaFile
是一个以m_crcPath
为路径的 MemoryFile
m_metaInfo
是一个 MMKVMetaInfo 结构体,记录了一些元数据,等到读写相关信息的时候再看,m_lock
,m_fileLock
,m_sharedProcessLock
,m_exclusiveProcessLock
均为锁对象
构造器根据传入的 cryptKey 创建了 AESCrypt 对象, 同时进行loadFromFile
调用
MemoryFile
首先来看看MemoryFile
这个类
1 | MemoryFile::MemoryFile(const string &path, size_t size, FileType fileType) |
这里对 FileType 做了区分,如果是MMFILE_TYPE_FILE
,则是走文件 IO,否则,则是 ASHMEM 匿名内存文件,我们这里分析下文件 IO 的情况
- MMFILE_TYPE_FILE
1 | void MemoryFile::reloadFromFile() { |
这个函数做了几件事情
- 打开路径为
m_name
的文件,记录于描述符m_fd
- 将文件对齐为 DEFAULT_PMM_SIZE 的整数倍大小
- mmap 打开内存映射,映射到 m_ptr
- MMFILE_TYPE_ASHMEM
如果是匿名内存模式,执行逻辑类似,只是创建文件的实现换成了 ASharedMemory_create
至此,MemoryFile 初始化过程结束,回到 MMKV 的构造器中调用的 loadFromFile 函数
MMKV::loadFromFile
1 | void MMKV::loadFromFile() { |
从 readActualSize 的实现中,我们似乎可以看出,m_file 似乎是有一个 header 结构,这个结构的第一个 int32 表示了文件的实际大小,那么m_actualSize < fileSize && (m_actualSize + Fixed32Size) <= fileSize
这个判断其实表达的意思是除去这个 int32 之外的信息有没有被写入到文件。fileSize 就是 MemoryFile 中传入的 size 对齐 PAGE_SIZE 后的值
走到checkFileCRCValid
逻辑,可以看出这里实际上是使用了 CRC 对存储内容进行了校验, 如果校验成功,则置 loadFromFile=true
我们初始化时不需要数据写回,因此调用 checkDataValid 返回后loadFromFile=true, needFullWriteback=false
走到 else 分支
1 | SCOPED_LOCK(m_exclusiveProcessLock); |
writeActualSize 在初始化阶段做了几件事情
- m_metaInfo->m_actualSize=0
- m_metaInfo->m_crcDigest=0
- m_metaInfo->m_version=MMKVVersionActualSize
- m_metaInfo->m_sequence++;
- m_metaInfo->m_lastConfirmedMetaInfo.lastActualSize = static_cast<uint32_t>(0);
- m_metaInfo->m_lastConfirmedMetaInfo.lastCRCDigest = 0;
将上述信息的修改全部写入 m_metaFile 持有的指针中,同时利用 MemoryFile 中这个指针是由 mmap 创建的特性,同步这些信息到文件描述符中
至此,MMKV 的初始化过程分析完了,总结一下
m_mmapID
相当于是 MMKV 自己的唯一 id,用于全局变量g_instanceDic
进行内存管理m_path
表达了 MMKV 对应的文件路径,用于创建对象 m_filem_file
描述了 MMKV 对应的文件,这个文件可以是 IO 的,也可以是匿名内存的m_crcPath
表达了 m_path 这个路径对应的 CRC 校验文件路径,用于创建对象 m_metaFilem_metaFile
创建了 m_crcPath 对应的文件m_metaInfo
是一个元数据结构体,相当于是m_metaFile
的一份缓存
m_file
和m_metaFile
均为MemoryFile
对象,这个对象封装了文件描述符,同时构造了一个基于此文件描述符的 mmap 后的内存指针MemoryFile#m_ptr
, 通过对这个指针的读写,我们可以快速得到 IO 中的内容
初始化阶段,构造函数构造了上述变量的同时,将m_actualSize
和m_crcDigest
清 0,同时m_version=MMKVVersionActualSize
先写这么多,下一篇文章来分析一下 MMKV 的读写是如何实现的