一、前言:
MMKV 是腾讯开源的一款基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强,从 年中至今在微信上使用,其性能和稳定性经过了时间的验证。GitHub地址:/Tencent/MMKV
1、为什么要替代SharedPreferences?
1,数据加密。 在 Android 环境里,数据加密是非常必须的,SP实际上是把键值对放到本地文件中进行存储。如果要保证数据安全需要自己加密,MMKV 使用了 AES CFB-128 算法来加密/解密。
2,多进程共享。系统自带的 SharedPreferences 对多进程的支持不好。现有基于 ContentProvider 封装的实现,虽然多进程是支持了,但是性能低下,经常导致 ANR。考虑到 mmap 共享内存本质上是多进程共享的,MMKV 在这个基础上,深入挖掘了 Android 系统的能力,提供了可能是业界最高效的多进程数据共享组件。
3,匿名内存。 在多进程共享的基础上,考虑到某些敏感数据(例如密码)需要进程间共享,但是不方便落地存储到文件上,直接用 mmap 不合适。而Android 系统提供了 Ashmem 匿名共享内存的能力,它在进程退出后就会消失,不会落地到文件上,非常适合这个场景。MMKV 基于此也提供了 Ashmem(匿名共享内存) MMKV 的功能。
4,效率更高。MMKV 使用protobuf进行序列化和反序列化,比起SP的xml存放方式,更加高效。
5,支持从 SP迁移,如果你之前项目里面都是使用SP,现在想改为使用MMKV,只需几行代码即可将之前的SP实现迁移到MMKV。
2、支持的数据类型
1,支持以下 Java 语言基础类型:
boolean、int、long、float、double、byte[]
2,支持以下 Java 类和容器:
String、Set< String >
任何实现了Parcelable的类型
二、使用:
1、添加依赖
dependencies {
implementation 'com.tencent:mmkv-static:1.2.7'
}
2、初始化
在自定义的Application中:
MMKV.initialize(this);
3、数据操作(工具类封装)
MMKV 提供一个全局的实例,可以直接使用:
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
kv.encode("int", Integer.MIN_VALUE);
kv.encode("float", -3.14f);
kv.encode("string", "Hello from mmkv");
byte[] bytes = {'m', 'm', 'k', 'v'};
kv.encode("bytes",bytes);
//打印结果:
Timber.d("bool值: "+ kv.decodeBool("bool"));
Timber.d("int值: "+ kv.decodeInt("int"));
Timber.d("float值: "+ kv.decodeFloat("float"));
Timber.d("string值: "+ kv.decodeString("string"));
Timber.d("bytes值: "+ new String(kv.decodeBytes("bytes")));
Timber.d("1是否包含: "+ kv.containsKey("stringName"));
Timber.d("2是否包含: "+ kv.containsKey("stringffName"));
结果:
com.sumansoul.myviewdemo D/MainActivity: bool值: true
com.sumansoul.myviewdemo D/MainActivity: int值: -2147483648
com.sumansoul.myviewdemo D/MainActivity: float值: -3.14
com.sumansoul.myviewdemo D/MainActivity: string值: Hello from mmkv
com.sumansoul.myviewdemo D/MainActivity: bytes值: mmkv
com.sumansoul.myviewdemo D/MainActivity: 1是否包含: true
com.sumansoul.myviewdemo D/MainActivity: 2是否包含: false
4、删除 & 查询:
MMKV kv = MMKV.defaultMMKV();
kv.removeValueForKey("bool");
System.out.println("bool: " + kv.decodeBool("bool"));
kv.removeValuesForKeys(new String[]{"int", "long"});
System.out.println("allKeys: " + Arrays.toString(kv.allKeys()));
kv.clearAll();
boolean hasBool = kv.containsKey("bool");
5、如果不同业务需要区别存储,也可以单独创建自己的实例:
MMKV mmkv = MMKV.mmkvWithID("MyID");
mmkv.encode("bool", true);
6、默认是支持单进程的,如果业务需要多进程访问,那么在初始化的时候加上标志位 MMKV.MULTI_PROCESS_MODE:
MMKV mmkv = MMKV.mmkvWithID("InterProcessKV", MMKV.MULTI_PROCESS_MODE);
mmkv.encode("bool", true);
三、自定义根目录
MMKV 默认把文件存放在$(FilesDir)/mmkv/目录。你可以在 App 启动时自定义根目录:
String dir = getFilesDir().getAbsolutePath() + "/mmkv_2";
String rootDir = MMKV.initialize(dir);
Log.i("MMKV", "mmkv root: " + rootDir);
MMKV 甚至支持自定义某个文件的目录:
String relativePath = getFilesDir().getAbsolutePath() + "/mmkv_3";
MMKV kv = MMKV.mmkvWithID("testCustomDir", relativePath);
四、工具类封装如下:
1、工具类:
public class SpUtils {
private static SpUtils mInstance;
private static MMKV mv;
private SpUtils() {
mv = MMKV.defaultMMKV();
}
/**
* 初始化MMKV,只需要初始化一次,建议在Application中初始化
*
*/
public static SpUtils getInstance() {
if (mInstance == null) {
synchronized (SpUtils.class) {
if (mInstance == null) {
mInstance = new SpUtils();
}
}
}
return mInstance;
}
/**
* 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
*
* @param key
* @param object
*/
public static void encode(String key, Object object) {
if (object instanceof String) {
mv.encode(key, (String) object);
} else if (object instanceof Integer) {
mv.encode(key, (Integer) object);
} else if (object instanceof Boolean) {
mv.encode(key, (Boolean) object);
} else if (object instanceof Float) {
mv.encode(key, (Float) object);
} else if (object instanceof Long) {
mv.encode(key, (Long) object);
} else if (object instanceof Double) {
mv.encode(key, (Double) object);
} else if (object instanceof byte[] ) {
mv.encode(key, (byte[]) object);
} else {
mv.encode(key, object.toString());
}
}
public static void encodeSet(String key, Set sets) {
mv.encode(key, sets);
}
public static void encodeParcelable(String key, Parcelable obj) {
mv.encode(key, obj);
}
/**
* 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
*/
public static Integer decodeInt(String key) {
return mv.decodeInt(key, 0);
}
public static Double decodeDouble(String key) {
return mv.decodeDouble(key, 0.00);
}
public static Long decodeLong(String key) {
return mv.decodeLong(key, 0L);
}
public static Boolean decodeBoolean(String key) {
return mv.decodeBool(key, false);
}
public static Float decodeFloat(String key) {
return mv.decodeFloat(key, 0F);
}
public static byte[] decodeBytes(String key) {
return mv.decodeBytes(key);
}
public static String decodeString(String key) {
return mv.decodeString(key,"");
}
public static Set decodeStringSet(String key) {
return mv.decodeStringSet(key, Collections.emptySet());
}
public static Parcelable decodeParcelable(String key) {
return mv.decodeParcelable(key, null);
}
/**
* 移除某个key对
*
* @param key
*/
public static void removeKey(String key) {
mv.removeValueForKey(key);
}
/**
* 清除所有key
*/
public static void clearAll() {
mv.clearAll();
}
/**
* 是否包含某个key
*/
public static boolean containsKey(String key) {
return mv.containsKey(key);
}
}
2、在application中初始化:
//数据存储初始化
MMKV.initialize(this);
SpUtils.getInstance();
3、Activity中使用:
SpUtils.encode("stringName","中国欢迎你");
SpUtils.encode("boolName",true);
SpUtils.encode("intName",1204);
//打印日志
Timber.d("bool值: "+ SpUtils.decodeBoolean("boolName"));
Timber.d("int值: "+ SpUtils.decodeInt("intName"));
Timber.d("string值: "+ SpUtils.decodeString("stringName"));
结果:
-02-22 16:45:51.444 13379-13379/com.sumansoul.myviewdemo D/MainActivity: bool值: true
-02-22 16:45:51.444 13379-13379/com.sumansoul.myviewdemo D/MainActivity: int值: 1204
-02-22 16:45:51.445 13379-13379/com.sumansoul.myviewdemo D/MainActivity: string值: 中国欢迎你
五、MMKV组件实现原理以及和SharedPreferences的比较
1、MMKV:
原理:MMKV是基于mmap内存映射关系的key-value组件,底层序列化/反序列化使用protobuf实现。性能高,稳定性强。从就在微信上使用,已经移植到了Android/MacOS/Windows平台
MMAP的优势:
MMAP对文件的读写操作只需要 从磁盘到用户主存的一次数据拷贝,减少了数据拷贝次数,提高了文件读写效率;
MMAP使用逻辑内存对此盘文件进行映射,操作内存就相当于操作文件,不需要开启线程,操作MMAP的速度和操作内存一样快;
MMAP提供一段可以随时写入的内存块,APP只管往里面写数据,在内存不足,进程退出的情况下由操作系统负责将数据写回到文件,不必担心Crash导致数据丢失;
2、SharedPreferences:
原理:SharedPreferences是Android提供的一种使用XML文件保存内容的机制,内部通过XML写入文件。
特点:
读写方式:直接I/O(耗时,容易出现anr)
数据格式:xml
写入方式:全量更新