Mmkv——存取速度比SharedPreferences快数十倍的本地缓存工具

MMKV 是基于 mmap 内存映射的移动端通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。

MMKV 原理

内存准备

通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

数据组织

数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。

写入优化

考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,通过append拼接到内存末尾。

空间增长

通过append实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

MMKV 性能

单进程性能

MMKV 在写入性能上远远超越 SharedPreferences & SQLite,在读取性能上也有相近或超越的表现(每组操作重复1k 次,时间单位: ms)。
MMKV 单进程性能

多进程性能

多进程下,MMKV 无论是在写入性能还是在读取性能,都远远超越 MultiProcessSharedPreferences & SQLite, MMKV 在 Android 多进程 key-value 存储组件上是不二之选(每组操作重复1k 次,时间单位: ms)。

MMKV 多进程性能
Mmkv配置:

在app的build.gradle里面添加依赖:

dependencies {
	......
	implementation 'com.tencent:mmkv:1.0.24'
	implementation 'com.getkeepsafe.relinker:relinker:1.4.0'
	......
}

Mmkv的封装:

public class MmkvTools {
	private static MmkvTools instance = null;
	private MMKV mmkv = null;

    /**
     * TODO:获取操作对象
     * @return
     */
    public static MmkvTools getInstance() {
        if (null == instance) {
            synchronized (MmkvUtils.class) {
                instance = new MmkvTools();
            }
        }
        return instance;
    }

    /**
     * TODO:初始化MMKV
     * @param app
     * @return
     */
    public String initMmkv(final Application app) {
        // if判断用于解决以下问题:一些 Android 设备(API level 19)在安装/更新 APK 时可能出错,导致 libmmkv.so 找不到。
        // 然后就会遇到 java.lang.UnsatisfiedLinkError 之类的 crash。 开源库 ReLinker 专门解决这个问题
        String rootDir = "";
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT){
            String dir = filePath(app);
            rootDir = MMKV.initialize(dir, new MMKV.LibLoader() {
                @Override
                public void loadLibrary(String libName) {
                    ReLinker.loadLibrary(app, libName);
                }
            });
            getMmkv();
            return rootDir;
        }
        rootDir = MMKV.initialize(app);
        getMmkv();
        return rootDir;
    }

    /**
     *
     * @param app
     * @return
     */
    private String filePath(Application app){
        return app.getFilesDir().getAbsolutePath() + "/" + app.getPackageName();
    }

    /**
     * TODO:可以自己定制数据存储的位置
     * @param filePath
     */
    public String initMMKVMyself(Application app, String filePath){
        if (TextUtils.isEmpty(filePath)){
            filePath = filePath(app);
        }
        return MMKV.initialize(filePath);
    }

    /**
     * TODO:根据业务区别存储, 附带一个自己的 ID
     * @param id
     * @return
     */
    public MmkvTools setStorageWithID(String id) {
        mmkv = MMKV.mmkvWithID(id);
        return this;
    }

    /**
     * TODO:获取MMKV的操作对象
     * @return
     */
    private MmkvTools getMmkv() {
        if (null == mmkv) {
            synchronized (MMKV.class){
                mmkv = MMKV.defaultMMKV();
            }
        }
        return this;
    }

    /**
     * TODO:保存boolean
     * @param key
     * @param value
     * @return
     */
    public boolean setBoolean(String key, boolean value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setInteger(String key, int value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setLong(String key, Long value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setFloat(String key, Float value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setDouble(String key, Double value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setString(String key, String value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setSet(String key, Set value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setParcelable(String key, Parcelable value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public boolean setShort(String key, short value){
        getMmkv();
        return mmkv.encode(key, value);
    }

    public String getString(String key, String defaultValue){
        getMmkv();
        return mmkv.decodeString(key, defaultValue);
    }

    public int getInteger(String key, Integer defaultValue){
        getMmkv();
        return mmkv.decodeInt(key, defaultValue);
    }

    public Long getLong(String key, Long defaultValue){
        getMmkv();
        return mmkv.decodeLong(key, defaultValue);
    }

    public Double getDouble(String key, Double defaultValue){
        getMmkv();
        return mmkv.decodeDouble(key, defaultValue);
    }

    public Float getFloat(String key, Float defaultValue){
        getMmkv();
        return mmkv.decodeFloat(key, defaultValue);
    }

    public boolean getBoolean(String key){
        getMmkv();
        return mmkv.decodeBool(key, false);
    }

    public byte[] getByte(String key){
        getMmkv();
        return mmkv.decodeBytes(key);
    }

    public Set getSet(String key){
        getMmkv();
        return mmkv.decodeStringSet(key);
    }

    public Parcelable getParcelable(String key, Object classes){
        getMmkv();
        return mmkv.decodeParcelable(key, ((Parcelable)classes).getClass());
    }

    public String[] getAllKey(){
        getMmkv();
        return mmkv.allKeys();
    }

    public long getTotalSize(){
        getMmkv();
        return mmkv.totalSize();
    }

    public void clearMmkv(){
        getMmkv();
        mmkv.clear();
    }

    public void clearAllMmkv(){
        getMmkv();
        mmkv.clearAll();
    }

    public void clearMemoryCache(){
        getMmkv();
        mmkv.clearMemoryCache();
    }

    public Map<String, Object> getAll(){
        getMmkv();
        return (Map<String, Object>) mmkv.getAll();
    }

    public boolean isContains(String key){
        getMmkv();
        return mmkv.contains(key);
    }

    /**
     * TODO:查询是否包含 key 的存储数据
     * @param key
     * @return
     */
    public boolean querryWithKey(String key){
        getMmkv();
        return mmkv.containsKey(key);
    }

    /**
     * TODO:以key删除数据
     * @param key
     */
    public void deleteDataWithKey(String key){
        getMmkv();
        if (mmkv.containsKey(key)){
            mmkv.removeValueForKey(key);
        }
    }

    /**
     * TODO:以String型数组的item作为key删除数据
     * @param sArray
     */
    public void deleteDataWithArray(String[] sArray){
        getMmkv();
        mmkv.removeValuesForKeys(sArray);
    }

    /**
     * TODO:把SharedPreferences的数据迁移到MMKV
     * @param context
     * @param tableName:SharedPreferences初始化的时候,用到的表名
     */
    public void migrationDataFromSharedPreferences(Context context, String tableName){
        setStorageWithID(tableName);
        SharedPreferences sharedPreferences = context.getSharedPreferences(tableName, Context.MODE_PRIVATE);
        mmkv.importFromSharedPreferences(sharedPreferences);
        sharedPreferences.edit().clear().commit();
    }
}
原创文章 118 获赞 149 访问量 9万+

猜你喜欢

转载自blog.csdn.net/haoyuegongzi/article/details/104973687