引言
最近將項目中的sharedpreference替換了微信開源的mmkv框架损俭,記錄下兩者之前的性能對比和mmvk的簡單封裝使用
MMKV原理
- 內(nèi)存準備
通過 mmap 內(nèi)存映射文件骑篙,提供一段可供隨時寫入的內(nèi)存塊,App 只管往里面寫數(shù)據(jù)威鹿,由操作系統(tǒng)負責將內(nèi)存回寫到文件吱韭,不必擔心 crash 導致數(shù)據(jù)丟失棱烂。 - 數(shù)據(jù)組織
數(shù)據(jù)序列化方面我們選用 protobuf 協(xié)議,pb 在性能和空間占用上都有不錯的表現(xiàn)喇伯。 - 寫入優(yōu)化
考慮到主要使用場景是頻繁地進行寫入更新喊儡,我們需要有增量更新的能力。我們考慮將增量 kv 對象序列化后稻据,append 到內(nèi)存末尾艾猜。 - 空間增長
使用 append 實現(xiàn)增量更新帶來了一個新的問題,就是不斷 append 的話,文件大小會增長得不可控匆赃。我們需要在性能和空間上做個折中淤毛。
替換原因
SharedPreference缺點
http://www.reibang.com/p/c4fa942d8153 請閱讀這位博主的博客MMKV優(yōu)點
1,數(shù)據(jù)加密算柳。 在 Android 環(huán)境里低淡,數(shù)據(jù)加密是非常必須的,SP實際上是把鍵值對放到本地文件中進行存儲瞬项。如果要保證數(shù)據(jù)安全需要自己加密蔗蹋,MMKV 使用了 AES CFB-128 算法來加密/解密。
2滥壕,多進程共享纸颜。系統(tǒng)自帶的 SharedPreferences 對多進程的支持不好。現(xiàn)有基于 ContentProvider 封裝的實現(xiàn)绎橘,雖然多進程是支持了胁孙,但是性能低下,經(jīng)常導致 ANR称鳞′探希考慮到 mmap 共享內(nèi)存本質(zhì)上是多進程共享的,MMKV 在這個基礎上冈止,深入挖掘了Android 系統(tǒng)的能力狂票,提供了可能是業(yè)界最高效的多進程數(shù)據(jù)共享組件。
3熙暴,匿名內(nèi)存闺属。 在多進程共享的基礎上,考慮到某些敏感數(shù)據(jù)(例如密碼)需要進程間共享周霉,但是不方便落地存儲到文件上掂器,直接用 mmap 不合適。而Android 系統(tǒng)提供了 Ashmem 匿名共享內(nèi)存的能力俱箱,它在 進程退出后就會消失国瓮,不會落地到文件上,非常適合這個場景狞谱。MMKV 基于此也提供了 Ashmem(匿名共享內(nèi)存) MMKV 的功能乃摹。
4,效率更高跟衅。MMKV 使用protobuf進行序列化和反序列化孵睬,比起SP的xml存放方式,更加高效与斤。
5肪康,支持從 SP遷移荚恶,如果你之前項目里面都是使用SP,現(xiàn)在想改為使用MMKV磷支,只需幾行代碼即可將之前的SP實現(xiàn)遷移到MMKV谒撼。
支持的數(shù)據(jù)類型
1,支持以下 Java 語言基礎類型:
boolean雾狈、int廓潜、long、float善榛、double辩蛋、byte[]
2,支持以下 Java 類和容器:
String移盆、Set< String >
任何實現(xiàn)了Parcelable的類型
使用
1.添加依賴庫
dependencies {
implementation 'com.tencent:mmkv:1.0.23'
}
2.在application中初始化
MMKV.initialize(this);
3.獲取mmkv實例
正常單一業(yè)務儲存
MMKV kv = MMKV.defaultMMKV();
如果不同業(yè)務需要區(qū)別存儲悼院,也可以單獨創(chuàng)建自己的實例:
MMKV mmkv = MMKV.mmkvWithID("MyID");
默認支持單進程的,如果業(yè)務需要多進程訪問咒循,則需要加上標志位 MMKV.MULTI_PROCESS_MODE:
MMKV mmkv = MMKV.mmkvWithID("InterProcessKV", MMKV.MULTI_PROCESS_MODE);
自定義跟目錄据途,MMKV 默認把文件存放在$(FilesDir)/mmkv/目錄。你可以在 App 啟動時自定義根目錄:
String dir = getFilesDir().getAbsolutePath() + "/mmkv_2";
String rootDir = MMKV.initialize(dir);
class SpUtils private constructor() {
private var mv: MMKV = MMKV.defaultMMKV()
/**
* 保存數(shù)據(jù)的方法叙甸,我們需要拿到保存數(shù)據(jù)的具體類型颖医,然后根據(jù)類型調(diào)用不同的保存方法
*
* @param key
* @param object
*/
fun encode(key: String?, `object`: Any) {
if (`object` is String) {
mv.encode(key, `object`)
} else if (`object` is Int) {
mv.encode(key, `object`)
} else if (`object` is Boolean) {
mv.encode(key, `object`)
} else if (`object` is Float) {
mv.encode(key, `object`)
} else if (`object` is Long) {
mv.encode(key, `object`)
} else if (`object` is Double) {
mv.encode(key, `object`)
} else if (`object` is ByteArray) {
mv.encode(key, `object`)
} else {
mv.encode(key, `object`.toString())
}
}
fun encodeSet(key: String?, sets: Set<String?>?) {
mv.encode(key, sets)
}
fun encodeParcelable(key: String?, obj: Parcelable?) {
mv.encode(key, obj)
}
/**
* 得到保存數(shù)據(jù)的方法,我們根據(jù)默認值得到保存的數(shù)據(jù)的具體類型裆蒸,然后調(diào)用相對于的方法獲取值
*/
fun decodeInt(key: String?): Int {
return mv.decodeInt(key, 0)
}
fun decodeDouble(key: String?): Double {
return mv.decodeDouble(key, 0.00)
}
fun decodeLong(key: String?): Long {
return mv.decodeLong(key, 0L)
}
fun decodeBoolean(key: String?): Boolean {
return mv.decodeBool(key, false)
}
fun decodeFloat(key: String?): Float {
return mv.decodeFloat(key, 0f)
}
fun decodeBytes(key: String?): ByteArray {
return mv.decodeBytes(key)
}
fun decodeString(key: String?): String {
return mv.decodeString(key, "")
}
fun decodeStringSet(key: String?): Set<String> {
return mv.decodeStringSet(key, emptySet())
}
fun <T : Parcelable?> decodeParcelable(key: String?, tClass: Class<T>?): T {
return mv.decodeParcelable(key, tClass)
}
/**
* 移除某個key對
*
* @param key
*/
fun removeKey(key: String?) {
mv.removeValueForKey(key)
}
/**
* 清除所有key
*/
fun clearAll() {
mv.clearAll()
}
/**
* 從sp中遷移到mmvk
*/
fun testImportSharedPreferences(context: Context){
val old_man = context.getSharedPreferences("myData", Context.MODE_PRIVATE)
// 遷移舊數(shù)據(jù)
mv.importFromSharedPreferences(old_man);
// 清空舊數(shù)據(jù)
old_man.edit().clear().commit();
}
companion object {
private var mInstance: SpUtils? = null
/**
* 初始化MMKV,只需要初始化一次熔萧,建議在Application中初始化
*
*/
val instance: SpUtils?
get() {
if (mInstance == null) {
synchronized(SpUtils::class.java) {
if (mInstance == null) {
mInstance = SpUtils()
}
}
}
return mInstance
}
}
}