初始化
在使用 MMKV 框架前, 需调用以下方法进行初始化
MMKV.initialize(context);
这里的 Java 层主要是获取到保存文件的路径, 传入 Native 层, 这里默认的路径是 App 的内部存储目录下的 mmkv 路径, 这里不支持修改, 如需修改, 需将源码 clone 下来手动修改编译了.
- public static String initialize(Context context) {
- String rootDir = context.getFilesDir().getAbsolutePath() + "/mmkv";
- initialize(rootDir);
- return rootDir;
- }
到了 Native 层, 通过 Java_com_tencent_mmkv_MMKV_initialize 方法跳转到 MMKV::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());
- }
获取 MMKV 对象
获取 MMKV 对象的方法有以下几个, 最傻瓜式的 defaultMMKV 到最复杂的 mmkvWithAshmemID 方法, 按需调用.
- public MMKV defaultMMKV();
- public MMKV defaultMMKV(int mode, String cryptKey);
- public MMKV mmkvWithID(String mmapID);
- public MMKV mmkvWithID(String mmapID, int mode);
- public MMKV mmkvWithID(String mmapID, int mode, String cryptKey);
- @Nullable
- public MMKV mmkvWithAshmemID(Context context, String mmapID, int size, int mode, String cryptKey);
上面的方法, 基本都会来到 getMMKVWithID 方法, 然后跳转到 MMKV::mmkvWithID 里
- MMKV *MMKV::mmkvWithID(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey) {
- if (mmapID.empty()) {
- return nullptr;
- }
- SCOPEDLOCK(g_instanceLock);
- auto itr = g_instanceDic->find(mmapID);
- if (itr != g_instanceDic->end()) {
- MMKV *kv = itr->second;
- return kv;
- }
- auto kv = new MMKV(mmapID, size, mode, cryptKey);
- (*g_instanceDic)[mmapID] = kv;
- return kv;
- }
g_instanceDic 是 Map 对象, 先是根据 mmapID 在 g_instanceDic 进行查找, 有直接返回, 没就新建一个 MMKV 对象, 然后再添加到 g_instanceDic 里.
- MMKV::MMKV(const std::string &mmapID, int size, MMKVMode mode, string *cryptKey)
- : m_mmapID(mmapID)
- , m_path(mappedKVPathWithID(m_mmapID, mode))
- , m_crcPath(crcPathWithID(m_mmapID, mode))
- , m_metaFile(m_crcPath, DEFAULT_MMAP_SIZE, (mode & MMKV_ASHMEM) ? MMAP_ASHMEM : MMAP_FILE)
- , m_crypter(nullptr)
- , m_fileLock(m_metaFile.getFd())
- , m_sharedProcessLock(&m_fileLock, SharedLockType)
- , m_exclusiveProcessLock(&m_fileLock, ExclusiveLockType)
- , m_isInterProcess((mode & MMKV_MULTI_PROCESS) != 0)
- , m_isAshmem((mode & MMKV_ASHMEM) != 0) {
- m_fd = -1;
- m_ptr = nullptr;
- m_size = 0;
- m_actualSize = 0;
- m_output = nullptr;
- if (m_isAshmem) {
- m_ashmemFile = new MmapedFile(m_mmapID, static_cast<size_t>(size), MMAP_ASHMEM);
- m_fd = m_ashmemFile->getFd();
- } else {
- m_ashmemFile = nullptr;
- }
- if (cryptKey && cryptKey->length()> 0) {
- m_crypter = new AESCrypt((const unsigned char *) cryptKey->data(), cryptKey->length());
- }
- m_needLoadFromFile = true;
- m_crcDigest = 0;
- m_sharedProcessLock.m_enable = m_isInterProcess;
- m_exclusiveProcessLock.m_enable = m_isInterProcess;
- // sensitive zone
- {
- SCOPEDLOCK(m_sharedProcessLock);
- loadFromFile();
- }
- }
MMKV 的构造函数里, 做了一系列参数的构造, 分别有:
m_mmapID: 文件名
m_path: 存放路径
m_crcPath: 校验文件存放路径
m_metaFile: 内存映射的管理对象
m_crypter:AES 加密密钥
m_lock: 线程锁
m_fileLock: 文件锁
m_sharedProcessLock: 映射文件到内存的锁
m_exclusiveProcessLock: 在内存读写数据时的锁
m_isInterProcess: 是否主进程
m_isAshmem: 是否匿名内存
m_ptr: 文件映射到内存后的地址
m_size: 文件大小
m_actualSize: 内存大小, 这个会因为写数据动态变化
m_output:Protobuf 对象, 用于写文件, 效率之所高, 这里也很关键
m_ashmemFile: 匿名内存的文件对象
m_needLoadFromFile: 一个标识对象, 用于是否加载过文件, 加载过就将它置为 false
m_crcDigest: 数据校验
MMKV 对象构造完毕后, 会将该对象的指针地址返回给 Java 层, Java 层的 MMKV 类会保存住该地址, 用于接下来的读写操作.
- public static MMKV mmkvWithID(String mmapID, int mode, String cryptKey) {
- long handle = getMMKVWithID(mmapID, mode, cryptKey);
- return new MMKV(handle);
- }
写数据
以写入 String 对象为例, 看看写入步骤
- public boolean encode(String key, String value) {
- return encodeString(nativeHandle, key, value);
- }
来到 MMKV::setStringForKey 方法
- bool MMKV::setStringForKey(const std::string &value, const std::string &key) {
- if (key.empty()) {
- return false;
- }
- auto data = MiniPBCoder::encodeDataWithObject(value);
- return setDataForKey(std::move(data), key);
- }
MiniPBCoder::encodeDataWithObject 方法将 value 构造出一个 Protobuf 数据对象 (本章不对此详细分析), 然后将构造出来的数据对象通过 std::move https://zh.cppreference.com/w/cpp/utility/move 方法传到 setDataForKey 里
- bool MMKV::setDataForKey(MMBuffer &&data, const std::string &key) {
- if (data.length() == 0 || key.empty()) {
- return false;
- }
- SCOPEDLOCK(m_lock);
- SCOPEDLOCK(m_exclusiveProcessLock);
- checkLoadData();
- // m_dic[key] = std::move(data);
- auto itr = m_dic.find(key);
- if (itr == m_dic.end()) {
- itr = m_dic.emplace(key, std::move(data)).first;
- } else {
- itr->second = std::move(data);
- }
- return appendDataWithKey(itr->second, key);
- }
checkLoadData() 用来检查文件有效性 (本章不对此详细分析)
m_dic 是一个 Map 对象, 在这里判断是否已经存在该 Key, 有就替换, 没就添加
appendDataWithKey() 是将该对象添加到内存里 (本章不对此详细分析)
读数据
- public String decodeString(String key, String defaultValue) {
- return decodeString(nativeHandle, key, defaultValue);
- }
来到 MMKV::getDataForKey 方法
- const MMBuffer &MMKV::getDataForKey(const std::string &key) {
- SCOPEDLOCK(m_lock);
- checkLoadData();
- auto itr = m_dic.find(key);
- if (itr != m_dic.end()) {
- return itr->second;
- }
- static MMBuffer nan(0);
- return nan;
- }
通过 key 在 m_dic 对象里进行查找, 如果查找到, 就返回, 没则返回一个 0 长度的对象
到此, 整体流程讲完了, 还有很多原理性的东西, 在后面的章节再讲, 其中 mmap 映射的操作, 还有 protobuf 的读写原理等. 如果本文对您有用的话, 记得点一个赞哦!
来源: https://juejin.im/post/5bac285d5188255c7039ab80