一、 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 问题
-
应用程序反常退出或崩溃: 当应用程序在写入或更新数据过程中忽然退出或崩溃时,或许导致MMKV数据损坏。例如,应用程序在写入数据时遇到内存不足或其他反常情况,或许会导致数据写入不完整。
-
系统意外重启或关机: 如果设备在MMKV写入或更新数据过程中忽然重启或关机,或许导致MMKV数据损坏。这种情况下,操作系统或许没有满足的时刻将内存映射文件的内容写入磁盘。
三、DataStore
开发者攻略
关键字: SingleProcessDataStore.updateData、downloadFlow:通过flow实现内存缓存
写文件
数据源->actor协程办理改为次序执行->通过serializer写入文件中去 -> 同步内存缓存值
protoBuffer写文件,->存入内存缓存 确保了数据一致性
长处
- 根据Flow,确保线程安全性
- 可以监听到成功和失败
- 主动完结 SharedPreferences 迁移到 DataStore,确保数据一致性,不会形成数据损坏
您可以运用 runBlocking() 从 DataStore 同步读取数据
www.jianshu.com/p/90b152565…
运用
- 创建preferenceDataStore
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
- 读取内容
val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
.map { preferences ->
// No type safety.
preferences[EXAMPLE_COUNTER] ?: 0
}
- 写入内容
Preferences DataStore 提供了一个
edit()
函数,用于以业务方式更新DataStore
中的数据。该函数的transform
参数接受代码块,您可以在其间根据需要更新值。转换块中的所有代码均被视为单个业务。
suspend fun incrementCounter() {
context.dataStore.edit { settings ->
val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
settings[EXAMPLE_COUNTER] = currentCounterValue + 1
}
}
- 同步方式运用DataStore
val exampleData = runBlocking { context.dataStore.data.first() }