分析commit()和apply()的区别,首先从源码入手,先看两个方法的源码
apply()
@Override
public void apply() {
final long startTime = System.currentTimeMillis();
// 首先将数据提交到内存中
final MemoryCommitResult mcr = commitToMemory();
// 写入磁盘的异步任务
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
if (DEBUG && mcr.wasWritten) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " applied after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
};
// 将任务添加到队列中
QueuedWork.addFinisher(awaitCommit);
// 异步执行写入磁盘的任务
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
// 清楚队列中的任务
QueuedWork.removeFinisher(awaitCommit);
}
};
// 排队写入磁盘中
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
notifyListeners(mcr);
}
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
// 这里才是真的执行异步的操作,前面的两个Runnable执行被执行了run()方法
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
// 异步写入
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
// 清楚队列的操作
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
commit()
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
MemoryCommitResult mcr = commitToMemory();
// 在将数据写入内存中后,同步排队写入磁盘中
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
// 在写入的时候,会导致当前线程等待,直到写入工作完成,即工作队列中的写入任务数为0
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
分析
从上述的源码可以看出,SharedPreferences.Editor的commit()和apply()两个方法,在提交到内存的过程中,都是一样的,都是采用同步提交的方式提交到内存,但是提交到磁盘中的过程却有不同,commit是同步提交到磁盘,而apply是异步提交到磁盘;并且apply()并没有返回值,提交失败的时候也不会提示任何失败。并且apply的提交,后续的提交会覆盖之前的提交到内存的内容,只有最后一次提交的内容会被写入磁盘,说明apply()是原子提交的
如果当一个apply()的异步提交还在进行的时候,执行commit()操作,那么commit()是会阻塞的。而如果commit()的时候,前面的commit()还未结束,这个commit()还是会阻塞的。(所以引起commit阻塞会有这两种原因)
apply()引起的主线程阻塞
使用了apply方式异步写sp的时候每次apply()调用都会形成一次提交,每次有系统消息发生的时候(handleStopActivity, handlePauseActivity)都会去检查已经提交的apply写操作是否完成,如果没有完成则阻塞主线程。因为在apply()中,将awaitCommit添加到了一个静态队列中,但是一般情况下这个静态队列中的任务是不会被执行的,但是在QueueWork的waitToFinish()方法被调用的时候,就会执行awaitCommit.run(),而在awaitCommit中会有调用wait()等待阻塞。
SharedPreferences实例的获取
并且同一个进程中,SharedPreferences是单例的,获取SharedPreferences是通过Context获取,看Context的实现类ContextImpl
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage()
&& !getSystemService(UserManager.class)
.isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
throw new IllegalStateException("SharedPreferences in credential encrypted "
+ "storage are not available until after user is unlocked");
}
}
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
if ((mode & Context.MODE_MULTI_PROCESS) != 0 ||
getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) {
// If somebody else (some other process) changed the prefs
// file behind our back, we reload it. This has been the
// historical (if undocumented) behavior.
sp.startReloadIfChangedUnexpectedly();
}
return sp;
}
SharedPreferences读取数据
如果采用apply()方法提交数据,因为是先将数据同步提交到内存中,然后再异步提交到磁盘中,那么这样就有可能在磁盘中的数据并不是最新的数据。
同进程读取
在同一个进程进行读取数据的时候,因为数据的读写都是针对内存来进行,在写入的时候,会先将数据保存在一个HashMap集合中,读取数据的时候,在同一个进程中是直接从这个HashMap中读取,SharedPreferences中读取数据是针对SharedPreferences类中的HashMap集合,这个集合中的数据是在apply()或者commit()调用提交到内存中的方法的时候,将数据保存到这个集合中
不同进程读取
如果是在不同进程中,SharedPreferences提交数据,因为每个进程的内存是单独的,这样就导致在A进程中写入到内存的数据,在B进程中的内存的数据依然是旧的,所以SharedPreferences在多进程中并不是同步的。
SharedPreferences想要做到进程同步,可以通过设置MODE_MULTI_PROCESS来实现,即SharedPreferences在每次写入的时候都会重新加载磁盘中的数据到内存中,以达到进程间的同步