MMVK替換SP實現(xiàn)本地數(shù)據(jù)持有化

引言

最近將項目中的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 的話,文件大小會增長得不可控。我們需要在性能和空間上做個折中。

替換原因

1,數(shù)據(jù)加密。 在 Android 環(huán)境里,數(shù)據(jù)加密是非常必須的,SP實際上是把鍵值對放到本地文件中進行存儲。如果要保證數(shù)據(jù)安全需要自己加密,MMKV 使用了 AES CFB-128 算法來加密/解密。

2,多進程共享。系統(tǒng)自帶的 SharedPreferences 對多進程的支持不好?,F(xiàn)有基于 ContentProvider 封裝的實現(xiàn),雖然多進程是支持了,但是性能低下,經(jīng)常導致 ANR。考慮到 mmap 共享內(nèi)存本質(zhì)上是多進程共享的,MMKV 在這個基礎(chǔ)上,深入挖掘了Android 系統(tǒng)的能力,提供了可能是業(yè)界最高效的多進程數(shù)據(jù)共享組件。

3,匿名內(nèi)存。 在多進程共享的基礎(chǔ)上,考慮到某些敏感數(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 語言基礎(chǔ)類型:

 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
            }
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容