一、首先看一下介紹(mmkv官方介紹)

MMKV——基於 mmap 的高性能通用 key-value 組件

MMKV 是基於 mmap 內存映射的 key-value 組件,底層序列化/反序列化使用 protobuf 實現,性能高,穩定性強。

MMKV 源起

在微信客戶端的日常運營中,時不時就會爆發特殊文字引起系統的 crash,參考文章,文章裡面設計的技術方案是在關鍵代碼前後進行計數器的加減,通過檢查計數器的異常,來發現引起閃退的異常文字。在會話列表、會話界面等有大量 cell 的地方,希望新加的計時器不會影響滑動性能;另外這些計數器還要永久存儲下來——因為閃退隨時可能發生。這就需要一個性能非常高的通用 key-value 存儲組件,我們考察了 SharedPreferences、NSUserDefaults、SQLite 等常見組件,發現都沒能滿足如此苛刻的性能要求。考慮到這個防 crash 方案最主要的訴求還是實時寫入,而 mmap 內存映射文件剛好滿足這種需求,我們嘗試通過它來實現一套 key-value 組件。

MMKV 原理

  • 內存準備通過 mmap 內存映射文件,提供一段可供隨時寫入的內存塊,App 只管往裡面寫數據,由操作系統負責將內存回寫到文件,不必擔心 crash 導致數據丟失。
  • 數據組織數據序列化方面我們選用 protobuf 協議,pb 在性能和空間佔用上都有不錯的表現。
  • 寫入優化

    考慮到主要使用場景是頻繁地進行寫入更新,我們需要有增量更新的能力。我們考慮將增量 kv 對象序列化後,append 到內存末尾。

  • 空間增長使用 append 實現增量更新帶來了一個新的問題,就是不斷 append 的話,文件大小會增長得不可控。我們需要在性能和空間上做個折中。

二、在Android上的使用

String rootDir = MMKV.initialize(this);//見2.1

MMKV kv = MMKV.defaultMMKV();//見2.2

kv.encode("hello", "Hello from mmkv");//見2.3,以字元串為例。

int str = kv.decodeInt("hello");

2.1 initialize的定義

可以看到方法中調用了同名的本地方法,如下

extern "C":防止找不到本地方法,告訴編譯器,保持函數名不變。因為文件是c++文件,按照c++的方式編譯後會生成新的函數名,這樣程序運行時會找不到本地方法。

Java_com_tencent_mmkv_MMKV_initialize:源碼中採用的是靜態註冊本地方法,本地方法名稱規則是java_包名路徑_方法名。

還有一種「動態註冊」本地方法方式,可以很好的解決上訴兩個問題,其他文章會進行介紹,這裡就不再贅述。

GetStringUTFChars:將java字元串轉換成c能使用的字元串

ReleaseStringUTFChars:字元串資源的釋放,c中多數需要手動釋放資源這點與java有所區別。

MMKV::initializeMMKV(kstr),進入c的方法。如下:

mkPath(...),創建mmkv目錄,如下:

小結,創建mmkv 目錄

2.2 MMKV.defaultMMKV()

getDefaultMMKV是本地方法,進入c層。

最終調用defultMMKV();

這裡面new MMKV(). c中創建MMKV類(與java中同名的類),並返回該類的地址,緊接著java中創建MMKV類,return new MMKV(handle);傳入了c中對應MMKV的地址。這樣Java中的類與c中的類,綁定到了一起。(這種操作是不是很熟悉,與android中的序列化parcel實現很像)。

看一下new MMKV(handle);的實現,將其緩存到nativeHandle.

2.3 kv.encode

encodeString是本地方法,注意在第一個參數。

利用reinterpret_cast,將long 地址轉換成c中的MMKV。kv->setStringForKey(value, key);如下

推薦閱讀:

查看原文 >>
相关文章