先上图,由于是试用版软件,有水印了,实在抱歉了。
步骤说明
1、获取SharedPreferences对象
ContextImpl:
public SharedPreferences getSharedPreferences(String name, int mode) {
}
public SharedPreferences getSharedPreferences(File file, int mode) {
}
说明:
1、两个重载方法,第一个会调用第二个方法。
2、传入File的方法,可用于跨进程读写(首先此文件支持快进程读写权限),但一般现在这个模式已经不建议使用了,存在数据缺失问题,后面再提。
3、中间缓存有两个map说明下:
private ArrayMap<String, File> mSharedPrefsPaths;//通过name获取对应的文件对象
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;//通过包名获取对应应用的SharePreferencesImpl集合。再通过File获取SharePreferencesImpl
2、创建SharedPreferencesImpl对象中读取对应的xml文件
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false; //用于判断是否加载完成,处理多线程问题
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();}
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) { //用于判断是否加载完成,处理多线程问题
return;
}
if (mBackupFile.exists()) {
//多进程间创建不同SharedPrefrences对象,备份文件存在说明有进程正在写入数据,
//或者写入失败,导致备份文件未被删除,所以在此要讲失败的文件删除,
//将备份文件转为正式文件。
//所以说多进程间不安全,这是个原因。
mFile.delete();
mBackupFile.renameTo(mFile);
}
} ……
mLock.notifyAll();
}
说明:
1、mLoaded: //用于判断是否加载完成,同步锁处理多线程问题
2、判断是否有备份文件(name.xml.bak),若存在有两个原因,一是先前写入文件异常导致备份文件未删除,另一个原因是另一个进程正在写入文件,当前进程准备写入,由于进程不同,创建的对象肯定是不同的,但文件又是同一个,所以出现了这种把文件直接删除的问题,导致新写入的问题异常。(未实践验证)
3、将xml文件中的数据加载到内存map中。并唤醒所有等待mLock锁的代码块。
3、创建EditorImpl对象
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();//等待mLoaded=true,即等待加载数据完成
}
return new EditorImpl();
}
说明:
awaitLoadedLocked()就是轮询等待mLoaded为true,若不是则等待获取mLock锁,等加载完数据被唤醒。这里如果加载的xml文件特大,实践耗时较长,容易导致ANR。
4、给Editor添加数据
public final class EditorImpl implements Editor {
private final Map<String, Object> mModified = Maps.newHashMap();
public Editor putInt(String key, int value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor clear() {
synchronized (mLock) {
mClear = true;
return this;
}
}
}
说明:
1、添加数据也加了同步锁,防止多线程数据异常
2、clear方法可以清除掉先前所有老数据,配合commitToMemory()方法,在方法中会判断clear,若true则清除原有读取的map数据,使用新的mModified的数据。
5、处理添加进入的数据
private MemoryCommitResult commitToMemory() {
if (mClear) {
if (!mMap.isEmpty()) {
changesMade = true;
mMap.clear();//清除原来数据
}
mClear = false;
}
for (Map.Entry<String, Object> e : mModified.entrySet()) {
…… //判断是否有key,key对应的的value是否相同,不同则替换原来的map数据
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk);
//这个对象还是很有用的,类中setDiskWriteResult方法用于结束sp的写入,其中有个CountDownLatch,apply和commit结束都需要等待此闭锁。
说明:
1、mClear==true会清除原有数据
2、遍历新增加的数据,和原有数据对比,若无对应key或相应key对应的value不同,则覆盖添加。
3、MemoryCommitResult:这个对象还是很有用的,类中setDiskWriteResult方法用于结束sp的写入,其中有个CountDownLatch,apply和commit结束都需要等待此闭锁。
6、apply()异步提交数据
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
这中间用到了很多嵌套的runnable暂时没弄明白,可能和QueuedWork原理有关,后期再研究。
但是否异步的原因是取决于enqueueDiskWrite()的参数。
7、commit()同步提交数据
SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */);
直接调用enqueueDiskWrite(),并用闭锁直接等待,直到sp写入数据完成才往下走,所以是同步的,这个会返回写入的结果成功/失败。
8、enqueueDiskWrite处理同步异步提交数据方式
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);//同步提交还是异步
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();//同步就直接运行,并返回
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);//异步添加到队列中
}
说明:
1、使用参数postWriteRunnable==null来区分同步异步
2、若wasEmpty==true说明暂时还没有线程正在写文件,mDiskWritesInFlight这个数据是在步骤5中进行++的,若==1说明,只有现有的一个提交认数,所以直接执行。
3、若wasEmpty==false,即使是同步提交也需要加入到队列中,但不需要延迟,这个可以看QueuesWork原理。
9、写入文件
// Note: must hold mWritingToDiskLockprivate void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
if (fileExists) {
if (!needsWrite) {
//若不需要现在写,直接中止,这是apply异步执行的结果
mcr.setDiskWriteResult(false, true);
return;
}
boolean backupFileExists = mBackupFile.exists();
if (DEBUG) {
backupExistsTime = System.currentTimeMillis();
}
//备份文件不存在则将原来的文件重命名为备份文件,这个可以看下面的打点的截图。若存在,那说明先前写文件时出现了问题,所以将现有文件删除,这还是有个问题--多进程之间的问题,会导致正在另一个进程正在写入中断,或者数据混乱。
if (!backupFileExists) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
…………
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
……
//根据mode修改文件的权限
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
}
说明:
1、由于writeToFile()调用前有个写锁锁住,所以同一进程间不会有问题,但多进程间就会有问题。
2、文件已经存在的情况做出了处理,判断是否需要执行写入(needsWrite这个逻辑还是似懂非懂,后面再研究)
3、文件已经存在的情况做出了处理,判断备份文件是否存在,若不存在,则将现有文件重命名为备份文件
4、文件已经存在的情况做出了处理,判断备份文件是否存在,若存在,那说明先前写文件时出现了问题,所以将现有文件删除,这就有另外一个问题--多进程之间的问题,会导致正在另一个进程正在写的文件被删除,导致数据异常。
总结:
差不多就读到这了,后面再去看看MMVK的原理吧,看完SP,还是感觉性能确实比较差,多进程间的安全性也成问题。
建议:
1、建议使用私有模式,不要进行跨进程处理,非要跨进程可以通过进程通信的方式+sp,或者使用contentProvide。
2、建议一个xml文件不要存放太多东西,防止写入时间过长导致ANR。
3、优先考虑apply写入数据,只有对写入结果非常重要的情况下才用commit
4、apply和commit在添加完所有数据后,再调用,只执行一次,防止多此写文件。
\