一、成员变量
//错误恢复策略相关
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);