mmkv源码解析(java)

一、成员变量

//错误恢复策略相关

private static final EnumMap<MMKVRecoverStrategic, Integer> recoverIndex = new EnumMap(MMKVRecoverStrategic.class);

//MMKV日志输出相关

private static final EnumMap<MMKVLogLevel, Integer> logLevel2Index;

private static final MMKVLogLevel[] index2LogLevel;

//存储已经打开的 MMKV 描述符集合

private static final Set<Long> checkedHandleSet;

//MMKV 文件存放根路径

private static String rootDir;

//MMKV 进程模式

public static final int SINGLE_PROCESS_MODE = 1;

public static final int MULTI_PROCESS_MODE = 2;

private static final int CONTEXT_MODE_MULTI_PROCESS = 4;

private static final int ASHMEM_MODE = 8;

//序列化相关的

private static final HashMap<String, Creator<?>> mCreators;

//处理日志重定向、文件错误恢复策略

private static MMKVHandler gCallbackHandler;

private static boolean gWantLogReDirecting;

//内容变更通知 主要是被其他进程更新时有回调

private static MMKVContentChangeNotification gContentChangeNotify;

//MMKV描述符 主要用来读写文件用

private final long nativeHandle;

二、API接口

1.初始化MMKV

//传入context的初始化函数,log默认是LevelInfo

public static String initialize(Context context) {

    String root = context.getFilesDir().getAbsolutePath() + "/mmkv";

    MMKVLogLevel logLevel = MMKVLogLevel.LevelInfo;

    return initialize((String)root, (MMKV.LibLoader)null, logLevel);

}

//传入context 和 logLevel

public static String initialize(Context context, MMKVLogLevel logLevel) {

    String root = context.getFilesDir().getAbsolutePath() + "/mmkv";

    return initialize((String)root, (MMKV.LibLoader)null, logLevel);

}

//传入context 和 loader

public static String initialize(Context context, MMKV.LibLoader loader) {

    String root = context.getFilesDir().getAbsolutePath() + "/mmkv";

    MMKVLogLevel logLevel = MMKVLogLevel.LevelInfo;

    return initialize(root, loader, logLevel);

}

// 传入 context 、loader/level

public static String initialize(Context context, MMKV.LibLoader loader, MMKVLogLevel logLevel) {

    String root = context.getFilesDir().getAbsolutePath() + "/mmkv";

    return initialize(root, loader, logLevel);

}

// 传入路径

public static String initialize(String rootDir) {

    MMKVLogLevel logLevel = MMKVLogLevel.LevelInfo;

    return initialize((String)rootDir, (MMKV.LibLoader)null, logLevel);

}

//传入路径和日志等级

public static String initialize(String rootDir, MMKVLogLevel logLevel) {

    return initialize((String)rootDir, (MMKV.LibLoader)null, logLevel);

}

//传入路径和loader

public static String initialize(String rootDir, MMKV.LibLoader loader) {

    MMKVLogLevel logLevel = MMKVLogLevel.LevelInfo;

    return initialize(rootDir, loader, logLevel);

}

//最后都调用的该方法  传入路径 /loader 和 日志等级

public static String initialize(String rootDir, MMKV.LibLoader loader, MMKVLogLevel logLevel) {

    //如果loader不为空,则由传入的loader 加载 c++_shared 和 mmkv 两个so库,否则由System.loadLibrary 加载

    if (loader != null) {

        if ("SharedCpp".equals("SharedCpp")) {

            loader.loadLibrary("c++_shared");

        }

        loader.loadLibrary("mmkv");

    } else {

        if ("SharedCpp".equals("SharedCpp")) {

            System.loadLibrary("c++_shared");

        }

        System.loadLibrary("mmkv");

    }

    //jni 调用native方法

    jniInitialize(rootDir, logLevel2Int(logLevel));

    //记录rootDir

    MMKV.rootDir = rootDir;

    return MMKV.rootDir;

}

总结:初始化最后都调用的是 initialize(String rootDir, MMKV.LibLoader loader, MMKVLogLevel logLevel)

2.获取MMKV存储根路径

//可以根据此方法判断路径是否为空 是否初始化成功,但是要注意是否在调用exit 后

public static String getRootDir() {

    return rootDir;

}

3.获取MMKV

//通过mapId 获取,默认单进程 获取前必须保证初始化 即路径不为空

@Nullable

public static MMKV mmkvWithID(String mmapID) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        long handle = getMMKVWithID(mmapID, 1, (String)null, (String)null);

        return checkProcessMode(handle, mmapID, 1);

    }

}

//通过mapId 、进程mode 获取

@Nullable

public static MMKV mmkvWithID(String mmapID, int mode) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        long handle = getMMKVWithID(mmapID, mode, (String)null, (String)null);

        return checkProcessMode(handle, mmapID, mode);

    }

}

//通过mapId mode 、key 获取

@Nullable

public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        long handle = getMMKVWithID(mmapID, mode, cryptKey, (String)null);

        return checkProcessMode(handle, mmapID, mode);

    }

}

//通过mapID 和 rootpath 获取

@Nullable

public static MMKV mmkvWithID(String mmapID, String rootPath) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        long handle = getMMKVWithID(mmapID, 1, (String)null, rootPath);

        return checkProcessMode(handle, mmapID, 1);

    }

}

//通过 mapId  rootpath  进程、key 获取

@Nullable

public static MMKV mmkvWithID(String mmapID, int mode, @Nullable String cryptKey, String rootPath) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        long handle = getMMKVWithID(mmapID, mode, cryptKey, rootPath);

        return checkProcessMode(handle, mmapID, mode);

    }

}

//匿名共享内存方式获取  跨进程传输 MMKV对应的 handle /size/ mode/key

@Nullable

public static MMKV mmkvWithAshmemID(Context context, String mmapID, int size, int mode, @Nullable String cryptKey) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        String processName = MMKVContentProvider.getProcessNameByPID(context, Process.myPid());

        if (processName != null && processName.length() != 0) {

            if (processName.contains(":")) {

                Uri uri = MMKVContentProvider.contentUri(context);

                if (uri == null) {

                    simpleLog(MMKVLogLevel.LevelError, "MMKVContentProvider has invalid authority");

                    return null;

                } else {

                    simpleLog(MMKVLogLevel.LevelInfo, "getting parcelable mmkv in process, Uri = " + uri);

                    Bundle extras = new Bundle();

                    extras.putInt("KEY_SIZE", size);

                    extras.putInt("KEY_MODE", mode);

                    if (cryptKey != null) {

                        extras.putString("KEY_CRYPT", cryptKey);

                    }

                    ContentResolver resolver = context.getContentResolver();

                    Bundle result = resolver.call(uri, "mmkvFromAshmemID", mmapID, extras);

                    if (result != null) {

                        result.setClassLoader(ParcelableMMKV.class.getClassLoader());

                        ParcelableMMKV parcelableMMKV = (ParcelableMMKV)result.getParcelable("KEY");

                        if (parcelableMMKV != null) {

                            MMKV mmkv = parcelableMMKV.toMMKV();

                            if (mmkv != null) {

                                simpleLog(MMKVLogLevel.LevelInfo, mmkv.mmapID() + " fd = " + mmkv.ashmemFD() + ", meta fd = " + mmkv.ashmemMetaFD());

                            }

                            return mmkv;

                        }

                    }

                    return null;

                }

            } else {

                simpleLog(MMKVLogLevel.LevelInfo, "getting mmkv in main process");

                mode |= 8;

                long handle = getMMKVWithIDAndSize(mmapID, size, mode, cryptKey);

                return new MMKV(handle);

            }

        } else {

            simpleLog(MMKVLogLevel.LevelError, "process name detect fail, try again later");

            return null;

        }

    }

}

//获取默认的存储MMKV

@Nullable

public static MMKV defaultMMKV() {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        //默认获取的mode是单进程,不加密,默认获取的mapId 是DefaultMMKV

        long handle = getDefaultMMKV(1, (String)null);

        return checkProcessMode(handle, "DefaultMMKV", 1);

    }

}

//获取默认的存储文件

@Nullable

public static MMKV defaultMMKV(int mode, @Nullable String cryptKey) {

    if (rootDir == null) {

        throw new IllegalStateException("You should Call MMKV.initialize() first.");

    } else {

        long handle = getDefaultMMKV(mode, cryptKey);

        return checkProcessMode(handle, "DefaultMMKV", mode);

    }

}

//检查进程模式  handle类似句柄不能为0,校验进程模式,如果模式不匹配,会抛异常

//handle 会存入本地的set,如果本地无记录handle, 则将当前模式和handle进行校验,不匹配则报错,抛出异常

//模式校验通过,则将handle 赋值给变量值 private final long nativeHandle,即new MMKV();

//这里的疑问见后续章节分析,发现如果用不同进程模式取获取同一文件名,获取到的handle值是一样的,并未报错

@Nullable

private static MMKV checkProcessMode(long handle, String mmapID, int mode) {

    if (handle == 0L) {

        return null;

    } else {

        if (!checkedHandleSet.contains(handle)) {

            if (!checkProcessMode(handle)) {

                String message;

                if (mode == 1) {

                    message = "Opening a multi-process MMKV instance [" + mmapID + "] with SINGLE_PROCESS_MODE!";

                } else {

                    message = "Opening a MMKV instance [" + mmapID + "] with MULTI_PROCESS_MODE, ";

                    message = message + "while it's already been opened with SINGLE_PROCESS_MODE by someone somewhere else!";

                }

                throw new IllegalArgumentException(message);

            }

            checkedHandleSet.add(handle);

        }

        return new MMKV(handle);

    }

}

4.功能接口

/****退出功能******/

//退出MMKV,针对的是全局MMKV,退出后必须重新调用initialize 否则再获取MMKV 实例时会抛异常

public static native void onExit();

//当某个MMKV实例不再使用时 可以考虑调用此方法关掉,后续需要重新获取该实例

public native void close();

/***操作 加解密 相关功能***/

//获取秘钥针对的是单个MMKV实例

@Nullable

public native String cryptKey();

//重置秘钥,针对单个MMKV文件,返回重置成功或者失败

public native boolean reKey(@Nullable String var1);

public native void checkReSetCryptKey(@Nullable String var1);

/*****获取size相关功能****/

//静态方法 获取pagesize  返回默认值 4K

public static native int pageSize();

//获取某个key 对应的value的size  需要结合PB编码 比如整型 1 PB编码后获取到的是 1,500获取到的是 2,对于引用类型比如字符串,由于PB编码会插入字节表示长度,这里获取的大小是包含长度字节的,比如“12345”,这里获取到是6

public int getValueSize(String key) {

    return this.valueSize(this.nativeHandle, key, false);

}

//获取某个key 对应的value的实际size 需要结合PB编码 大部分情况下获取到的值和上述 getValueSize值是一样的,但对于字符串类型 比如“12345”获取到的是实际大小 5,会去掉表示长度的字节

public int getValueActualSize(String key) {

    return this.valueSize(this.nativeHandle, key, true);

}

//该存储所占用大小 返回的是 pagesize 的倍数

public long totalSize() {

    return this.totalSize(this.nativeHandle);

}

//测试结果是 Key的数量(不含重复项);removeValueForkey 也会删除key,clearALL 后变为0

public long count() {

    return this.count(this.nativeHandle);

}

/***操作key相关功能***/

//是否包含某个key

public boolean containsKey(String key) {

    return this.containsKey(this.nativeHandle, key);

}

//删除某个key 对应的value,同时key 也会删除

public void removeValueForKey(String key) {

    this.removeValueForKey(this.nativeHandle, key);

}

@Nullable

//获取所有key

public native String[] allKeys();

//删除相关key对应的value

public native void removeValuesForKeys(String[] var1);

/***清除功能***/

//清除所有数据 针对单个MMKV

public native void clearAll();

//触发对齐 当删除很多key_value后强制对齐下 

public native void trim();

//清除缓存 由于和SP一样的机制,会读缓存加载所有keyvalue,内存告警时可以清除,再次使用时会加载所有

public native void clearMemoryCache();

//获取版本号

public static native String version();

//获取文件名即mmapId

public native String mmapID();

/****锁相关 多进程时使用*****/

//加锁

public native void lock();

//解锁

public native void unlock();

//尝试加锁

public native boolean tryLock();

//文件是否有效

public static native boolean isFileValid(String var0);

/**强制触发一次同步 写文件**/

//一般情况下不要调用 除非你担心 电池没电了 

public void sync() {

  this.sync(true);

}

//

public void async() {

  this.sync(false);

}

//

private native void sync(boolean var1);

/***共享内存相关***/

public native int ashmemFD();

public native int ashmemMetaFD();

/**当从 MMKV 取一个 String or byte[]的时候,会有一次从 native 到 JVM 的内存拷贝。如果这个值立即传递到另*一个 native 库(JNI),又会有一次从 JVM 到 native 的内存拷贝。当这个值比较大的时候,整个过程会非常浪费。*Native Buffer 就是为了解决这个问题。

*Native Buffer 是由 native 创建的内存缓冲区,在 Java 里封装成 NativeBuffer 类型,可以透明传递到另一个 *native 库进行访问处理。整个过程避免了先拷内存到 JVM 又从 JVM 拷回来导致的浪费

**/

public static NativeBuffer createNativeBuffer(int size) {

    long pointer = createNB(size);

    return pointer <= 0L ? null : new NativeBuffer(pointer, size);

}

public static void destroyNativeBuffer(NativeBuffer buffer) {

    destroyNB(buffer.pointer, buffer.size);

}

public int writeValueToNativeBuffer(String key, NativeBuffer buffer) {

    return this.writeValueToNB(this.nativeHandle, key, buffer.pointer, buffer.size);

}

//多进程通知监听

private static native void setWantsContentChangeNotify(boolean var0);

public native void checkContentChangedByOuterProcess();

5.写/读数据

//读写bool类型相关

public boolean encode(String key, boolean value) {

    return this.encodeBool(this.nativeHandle, key, value);

}

public boolean decodeBool(String key) {

    return this.decodeBool(this.nativeHandle, key, false);

}

public boolean decodeBool(String key, boolean defaultValue) {

    return this.decodeBool(this.nativeHandle, key, defaultValue);

}

//读写int 类型

public boolean encode(String key, int value) {

    return this.encodeInt(this.nativeHandle, key, value);

}

public int decodeInt(String key) {

    return this.decodeInt(this.nativeHandle, key, 0);

}

public int decodeInt(String key, int defaultValue) {

    return this.decodeInt(this.nativeHandle, key, defaultValue);

}

//读写long 类型

public boolean encode(String key, long value) {

    return this.encodeLong(this.nativeHandle, key, value);

}

public long decodeLong(String key) {

    return this.decodeLong(this.nativeHandle, key, 0L);

}

public long decodeLong(String key, long defaultValue) {

    return this.decodeLong(this.nativeHandle, key, defaultValue);

}

//读写float类型

public boolean encode(String key, float value) {

    return this.encodeFloat(this.nativeHandle, key, value);

}

public float decodeFloat(String key) {

    return this.decodeFloat(this.nativeHandle, key, 0.0F);

}

public float decodeFloat(String key, float defaultValue) {

    return this.decodeFloat(this.nativeHandle, key, defaultValue);

}

//读写double 类型

public boolean encode(String key, double value) {

    return this.encodeDouble(this.nativeHandle, key, value);

}

public double decodeDouble(String key) {

    return this.decodeDouble(this.nativeHandle, key, 0.0D);

}

public double decodeDouble(String key, double defaultValue) {

    return this.decodeDouble(this.nativeHandle, key, defaultValue);

}

//读写字符串类型

public boolean encode(String key, @Nullable String value) {

    return this.encodeString(this.nativeHandle, key, value);

}

@Nullable

public String decodeString(String key) {

    return this.decodeString(this.nativeHandle, key, (String)null);

}

@Nullable

public String decodeString(String key, @Nullable String defaultValue) {

    return this.decodeString(this.nativeHandle, key, defaultValue);

}

//读写 set<string> 类型

public boolean encode(String key, @Nullable Set<String> value) {

    return this.encodeSet(this.nativeHandle, key, value == null ? null : (String[])value.toArray(new String[0]));

}

@Nullable

public Set<String> decodeStringSet(String key) {

    return this.decodeStringSet(key, (Set)null);

}

@Nullable

public Set<String> decodeStringSet(String key, @Nullable Set<String> defaultValue) {

    return this.decodeStringSet(key, defaultValue, HashSet.class);

}

@Nullable

public Set<String> decodeStringSet(String key, @Nullable Set<String> defaultValue, Class<? extends Set> cls) {

    String[] result = this.decodeStringSet(this.nativeHandle, key);

    if (result == null) {

        return defaultValue;

    } else {

        Set a;

        try {

            a = (Set)cls.newInstance();

        } catch (IllegalAccessException var7) {

            return defaultValue;

        } catch (InstantiationException var8) {

            return defaultValue;

        }

        a.addAll(Arrays.asList(result));

        return a;

    }

}

//读写 byte[]

public boolean encode(String key, @Nullable byte[] value) {

    return this.encodeBytes(this.nativeHandle, key, value);

}

@Nullable

public byte[] decodeBytes(String key) {

    return this.decodeBytes(key, (byte[])null);

}

@Nullable

public byte[] decodeBytes(String key, @Nullable byte[] defaultValue) {

    byte[] ret = this.decodeBytes(this.nativeHandle, key);

    return ret != null ? ret : defaultValue;

}

//读写序列化类型 Parcelable

public boolean encode(String key, @Nullable Parcelable value) {

    if (value == null) {

        return this.encodeBytes(this.nativeHandle, key, (byte[])null);

    } else {

        Parcel source = Parcel.obtain();

        value.writeToParcel(source, value.describeContents());

        byte[] bytes = source.marshall();

        source.recycle();

        return this.encodeBytes(this.nativeHandle, key, bytes);

    }

}

@Nullable

public <T extends Parcelable> T decodeParcelable(String key, Class<T> tClass) {

    return this.decodeParcelable(key, tClass, (Parcelable)null);

}

@Nullable

public <T extends Parcelable> T decodeParcelable(String key, Class<T> tClass, @Nullable T defaultValue) {

    if (tClass == null) {

        return defaultValue;

    } else {

        byte[] bytes = this.decodeBytes(this.nativeHandle, key);

        if (bytes == null) {

            return defaultValue;

        } else {

            Parcel source = Parcel.obtain();

            source.unmarshall(bytes, 0, bytes.length);

            source.setDataPosition(0);

            Parcelable var8;

            try {

                String name = tClass.toString();

                Creator creator;

                synchronized(mCreators) {

                    //先从本地缓存中获取

                    creator = (Creator)mCreators.get(name);

                    if (creator == null) {

                        //获取public 变量  CREATOR,  由于是反射所以会保存到hashmap中 避免耗时

                        Field f = tClass.getField("CREATOR");

                        creator = (Creator)f.get((Object)null);

                        if (creator != null) {

                            mCreators.put(name, creator);

                        }

                    }

                }

                //如果为空 则表明类没有实现Parcelable 序列化

                if (creator == null) {

                    throw new Exception("Parcelable protocol requires a non-null static Parcelable.Creator object called CREATOR on class " + name);

                }

                var8 = (Parcelable)creator.createFromParcel(source);

            } catch (Exception var16) {

                simpleLog(MMKVLogLevel.LevelError, var16.toString());

                return defaultValue;

            } finally {

                source.recycle();

            }

            return var8;

        }

    }

}

//类型:bool/int/byte[]/long/float/double/Parcelable/Set<String>/string;

//数据的读取和写入关键是 nativeHandle 类似文件句柄

6.sp 迁移

public int importFromSharedPreferences(SharedPreferences preferences) {

    //获取sp 存储的所有键值对  这里没有对参数判空

    Map<String, ?> kvs = preferences.getAll();

    if (kvs != null && kvs.size() > 0) {

        Iterator var3 = kvs.entrySet().iterator();

        while(var3.hasNext()) {

            Entry<String, ?> entry = (Entry)var3.next();

            String key = (String)entry.getKey();

            Object value = entry.getValue();

            if (key != null && value != null) {

                if (value instanceof Boolean) {

                    this.encodeBool(this.nativeHandle, key, (Boolean)value);

                } else if (value instanceof Integer) {

                    this.encodeInt(this.nativeHandle, key, (Integer)value);

                } else if (value instanceof Long) {

                    this.encodeLong(this.nativeHandle, key, (Long)value);

                } else if (value instanceof Float) {

                    this.encodeFloat(this.nativeHandle, key, (Float)value);

                } else if (value instanceof Double) {

                    this.encodeDouble(this.nativeHandle, key, (Double)value);

                } else if (value instanceof String) {

                    this.encodeString(this.nativeHandle, key, (String)value);

                } else if (value instanceof Set) {

                    this.encode(key, (Set)value);

                } else {

                    simpleLog(MMKVLogLevel.LevelError, "unknown type: " + value.getClass());

                }

            }

        }

        //返回SP键值对的数量

        return kvs.size();

    } else {

        return 0;

    }

}

//sp 数据变更监听 这里MMKV没有做实现,调用会抛异常 谨慎调用

public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {

        throw new UnsupportedOperationException("Not implement in MMKV");

    }

    public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {

        throw new UnsupportedOperationException("Not implement in MMKV");

    }

7.日志定向/数据恢复

//默认的log等级是levelInfo,转换成相应等级

private static int logLevel2Int(MMKVLogLevel level) {

    byte realLevel;

    switch(level) {

    case LevelDebug:

        realLevel = 0;

        break;

    case LevelWarning:

        realLevel = 2;

        break;

    case LevelError:

        realLevel = 3;

        break;

    case LevelNone:

        realLevel = 4;

        break;

    case LevelInfo:

    default:

        realLevel = 1;

    }

    return realLevel;

}

public static void setLogLevel(MMKVLogLevel level) {

    int realLevel = logLevel2Int(level);

    //设置给MMKV底层库

    setLogLevel(realLevel);

}

//注册gCallbackHandler

public static void registerHandler(MMKVHandler handler) {

    gCallbackHandler = handler;

    if (gCallbackHandler.wantLogRedirecting()) {

        setCallbackHandler(true, true);

        gWantLogReDirecting = true;

    } else {

        setCallbackHandler(false, true);

        gWantLogReDirecting = false;

    }

}

//解注册gCallbackHandler

public static void unregisterHandler() {

    gCallbackHandler = null;

    setCallbackHandler(false, false);

    gWantLogReDirecting = false;

}

//文件CRC校验错误时 默认采用丢弃策略,如果设置了gCallbackHandler,则采用设置的策略,要么丢弃,要么恢复,恢复不可信

private static int onMMKVCRCCheckFail(String mmapID) {

    MMKVRecoverStrategic strategic = MMKVRecoverStrategic.OnErrorDiscard;

    if (gCallbackHandler != null) {

        strategic = gCallbackHandler.onMMKVCRCCheckFail(mmapID);

    }

    simpleLog(MMKVLogLevel.LevelInfo, "Recover strategic for " + mmapID + " is " + strategic);

    Integer value = (Integer)recoverIndex.get(strategic);

    return value == null ? 0 : value;

}

//文件长度错误时 默认采用丢弃策略,如果设置了gCallbackHandler,则采用设置的策略,要么丢弃要么恢复,恢复不可信

private static int onMMKVFileLengthError(String mmapID) {

    MMKVRecoverStrategic strategic = MMKVRecoverStrategic.OnErrorDiscard;

    if (gCallbackHandler != null) {

        strategic = gCallbackHandler.onMMKVFileLengthError(mmapID);

    }


    simpleLog(MMKVLogLevel.LevelInfo, "Recover strategic for " + mmapID + " is " + strategic);

    Integer value = (Integer)recoverIndex.get(strategic);

    return value == null ? 0 : value;

}

//如果gCallbackHandler 设置了日志重定向 则重定向日志由业务方接管,否则MMKV采用默认的Log 输出

private static void mmkvLogImp(int level, String file, int line, String function, String message) {

    if (gCallbackHandler != null && gWantLogReDirecting) {

        gCallbackHandler.mmkvLog(index2LogLevel[level], file, line, function, message);

    } else {

        switch(index2LogLevel[level]) {

        case LevelDebug:

            Log.d("MMKV", message);

            break;

        case LevelWarning:

            Log.w("MMKV", message);

            break;

        case LevelError:

            Log.e("MMKV", message);

        case LevelNone:

        default:

            break;

        case LevelInfo:

            Log.i("MMKV", message);

        }

    }

}

//获取堆栈信息 组织log  函数内没有做越界判断 

private static void simpleLog(MMKVLogLevel level, String message) {

    StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();

    StackTraceElement e = stacktrace[stacktrace.length - 1];

    Integer i = (Integer)logLevel2Index.get(level);

    int intLevel = i == null ? 0 : i;

    mmkvLogImp(intLevel, e.getFileName(), e.getLineNumber(), e.getMethodName(), message);

}


8.数据变化通知(进程间使用)

//注册数据变化监听

public static void registerContentChangeNotify(MMKVContentChangeNotification notify) {

    gContentChangeNotify = notify;

    setWantsContentChangeNotify(gContentChangeNotify != null);

}

//解注册数据变化监听

public static void unregisterContentChangeNotify() {

    gContentChangeNotify = null;

    setWantsContentChangeNotify(false);

}

//其他进程改变数据后通知

private static void onContentChangedByOuterProcess(String mmapID) {

    if (gContentChangeNotify != null) {

        gContentChangeNotify.onContentChangedByOuterProcess(mmapID);

    }

}

private static native void setWantsContentChangeNotify(boolean var0);

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容