初始化
在使用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方法傳到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的讀寫原理等。
如果本文對您有用的話,記得點一個贊哦!