MMKV攻克SP ANR难题:从原理到实战的高性能键值存储方案

简介

MMKV作为微信开源的高性能键值存储框架,通过内存映射和增量更新技术,彻底解决了SharedPreferences全量更新导致的ANR问题。本文将深入剖析SP的ANR根源,详细解析MMKV的技术原理,并提供从零开始的完整集成指南和代码实战。通过性能对比和实际案例,您将看到MMKV如何实现零ANR、微秒级响应的存储方案。

一、SP的ANR问题:技术剖析与实战案例

1.1 SP的ANR根源:同步提交与全量更新

SharedPreferences的ANR问题主要来源于三个致命缺陷。首先是commit的同步提交机制,直接阻塞主线程等待磁盘写入完成。在SP的commit源码中,我们看到:

public boolean commit() {
   
   
    MemoryCommitResult mcr = commitToMemory();
    enqueueDiskWrite(mcr, null);
    try {
   
   
        mcr.writtenToDiskLatch.await(); // 阻塞主线程直到写入完成
    } catch (InterruptedException e) {
   
   
        return false;
    }
    notifyListeners(mcr);
    return mcr.writeToDiskResult;
}

当数据量较大时,这个await()调用会导致主线程长时间阻塞,进而触发ANR。其次是apply的异步陷阱,虽然表面上是异步操作,但在Activity生命周期关键节点(如onStop)会强制等待所有写入完成:

public void apply() {
   
   
    final MemoryCommitResult mcr = commitToMemory();
    final Runnable awaitCommit = new Runnable() {
   
   
        @Override
        public void run() {
   
   
            mcr.writtenToDiskLatch.await(); // 异步等待
        }
    };
    QueuedWork.addFinisher(awaitCommit); // 添加到队列
    enqueueDiskWrite(mcr, postWriteRunnable);
}

最后是全量更新模式,SP每次修改数据时,会将所有键值重新序列化为XML并全量写入文件,导致IO频繁且耗时。

1.2 SP的内存缓存与加载阻塞

SP的缓存机制也存在严重问题。在ContextImpl类中存在一个静态的ArrayMap对象用于缓存不同packageName下的所有sp文件对象:

private static ArrayMap<String, ArrayMap 文件,     > sSharedPrefsCache;

这个缓存数组在初始化和赋值,但从未对数组对象里的数据进行移除或释放操作。更严重的是,当首次加载SP时,SP的构造函数会启动一个子线程去加载磁盘文件:

private void startLoadFromDisk() {
   
   
    synchronized (mLock) {
   
   
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
   
   
        public void run() {
   
   
            loadFromDisk();
        }
    }.start();
}

而如果主线程紧接着调用getValue,会通过awaitLoadedLocked方法阻塞主线程:

private void awaitLoadedLocked() {
   
   
    if (!mLoaded) {
   
   
        BlockGuard.getThreadPolicy().onReadFromDisk();
    }
    while (!mLoaded) {
   
   
        try {
   
   
            mLock.wait(); // 阻塞主线程直到加载完成
        } catch (InterruptedException unused) {
   
   
        }
    }
    if (mThrowable != null) {
   
   
        throw new IllegalStateException(mThrowable);
    }
}

当SP文件较大或加载线程调度延迟时,主线程会陷入长时间等待,直接导致ANR。这在实际应用中非常常见,尤其是在应用启动时加载大量配置数据的场景。

1.3 实际案例:SP导致的ANR

在微信的日常运营中,曾多次因SP问题导致应用闪退和ANR。例如,在会话列表和会话界面等有大量cell的地方,需要频繁记录用户操作计数器,但SP的全量更新机制导致每次修改都会重写整个XML文件,造成滑动卡顿和ANR。更严重的是,SP在多进程环境下支持差,容易出现数据错乱和丢失。

闲鱼团队也遇到过类似问题,他们发现ANR日志中存在主线程等待sp apply队列持久化完成、主线程对sp commit以及主线程阻塞等待sp加载数据完成的情况。其中有一个消息执行耗时155ms,挂钟耗时411ms,导致ANR。问题根源是在主线程调用较重的初始化操作,并且存在跨进程调用,阻塞了后面Receiver、Service等消息的调度执行。

二、MMKV的技术原理:内存映射与增量更新

2.1 内存映射(mmap)技术

MMKV的核心技术是内存映射(mmap)。与SP的两次数据拷贝不同,MMKV通过mmap将文件直接映射到内存,APP直接操作内存,由系统自动同步到文件,仅需一次数据拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android洋芋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值