本文已收录到 AndroidFamily,技能和职场问题,请关注公众号 [彭旭锐] 发问。
前言
咱们持续上一篇文章的分析:
- Android 初代 K-V 存储结构 SharedPreferences,旧时代的余晖?(上)
- Android 初代 K-V 存储结构 SharedPreferences,旧时代的余晖?(下)
6. 两种写回战略
在获得业务目标后,咱们持续分析 Editor 接口中的 commit 同步写回战略和 apply 异步写回战略。
6.1 commit 同步写回战略
Editor#commit 同步写回相对简单,中心进程分为 4 步:
- 1、调用
commitToMemory()
创立MemoryCommitResult
业务目标; - 2、调用
enqueueDiskWrite(mrc, null)
提交磁盘写回使命(在其时线程履行); - 3、调用 CountDownLatch#await() 堵塞等候磁盘写回完结;
- 4、调用 notifyListeners() 触发回调监听。
commit 同步写回示意图
其实严格来说,commit 同步写回也不肯定是在其时线程同步写回,也有或许在后台 HandlerThread 线程写回。但不管怎么样,关于 commit 同步写回来说,都会调用 CountDownLatch#await() 堵塞等候磁盘写回完结,所以在逻辑上也等价于在其时线程同步写回。
SharedPreferencesImpl.java
public final class EditorImpl implements Editor {
@Override
public boolean commit() {
// 1、获取业务目标(前文已分析)
MemoryCommitResult mcr = commitToMemory();
// 2、提交磁盘写回使命
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* 写回成功回调 */);
// 3、堵塞等候写回完结
mcr.writtenToDiskLatch.await();
// 4、触发回调监听器
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
}
6.2 apply 异步写回战略
Editor#apply 异步写回相对复杂,中心进程分为 5 步:
- 1、调用
commitToMemory()
创立MemoryCommitResult
业务目标; - 2、创立
awaitCommit
Ruunnable 并提交到 QueuedWork 中。awaitCommit 中会调用 CountDownLatch#await() 堵塞等候磁盘写回完结; - 3、创立
postWriteRunnable
Runnable,在 run() 中会履行 awaitCommit 使命并将其从 QueuedWork 中移除; - 4、调用
enqueueDiskWrite(mcr, postWriteRunnable)
提交磁盘写回使命(在子线程履行); - 5、调用 notifyListeners() 触发回调监听。
能够看到不管是调用 commit 仍是 apply,终究都会调用 SharedPreferencesImpl#enqueueDiskWrite()
提交磁盘写回使命。
差异在于:
- 在 commit 中 enqueueDiskWrite() 的第 2 个参数是 null;
- 在 apply 中 enqueueDiskWrite() 的第 2 个参数是一个
postWriteRunnable
写回完毕的回调目标,enqueueDiskWrite() 内部就是依据第 2 个参数来差异 commit 和 apply 战略。
apply 异步写回示意图
SharedPreferencesImpl.java
@Override
public void apply() {
// 1、获取业务目标(前文已分析)
final MemoryCommitResult mcr = commitToMemory();
// 2、提交 aWait 使命
// 疑问:postWriteRunnable 能够了解,awaitCommit 是什么?
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
// 堵塞线程直到磁盘使命履行完毕
mcr.writtenToDiskLatch.await();
}
};
QueuedWork.addFinisher(awaitCommit);
// 3、创立写回成功回调
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
// 履行 aWait 使命
awaitCommit.run();
// 移除 aWait 使命
QueuedWork.removeFinisher(awaitCommit);
}
};
// 4、提交磁盘写回使命,并绑定写回成功回调
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable /* 写回成功回调 */);
// 5、触发回调监听器
notifyListeners(mcr);
}
QueuedWork.java
// 提交 aWait 使命(后文详细分析)
private static final LinkedList<Runnable> sFinishers = new LinkedList<>();
public static void addFinisher(Runnable finisher) {
synchronized (sLock) {
sFinishers.add(finisher);
}
}
public static void removeFinisher(Runnable finisher) {
synchronized (sLock) {
sFinishers.remove(finisher);
}
}
这儿有一个疑问:
在 apply() 办法中,在履行 enqueueDiskWrite() 前创立了 awaitCommit 使命并加入到 QueudWork 等候行列,直到磁盘写回完毕才将 awaitCommit 移除。这个 awaitCommit 使命是做什么的呢?
咱们略微再答复,先持续往下走。
6.3 enqueueDiskWrite() 提交磁盘写回业务
能够看到,不管是 commit 仍是 apply,终究都会调用 SharedPreferencesImpl#enqueueDiskWrite() 提交写回磁盘使命。虽然 enqueueDiskWrite() 还没到实在调用磁盘写回操作的当地,但确实创立了与磁盘 IO 相关的 Runnable 使命,中心进程分为 4 步:
- 进程 1:依据是否有 postWriteRunnable 回调差异是 commit 和 apply;
- 进程 2:创立磁盘写回使命(实在履行磁盘 IO 的当地):
- 2.1 调用 writeToFile() 履行写回磁盘 IO 操作;
- 2.2 在写回完毕后对前文说到的 mDiskWritesInFlight 计数自减 1;
- 2.3 履行 postWriteRunnable 写回成功回调;
- 进程 3:假如是异步写回,则提交到 QueuedWork 使命行列;
- 进程 4:假如是同步写回,则查看 mDiskWritesInFlight 变量。假如存在并发写回的业务,则也要提交到 QueuedWork 使命行列,否则就直接在其时线程履行。
其中进程 2 是实在履行磁盘 IO 的当地,逻辑也很好了解。欠好了解的是,咱们发现除了 “同步写回并且不存在并发写回业务” 这种特殊状况,其他状况都会交给 QueuedWork
再调度一次。
在通过 QueuedWork#queue
提交使命时,会将 writeToDiskRunnable 使命追加到 sWork 使命行列中。假如是首次提交使命,QueuedWork 内部还会创立一个 HandlerThread
线程,通过这个子线程完结异步的写回使命。这说明 SharedPreference 的异步写回相当于运用了一个单线程的线程池,事实上在 Android 8.0 曾经的版别中就是运用一个 singleThreadExecutor 线程池完结的。
提交使命示意图
SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
// 1、依据是否有 postWriteRunnable 回调差异是 commit 和 apply
final boolean isFromSyncCommit = (postWriteRunnable == null);
// 2、创立磁盘写回使命
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 2.1 写入磁盘文件
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
// 2.2 mDiskWritesInFlight:进行中业务自减 1
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
// 2.3 触发写回成功回调
postWriteRunnable.run();
}
}
};
// 3、同步写回且不存在并发写回,则直接在其时线程
// 这就是前文说到 “commit 也不是肯定在其时线程同步写回” 的源码出处
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
// 假如存在并发写回的业务,则此处 wasEmpty = false
wasEmpty = mDiskWritesInFlight == 1;
}
// wasEmpty 为 true 说明其时只要一个线程在履行提交操作,那么就直接在此线程上完结使命
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
// 4、交给 QueuedWork 调度(同步使命不能够延迟)
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit /*是否能够延迟*/ );
}
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// 稍后分析
}
QueuedWork 调度:
QueuedWork.java
@GuardedBy("sLock")
private static LinkedList<Runnable> sWork = new LinkedList<>();
// 提交使命
// shouldDelay:是否延迟
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
// 入队
sWork.add(work);
// 发送 Handler 音讯,触发 HandlerThread 履行使命
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY /* 100ms */);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
// 创立 HandlerThread 后台线程
HandlerThread handlerThread = new HandlerThread("queued-work-looper", Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
private static class QueuedWorkHandler extends Handler {
static final int MSG_RUN = 1;
QueuedWorkHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
// 履行使命
processPendingWork();
}
}
}
private static void processPendingWork() {
synchronized (sProcessingWork) {
LinkedList<Runnable> work;
synchronized (sLock) {
// 创立新的使命行列
// 这一步是有必要的,否则会与 enqueueDiskWrite 抵触
work = sWork;
sWork = new LinkedList<>();
// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
// 遍历 ,按顺序履行 sWork 使命行列
if (work.size() > 0) {
for (Runnable w : work) {
w.run();
}
}
}
}
比较不了解的是:
同一个文件的屡次写回串行化能够了解,关于多个文件的写回串行化意义是什么,是不是能够用多线程来写回多个不同的文件?或许这也是 SharedPreferences 是轻量级结构的原因之一,你觉得呢?
6.4 自动等候写回使命完毕
现在咱们能够答复 6.1 中遗留的问题:
在 apply() 办法中,在履行 enqueueDiskWrite() 前创立了 awaitCommit 使命并加入到 QueudWork 等候行列,直到磁盘写回完毕才将 awaitCommit 移除。这个 awaitCommit 使命是做什么的呢?
要了解这个问题需求管理分析到 ActivityThread 中的主线程音讯循环:
能够看到,在主线程的 Activity#onPause、Activity#onStop、Service#onStop、Service#onStartCommand 等生命周期状况改变时,会调用 QueudeWork.waitToFinish():
ActivityThread.java
@Override
public void handlePauseActivity(...) {
performPauseActivity(r, finished, reason, pendingActions);
// Make sure any pending writes are now committed.
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
...
}
private void handleStopService(IBinder token) {
...
QueuedWork.waitToFinish();
ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
...
}
waitToFinish() 会履行一切 sFinishers 等候行列中的 aWaitCommit 使命,自动等候一切磁盘写回使命完毕。在写回使命完毕之前,主线程会堵塞在等候锁上,这儿也有或许发生 ANR。
自动等候示意图
至于为什么 Google 要在 ActivityThread 中部分生命周期中自动等候一切磁盘写回使命完毕呢?官方并没有明确表示,结合头条和抖音技能团队的文章,我比较倾向于这 2 点解说:
- 解说 1 – 跨进程同步(首要): 为了保证跨进程的数据同步,要求在组件跳转前,保证其时组件的写回使命有必要在其时生命周期内完结;
- 解说 2 – 数据完整性: 为了避免在组件跳转的进程中或许发生的 Crash 造成未写回的数据丢掉,要求其时组件的写回使命有必要在其时生命周期内完结。
当然这两个解说并不全面,因为就算要求自动等候,也不能保证跨进程实时同步,也不能保证不发生 Crash。
抖音技能团队观念
QueuedWork.java
@GuardedBy("sLock")
private static Handler sHandler = null;
public static void waitToFinish() {
boolean hadMessages = false;
Handler handler = getHandler();
synchronized (sLock) {
if (handler.hasMessages(QueuedWorkHandler.MSG_RUN)) {
// Delayed work will be processed at processPendingWork() below
handler.removeMessages(QueuedWorkHandler.MSG_RUN);
}
// We should not delay any work as this might delay the finishers
sCanDelay = false;
}
// Android 8.0 优化:帮助子线程履行磁盘写回
// 作用有限,因为 QueuedWork 运用了 sProcessingWork 锁保证同一时刻最多只要一个线程在履行磁盘写回
// 所以这儿应该是尝试在主线程履行,能够提升线程优先级
processPendingWork();
// 履行 sFinshers 等候行列,等候一切写回使命完毕
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
// 履行 mcr.writtenToDiskLatch.await();
// 堵塞线程直到磁盘使命履行完毕
finisher.run();
}
} finally {
sCanDelay = true;
}
}
Android 7.1 QueuedWork 源码对比:
public static boolean hasPendingWork() {
return !sPendingWorkFinishers.isEmpty();
}
7. writeToFile() 姗姗来迟
终究走到详细调用磁盘 IO 操作的当地了!
7.1 写回进程
writeToFile() 的逻辑相对复杂一些了。通过简化后,剩余的中心进程只要 4 大进程:
-
进程 1:过滤无效写回业务:
- 1.1 业务的 memoryStateGeneration 内存版别小于 mDiskStateGeneration 磁盘版别,跳过;
- 1.2 同步写回有必要写回;
- 1.3 异步写回业务的 memoryStateGeneration 内存版别版别小于 mCurrentMemoryStateGeneration 最新内存版别,跳过。
-
进程 2:文件备份:
- 2.1 假如不存在备份文件,则将旧文件重命名为备份文件;
- 2.2 假如存在备份文件,则删去无效的旧文件(上一次写回出并且后处理没有成功删去的状况)。
-
进程 3:全量掩盖写回磁盘:
- 3.1 翻开文件输出流;
- 3.2 将 mapToWriteToDisk 映射表全量写出;
- 3.3 调用 FileUtils.sync() 强制操作系统页缓存写回磁盘;
- 3.4 写入成功,则删去被封文件(假如没有走到这一步,在将来读取文件时,会从头康复备份文件);
- 3.5 将磁盘版别记载为其时内存版别;
- 3.6 写回完毕(成功)。
-
进程 4:后处理: 删去写至半途的无效文件。
7.2 写回优化
持续分析发现,SharedPreference 的写回操作并不是简单的调用磁盘 IO,在保证 “可用性” 方面也做了一些优化规划:
- 优化 1 – 过滤无效的写回业务:
如前文所述,commit 和 apply 都或许呈现并发修正同一个文件的状况,此刻在连续修正同一个文件的业务序列中,旧的业务是没有意义的。为了过滤这些无意义的业务,在创立 MemoryCommitResult
业务目标时会记载其时的 memoryStateGeneration
内存版别,而在 writeToFile() 中就会依据这个字段过滤无效业务,避免了无效的 I/O 操作。
- 优化 2 – 备份旧文件:
因为写回文件的进程存在不确定的异常(比如内核溃散或许机器断电),为了保证文件的完整性,SharedPreferences 采用了文件备份机制。在履行写回操作之前,会先将旧文件重命名为 .bak
备份文件,在全量掩盖写入新文件后再删去备份文件。
假如写回文件失利,那么在后处理进程中会删去写至半途的无效文件。此刻磁盘中只要一个备份文件,而实在文件需求比及下次触发写回业务时再写回。
假如直到运用退出都没有触发下次写回,或许写回的进程中 Crash,那么在前文说到的创立 SharedPreferencesImpl 目标的结构办法中调用 loadFromDisk() 读取并解析文件数据时,会从备份文件康复数据。
- 优化 3 – 强制页缓存写回:
在写回文件成功后,SharedPreference 会调用 FileUtils.sync()
强制操作系统将页缓存写回磁盘。
写回示意图
SharedPreferencesImpl.java
// 内存版别
@GuardedBy("this")
private long mCurrentMemoryStateGeneration;
// 磁盘版别
@GuardedBy("mWritingToDiskLock")
private long mDiskStateGeneration;
// 写回业务
private static class MemoryCommitResult {
// 内存版别
final long memoryStateGeneration;
// 需求全量掩盖写回磁盘的数据
final Map<String, Object> mapToWriteToDisk;
// 同步计数器
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
// 后文写回完毕后调用
// wasWritten:是否有履行写回
// result:是否成功
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
// 唤醒等候锁
writtenToDiskLatch.countDown();
}
}
// 提交写回业务
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
...
// 创立磁盘写回使命
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
// 2.1 写入磁盘文件
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
// 2.2 mDiskWritesInFlight:进行中业务自减 1
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
// 2.3 触发写回成功回调
postWriteRunnable.run();
}
}
};
...
}
// 写回文件
// isFromSyncCommit:是否同步写回
@GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
boolean fileExists = mFile.exists();
// 假如旧文件存在
if (fileExists) {
// 1. 过滤无效写回业务
// 是否需求履行写回
boolean needsWrite = false;
// 1.1 磁盘版别小于内存版别,才有或许需求写回
// (只要旧文件存在才会走到这个分支,但是旧文件不存在的时候也或许存在无意义的写回,
// 猜想官方是希望首次创立文件的写回能够及时尽快履行,毕竟只要一个后台线程)
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
// 1.2 同步写回有必要写回
needsWrite = true;
} else {
// 1.3 异步写回需求判断业务目标的内存版别,只要最新的内存版别才有必要履行写回
synchronized (mLock) {
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
if (!needsWrite) {
// 1.4 无效的异步写回,直接完毕
mcr.setDiskWriteResult(false, true);
return;
}
// 2. 文件备份
boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) {
// 2.1 假如不存在备份文件,则将旧文件重命名为备份文件
if (!mFile.renameTo(mBackupFile)) {
// 备份失利
mcr.setDiskWriteResult(false, false);
return;
}
} else {
// 2.2 假如存在备份文件,则删去无效的旧文件(上一次写回出并且后处理没有成功删去的状况)
mFile.delete();
}
}
try {
// 3、全量掩盖写回磁盘
// 3.1 翻开文件输出流
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
// 翻开输出流失利
mcr.setDiskWriteResult(false, false);
return;
}
// 3.2 将 mapToWriteToDisk 映射表全量写出
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
// 3.3 FileUtils.sync:强制操作系统将页缓存写回磁盘
FileUtils.sync(str);
// 封闭输出流
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
// 3.4 写入成功,则删去被封文件(假如没有走到这一步,在将来读取文件时,会从头康复备份文件)
mBackupFile.delete();
// 3.5 将磁盘版别记载为其时内存版别
mDiskStateGeneration = mcr.memoryStateGeneration;
// 3.6 写回完毕(成功)
mcr.setDiskWriteResult(true, true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// 在 try 块中抛出异常,会走到这儿
// 4、后处理:删去写至半途的无效文件
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
// 写回完毕(失利)
mcr.setDiskWriteResult(false, false);
}
// -> 读取并解析文件数据
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
// 1、假如存在备份文件,则康复备份数据(后文详细分析)
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
...
}
至此,SharedPreferences 中心源码分析完毕。
8. SharedPreferences 的其他细节
SharedPreferences 还有其他细节值得学习。
8.1 SharedPreferences 锁总结
SharedPreferences 是线程安全的,但它的线程安全并不是直接运用一个大局的锁目标,而是采用多种颗粒度的锁目标完结 “锁细化” ,并且还贴心地运用了 @GuardedBy
注解符号字段或办法所述的锁等级。
运用 @GuardedBy 注解符号锁等级
@GuardedBy("mLock")
private Map<String, Object> mMap;
目标锁 | 功用呢 | 描述 |
---|---|---|
1、SharedPreferenceImpl#mLock | SharedPreferenceImpl 目标的大局锁 | 大局运用 |
2、EditorImpl#mEditorLock | EditorImpl 修正器的写锁 | 保证多线程拜访 Editor 的竞赛安全 |
3、SharedPreferenceImpl#mWritingToDiskLock | SharedPreferenceImpl#writeToFile() 的互斥锁 | writeToFile() 中会修正内存状况,需求保证多线程竞赛安全 |
4、QueuedWork.sLock | QueuedWork 的互斥锁 | 保证 sFinishers 和 sWork 的多线程资源竞赛安全 |
5、QueuedWork.sProcessingWork | QueuedWork#processPendingWork() 的互斥锁 | 保证同一时刻最多只要一个线程履行磁盘写回使命 |
8.2 运用 WeakHashMap 存储监听器
SharedPreference 提供了 OnSharedPreferenceChangeListener 回调监听器,能够在主线程监听键值对的改变(包含修正、新增和移除)。
SharedPreferencesImpl.java
@GuardedBy("mLock")
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
SharedPreferences.java
public interface SharedPreferences {
public interface OnSharedPreferenceChangeListener {
void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key);
}
}
比较意外的是: SharedPreference 运用了一个 WeakHashMap 弱键散列表存储监听器,并且将监听器目标作为 Key 目标。这是为什么呢?
这是一种避免内存走漏的考虑,因为 SharedPreferencesImpl 的生命周期是大局的(坐落 ContextImpl 的内存缓存),所以有必要运用弱引证避免内存走漏。想想也对,Java 规范库没有提供相似 WeakArrayList 或 WeakLinkedList 的容器,所以这儿将监听器目标作为 WeakHashMap 的 Key,就很巧妙的复用了 WeakHashMap 自动清理无效数据的能力。
提示: 关于 WeakHashMap 的详细分析,请阅览小彭说 数据结构与算法 专栏文章 《WeakHashMap 和 HashMap 的差异是什么,何时运用?》
8.3 怎么查看文件被其他进程修正?
在读取和写入文件后记载 mStatTimestamp 时刻戳和 mStatSize 文件巨细,在查看时查看这两个字段是否发生变化
SharedPreferencesImpl.java
// 文件时刻戳
@GuardedBy("mLock")
private StructTimespec mStatTimestamp;
// 文件巨细
@GuardedBy("mLock")
private long mStatSize;
// 读取文件
private void loadFromDisk() {
...
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
...
}
// 写入文件
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
...
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
...
}
// 查看文件
private boolean hasFileChangedUnexpectedly() {
synchronized (mLock) {
if (mDiskWritesInFlight > 0) {
// If we know we caused it, it's not unexpected.
if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
return false;
}
}
// 读取文件 Stat 信息
final StructStat stat = Os.stat(mFile.getPath());
synchronized (mLock) {
// 查看修正时刻和文件巨细
return !stat.st_mtim.equals(mStatTimestamp) || mStatSize != stat.st_size;
}
}
至此,SharedPreferences 全部源码分析完毕。
9. 总结
能够看到,虽然 SharedPreferences 是一个轻量级的 K-V 存储结构,但的确是一个完整的存储方案。从源码分析中,咱们能够看到 SharedPreferences 在读写功能、可用性方面都有做一些优化,例如:锁细化、业务化、业务过滤、文件备份等,值得细细品味。
鄙人篇文章里,咱们来盘点 SharedPreferences 中存在的 “缺陷”,为什么 SharedPreferences 没有乘上新时代的船只。请关注。
版权声明
本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
参考资料
- Android SharedPreferences 的了解与运用 —— ghroosk 著
- 一文读懂 SharedPreferences 的缺陷及一点点思考 —— 业志陈 著
- 反思|官方也无力回天?Android SharedPreferences 的规划与完结 —— 却把青梅嗅 著
- 分析 SharedPreference apply 引起的 ANR 问题 —— 字节跳动技能团队
- 今天头条 ANR 优化实践系列 – 告别 SharedPreference 等候 —— 字节跳动技能团队