Android Key Value存储技术选型

一、 SP 问题: 卡顿anr

问题1: 写入大数据\当时资源较紧张情况进行写入, 切换页面(执行onstop), 会出现卡顿

1.1. SharedPreferencesImpl.apply, 异步操作
@Override
public void apply() {
    ...
    final Runnable awaitCommit = new Runnable() {
            @Override
            public void run() {
                try {
                    mcr.writtenToDiskLatch.await();
                } catch (InterruptedException ignored) {
            }
        };
    QueuedWork.addFinisher(awaitCommit);
    Runnable postWriteRunnable = new Runnable() {
            @Override
            public void run() {
                awaitCommit.run();
                QueuedWork.removeFinisher(awaitCommit);
            }
        };
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
    ...
}
private void enqueueDiskWrite(final MemoryCommitResult mcr,
                              final Runnable postWriteRunnable) {
    final boolean isFromSyncCommit = (postWriteRunnable == null);
    final Runnable writeToDiskRunnable = new Runnable() {
            @Override
            public void run() {
                synchronized (mWritingToDiskLock) {
                    writeToFile(mcr, isFromSyncCommit);
                    writtenToDiskLatch.countDown();//写完文件执行countDown
                }
                ..
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };
1.2 ActivityThread.handleStopActivity(), onStop 生命周期
@Override
public void handleStopActivity(ActivityClientRecord r, int configChanges,
        PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
    ...
    //要点关注
    // Make sure any pending writes are now committed.
    if (!r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }
    ...
}

要点关注

if (!r.isPreHoneycomb()) { QueuedWork.waitToFinish(); }

public static void waitToFinish() {
    try {
        while (true) {
            Runnable finisher;
            synchronized (sLock) {
                finisher = sFinishers.poll();
            }
            if (finisher == null) {
                break;
            }
            finisher.run();
        }
    } finally {
        sCanDelay = true;
    }
}

while循环中finisher会堵塞当时线程,等候完结写入文件任务

问题2: sp本地文件巨大,初始化阶段(还未初始化完结), 去读sp数据, 会出现卡顿

2.1 初始化
SharedPreferencesImpl(File file, int mode) {
    ...
    mLoaded = false;
    ...
    startLoadFromDisk();
}
private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}
2.2 从磁盘读取数据到内存
private void loadFromDisk() {
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }
    ...
    Map<String, Object> map = null;
    ...
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16 * 1024);
                map = (Map<String, Object>) XmlUtils.readMapXml(str);
            } catch (Exception e) {
                Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
            } finally {
                IoUtils.closeQuietly(str);
            }
        }
    ...
    synchronized (mLock) {
        mLoaded = true;
        ...
    }
    finally{
        //notify 线程
        mLock.notifyAll();
    }
}
2.3 获取数据
public String getString(String key, @Nullable String defValue) {
    synchronized (mLock) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}
private void awaitLoadedLocked() {
    ...
    while (!mLoaded) {
        try {
            //堵塞线程
            mLock.wait();
        } catch (InterruptedException unused) {
        }
    }
    ...
}

关键字: mLoaded、 mLock.wait() 、 mLock.notifyAll()

二、 mmkv 问题: 数据损坏

2.1 原理 mmap 内存映射文件

MMKV原理

2.2 问题

Android Key Value存储技术选型

  • 应用程序反常退出或崩溃: 当应用程序在写入或更新数据过程中忽然退出或崩溃时,或许导致MMKV数据损坏。例如,应用程序在写入数据时遇到内存不足或其他反常情况,或许会导致数据写入不完整。

  • 系统意外重启或关机: 如果设备在MMKV写入或更新数据过程中忽然重启或关机,或许导致MMKV数据损坏。这种情况下,操作系统或许没有满足的时刻将内存映射文件的内容写入磁盘。

三、DataStore

开发者攻略

关键字: SingleProcessDataStore.updateData、downloadFlow:通过flow实现内存缓存

写文件

数据源->actor协程办理改为次序执行->通过serializer写入文件中去 -> 同步内存缓存值

protoBuffer写文件,->存入内存缓存 确保了数据一致性

长处
  • 根据Flow,确保线程安全性
  • 可以监听到成功和失败
  • 主动完结 SharedPreferences 迁移到 DataStore,确保数据一致性,不会形成数据损坏

您可以运用 runBlocking() 从 DataStore 同步读取数据

www.jianshu.com/p/90b152565…

运用
  1. 创建preferenceDataStore
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
  1. 读取内容
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
 .map { preferences ->
  // No type safety.
  preferences[EXAMPLE_COUNTER] ?: 0
}
  1. 写入内容

Preferences DataStore 提供了一个edit()函数,用于以业务方式更新DataStore中的数据。该函数的transform参数接受代码块,您可以在其间根据需要更新值。转换块中的所有代码均被视为单个业务。

suspend fun incrementCounter() {
 context.dataStore.edit { settings ->
  val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
  settings[EXAMPLE_COUNTER] = currentCounterValue + 1
 }
}
  1. 同步方式运用DataStore
val exampleData = runBlocking { context.dataStore.data.first() }