天天看點

MMKV與SharedPreference

2020-08-23 11:31:39

文章目錄

          • 存儲方式
          • 如何選擇
        • sp的N宗罪
        • SP優化
        • MMKV與SP
        • MMKV原理:來源https://github.com/Tencent/MMKV
        • 使用方式
        • 參考

鑒于SP的"種種問題",萌發了想要使用寫檔案的方式替換掉sp的想法,發現騰訊開源MMKV是個不錯的選擇。

存儲方式
  1. SharedPreferences
  2. ContentProvider
  3. 檔案
  4. 資料庫
如何選擇
時間開銷 這裡說的時間開銷包括了CPU時間和/0時間,在I/O優化中我就多次提到相比CPU和記憶體,I/0存儲的速度是非常慢的。但是如果存儲方法中比如編解碼或者加密/解密等設計的比較複雜,整個資料存儲過程也會出現CPU時間變得更長的情況
正确性 選擇存儲方案的時候,第一個需要判斷它是否靠譜。這套存儲方案設計是否完備,有沒有支援多線程或者跨程序同步操作。内部是否健壯,有沒有考慮異常情況下資料的校驗和恢複,比如采用雙寫或者備份檔案政策,即使主檔案因為系統底層導緻損壞,也可以一定程度 上恢複大部分資料
空間開銷 即使相同的資料如果使用不同的編碼方式,最後占用的存儲空間也會有所不同。舉一個簡單的例子,相同的資料所占的空間大小是XML>JSON>ProtocolBuffer。除了編碼方式的差異,在一些場景我們可能還需要引入壓縮政策來進-步減少存儲空間,例如zip、lzma等。資料存儲的空間開銷還需要考慮記憶體空間的占用量,整個存儲過程會不會導緻應用出現大量GC、OOM等
安全 應用中可能會有一些非常敏感的資料,即使它們存儲在/data/data中,我們依然必須将它們加密。例如微信的聊天資料是存儲在加密的資料庫中,一些些賬号相關的資料我們也要單獨做加密落地。根據加密強度的不同,可以選擇RSA、AES、chacha20、 TEA這些常用的加密算法
開發成本 有些存儲方案看起來非常高大上,但是需要業務做很大改造才能接入。這裡我們當然希望能無縫的接入到業務中,在整個開發過程越簡單越好
相容性 業務不停地向前演進,我們的存儲字段或者格式有時候也會不得不有所變化。相容性首先要考慮的是向前、向後的相容性,老的資料在更新時能否遷移過來,新的資料在老版本能否降級使用。相容性另外一個需要考慮的可能是多語言的問題,不同的語言是否支援轉換

sp的N宗罪

  1. 跨程序不安全
  2. 加載緩慢:異步加載,但是異步加載線程沒有設定優先級,如果這時候主線程讀取資料需要等待加載線程執行完畢(也就是主線程等待低優先級線程鎖的問題)
  3. 全量寫入:無論是commit還是apply,即使改動一個條目,也會把全部内容寫到檔案
  4. 卡頓:異步落盤機制在應用崩潰時會導緻資料丢失

下面是SP操作源碼的簡介圖,來源:https://juejin.im/entry/6844903488271417351

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-DhYdjRhi-1604329094183)(/image/Android/MMKV/apply.png)]

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-1yFyfDU6-1604329094185)(/image/Android/MMKV/commit.png)]

SP優化

可以在Application中重寫getSharedPreference方法,傳回自己實作的sp。我們可以自己将多次讀寫進行合并

MMKV與SP

關鍵要素 SP MMKV
正确性

跨程序和apply機制導緻資料丢失

使用mmap和檔案 鎖保證資料完整

時間開銷

全量寫入、卡頓

1.使用mmap

2.修改插入檔案尾部,無需全量寫入

空間開銷

使用XML,格式比較備援

使用Protocol Buffer,但是增量更新可能會導緻部分備援

安全

完全明文存儲,沒有支援加密與權限校驗,不适合存放敏感資料

使用Protocol Buffer,不是完全明文。沒有支援加密與權限校驗,不适合存放敏感資料

開發成本

系統支援,非常簡單

需要引入單獨庫,有一定的改造成本

相容性

支援前後相容

支援前後相容,支援導入

SharedPreferences曆史資料,但注意

轉換後版本無法回退

MMKV原理:來源https://github.com/Tencent/MMKV

  1. 記憶體準備

    通過 mmap 記憶體映射檔案,提供一段可供随時寫入的記憶體塊,App 隻管往裡面寫資料,由作業系統負責将記憶體回寫到檔案,不必擔心 crash 導緻資料丢失。

    [外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-1s8OdDNf-1604329094187)(/image/Android/MMKV/Linux-storage-stack.png)]

簡化版

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-qGETkZXT-1604329094189)(/image/Android/MMKV/linux-io.png)]

  1. 資料組織

    資料序列化方面我們選用 protobuf 協定,pb 在性能和空間占用上都有不錯的表現。考慮到我們要提供的是通用 kv 元件,key 可以限定是 string 字元串類型,value 則多種多樣(int/bool/double 等)。要做到通用的話,考慮将 value 通過 protobuf 協定序列化成統一的記憶體塊(buffer),然後就可以将這些 KV 對象序列化到記憶體中。

  2. 寫入優化

    标準 protobuf 不提供增量更新的能力,每次寫入都必須全量寫入。考慮到主要使用場景是頻繁地進行寫入更新,我們需要有增量更新的能力:将增量 kv 對象序列化後,直接 append 到記憶體末尾;這樣同一個 key 會有新舊若幹份資料,最新的資料在最後;那麼隻需在程式啟動第一次打開 mmkv 時,不斷用後讀入的 value 替換之前的值,就可以保證資料是最新有效的。

  3. 空間增長

    使用 append 實作增量更新帶來了一個新的問題,就是不斷 append 的話,檔案大小會增長得不可控。例如同一個 key 不斷更新的話,是可能耗盡幾百 M 甚至上 G 空間,而事實上整個 kv 檔案就這一個 key,不到 1k 空間就存得下。這明顯是不可取的。我們需要在性能和空間上做個折中:以記憶體 pagesize 為機關申請空間,在空間用盡之前都是 append 模式;當 append 到檔案末尾時,進行檔案重整、key 排重,嘗試序列化儲存排重結果;排重後空間還是不夠用的話,将檔案擴大一倍,直到空間足夠。

  4. 資料有效性

    考慮到檔案系統、作業系統都有一定的不穩定性,我們另外增加了 crc 校驗,對無效資料進行甄别。在 iOS 微信現網環境上,我們觀察到有平均約 70萬日次的資料校驗不通過。

使用方式

https://github.com/Tencent/MMKV github上有對應的示例

參考

聊聊 Linux IO

磁盤I/O那些事

Linux 核心的檔案 Cache 管理機制介紹

Linux 中直接 I/O 機制的介紹

MemoryFile

MappedByteBuffer

徹底搞懂 SharedPreferences

Java 對象序列化