注册

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 问题


image.png




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




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




三、DataStore


开发者指南


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


写文件

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


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


优点


  • 基于Flow,保证线程安全性
  • 可以监听到成功和失败
  • 自动完成 SharedPreferences 迁移到 DataStore,保证数据一致性,不会造成数据损坏

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


http://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() }

作者:zyl409214686
链接:https://juejin.cn/post/7219123036779724858
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册