5,提交
5.1 commit
commit方法的逻辑如下,
1, 将数据更新到内存
MemoryCommitResult mcr = commitToMemory();
2, 将内存数据同步到文件
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
3, 进入等待状态, 直到写入文件的操作完成
mcr.writtenToDiskLatch.await();
4,通知并返回写入结果,
notifyListeners(mcr);
return mcr.writeToDiskResult;
1, commitToMemory
内部类EditorImpl 的commitToMemory的主要逻辑如下,
1,当mClear 值为true,则直接清空外部类mMap 中的所有值。
if (mClear) {
if (!mMap.isEmpty()) {
mcr.changesMade = true;
mMap.clear();
}
mClear = false;
}
2,逐个遍历mModified中的值,如果值为空或者内部类EditorImpl,则删除该数据,
if (v == this || v == null) {
if (!mMap.containsKey(k)) {
continue;
}
mMap.remove(k);
}
3, 当value值发生改变, 则会更新到mMap
if (mMap.containsKey(k)) {
Object existingValue = mMap.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mMap.put(k, v);
4,最后清空mModified变量,
mModified.clear();
2, enqueueDiskWrite
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
//执行文件写入操作[见小节4.3.1]
writeToFile(mcr);
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
}
//此时postWriteRunnable为null不执行该方法
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
final boolean isFromSyncCommit = (postWriteRunnable == null);
if (isFromSyncCommit) { //commit方法会进入该分支
boolean wasEmpty = false;
synchronized (SharedPreferencesImpl.this) {
//commitToMemory过程会加1,则wasEmpty=true
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
//跳转到上面
writeToDiskRunnable.run();
return;
}
}
//不执行该方法
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
新建一个子线程调用writeToFile方法保存数据,该方法主要逻辑如下,
1,将mMap数据写入xml文件,
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (this) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
2,如果写入成功,则删除备份文件,唤醒等待线程,
mBackupFile.delete();
mcr.setDiskWriteResult(true);
3,如果写入失败, 删除未成功写入的文件
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false);
可见, 每次commit是把全部数据更新到文件, 所以每个文件的数据量必须保证足够精简尽量不要一次写入大量的数据。
5.2 apply
apply方法的逻辑如下,
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.add(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
public void run() {
awaitCommit.run();
QueuedWork.remove(awaitCommit);
}
};
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// Okay to notify the listeners before it's hit disk
// because the listeners should always get the same
// SharedPreferences instance back, which has the
// changes reflected in memory.
notifyListeners(mcr);
可见, apply跟commit的最大区别 在于apply的写入文件操作是在单线程的线程池来完成.
1,apply方法开始的时候, 会把awaitCommit放入QueuedWork;
2,文件写入操作完成, 则会把相应的awaitCommit从QueuedWork中移除.
QueuedWork在这里存在的价值主要是用于在Stop Service, finish BroadcastReceiver过程用于 判定是否处理完所有的异步SP操作.
6,总结
apply 与commit的对比
apply没有返回值, commit有返回值能知道修改是否提交成功
apply是将修改提交到内存,再异步提交到磁盘文件; commit是同步的提交到磁盘文件;
多并发的提交commit时,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,从而降低效率;
而apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率。
获取SP与Editor:
getSharedPreferences()是从ContextImpl.sSharedPrefsCache唯一的SPI对象;
edit()每次都是创建新的EditorImpl对象.
优化建议:
强烈建议不要在sp里面存储特别大的key/value, 有助于减少卡顿/anr
请不要高频地使用apply, 尽可能地批量提交;commit直接在主线程操作, 更要注意了
不要使用MODE_MULTI_PROCESS;
高频写操作的key与高频读操作的key可以适当地拆分文件, 由于减少同步锁竞争;
不要一上来就执行getSharedPreferences().edit(), 应该分成两大步骤来做, 中间可以执行其他代码.
不要连续多次edit(), 应该获取一次获取edit(),然后多次执行putxxx(), 减少内存波动; 经常看到大家喜欢封装方法, 结果就导致这种情况的出现.
每次commit时会把全部的数据更新的文件, 所以整个文件是不应该过大的, 影响整体性能;