Android基础笔记:MMKV VS SP

290 阅读3分钟
功能MMKVSharedPreferences
是否阻塞主线程
是否线程安全
是否支持跨进程
是否支持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提交方法返回值线程同步存在问题
commitboolean同步cpu忙碌的时候数据大容易造成ANR
applyvoid异步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

image.png

MMKV中申请的内存如下,然后保存到Map当中,每次添加数据与修改数据都往后面增加,因为Map中相同的key会覆盖,从而实现数据更新;因此MMKV相比sp会更加的快;