简介
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直接操作内存,由系统自动同步到文件,仅需一次数据拷贝。