| 功能 | MMKV | SharedPreferences |
|---|---|---|
| 是否阻塞主线程 | 否 | 是 |
| 是否线程安全 | 是 | 是 |
| 是否支持跨进程 | 是 | 否 |
| 是否支持protocol buffers | 是 | 否 |
| 是否类型安全 | 否 | 否 |
| 是否能监听数据变化 | 否 | 是 |
SharedPreferences
简单描述:
| 特点 | 说明 |
|---|---|
| 数据格式 | XML格式保存 |
| 初始化 | 子线程使用I0读取整个文件,进行XML解析,存入内存Map集合 |
| 保存 | commit同步提交,阻塞主线程; apply 异步提交,无法获取结果且可能数据丢失 |
| 更新 | 把Map中的数据,全部序列化为XML,覆盖文件保存。(全量更 新) |
1.SharedPreferences初始可能会导致App启动变慢,原因如下:
//SharedPreferencesImpl源码中的构造方法
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
//读取数据方法
@Override
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
@GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
可以看到SP初始化会让mLoaded设为false,当loadFromDisk完成之后才会变成true(操作在loadFromDisk方法里面),并且唤醒锁mLock.notifyAll(),如果启动app的时候去调用SP去读取数据就会卡主线程,这时候就会让App启动变慢。
2.SharedPreferences#Editor#(commit()|apply())
| sp提交方法 | 返回值 | 线程同步 | 存在问题 |
|---|---|---|---|
| commit | boolean | 同步 | cpu忙碌的时候数据大容易造成ANR |
| apply | void | 异步 | app跳转的时候也可能造成ANR |
apply异步跳转导致ANR问题是因为,普通模式下ActivityA->ActivityB两个activity的生命周期:A:onPause->B:onCreate-onStart-onResume->A:onStop
首先查看SharedPreferencesImpl#apply()方法
@Override
public void apply() {
*****//忽略代码
//添加一个Runnable到任务队列中去
QueuedWork.addFinisher(awaitCommit);
****//忽略代码
}
在A调用onPause的之前可以查看到ActivityThread的源码:
@Override
public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
int configChanges, PendingTransactionActions pendingActions, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
if (userLeaving) {
performUserLeavingActivity(r);
}
r.activity.mConfigChangeFlags |= configChanges;
performPauseActivity(r, finished, reason, pendingActions);
// Make sure any pending writes are now committed.
if (r.isPreHoneycomb()) {
//这里会等待全部的任务都执行完成
QueuedWork.waitToFinish();
}
mSomeActivitiesChanged = true;
}
}
如上面代码,如果在Activity跳转中,SP#Editor#apply中操作了大量数据导致耗时也会导致ANR发生。
MMKV
MMKV官网原理解析:design · Tencent/MMKV Wiki · GitHub
1.MMKV相比sp解决了以下问题;
| MMKV相比SP解决的问题 | 方式 |
|---|---|
| 比XML更精简的数据格式 | 保存有效的数据 |
| 高效的文件操作 | 使用mmap内存映射方案 |
| 更优的数据更新方式 | 往有效数据后面添加,然后map覆盖实现更新 |
| 支持多进程 | 文件锁、crc校验 |
2.简述MMKV中IO优化
MMKV使用mmap相比sp中IO的优化体验如下:
SP使用的传统IO读取数据:用户->CPU->本地文件
MMKV使用mmap(memory mapping)IO读取数据:是一种零拷贝技术;零拷贝技术是指没有CPU参与的拷贝;简单理解:用户->本地文件;
对比下来可以省去CPU中间层的拷贝;
3.简述MMKV中的数据结构protobuf
MMKV中申请的内存如下,然后保存到Map当中,每次添加数据与修改数据都往后面增加,因为Map中相同的key会覆盖,从而实现数据更新;因此MMKV相比sp会更加的快;