SP几个关键方法。
1.getSharedPreferences
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name); //尝试从内存的mSharedPrefsPaths中获取xml文件
if (file == null) {
file = getSharedPreferencesPath(name); //如果不存在,则new一个file,保存在mSharedPrefsPaths里。
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
然后就是获取SharedPreferences对象,如果sSharedPrefsCache有缓存,直接返回;没有的话new一个SharedPreferencesImpl对象。
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
.....
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
...
return sp;
}
2.SharedPreferencesImpl构造方法
SharedPreferencesImpl(File file, int mode) {
...
startLoadFromDisk();
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk(); //单开线程,从磁盘读文件
}
}.start();
}
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
//如果是走调用链startReloadIfChangedUnexpectedly->startLoadFromDisk->loadFromDisk,会把备份文件还原成mFile,直接使用。
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
Map<String, Object> map = null;
StructStat stat = null;
Throwable thrown = 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);
}
}
} catch (ErrnoException e) {
// An errno exception means the stat failed. Treat as empty/non-existing by
// ignoring.
} catch (Throwable t) {
thrown = t;
}
synchronized (mLock) {
mLoaded = true; //读文件完成
mThrowable = thrown;
// It's important that we always signal waiters, even if we'll make
// them fail with an exception. The try-finally is pretty wide, but
// better safe than sorry.
try {
if (thrown == null) {
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
}
// In case of a thrown exception, we retain the old map. That allows
// any open editors to commit and store updates.
} catch (Throwable t) {
mThrowable = t;
} finally {
mLock.notifyAll(); //唤醒其他被阻塞的线程
}
}
}
可以看出,每个SharedPreferencesImpl对象,会去解析sp的xml文件,将k-v保存在内存里
private Map<String, Object> mMap;
3.getString等get方法。
会等待直到loadFromDisk方法执行完,将所有k-v独取出来。
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
4.Editor
Editor是个接口,实现类是内部类EditorImpl。
每次调用edit()方法都会new一个EditorImpl。所以尽量多次put一次提交,避免生成太多的editor对象。
@Override
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
5.putString
EditorImpl内部的mModified集合保存了所有要修改的k-v。
private final Map<String, Object> mModified = new HashMap<>();
@Override
public SharedPreferences.Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
6.commit
public boolean commit() {
MemoryCommitResult mcr = commitToMemory();
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* sync write on this thread okay */);
try {
mcr.writtenToDiskLatch.await(); //写入完成会唤醒
} catch (InterruptedException e) {
return false;
}
notifyListeners(mcr); //通知各种Listener
return mcr.writeToDiskResult;
}
7.apply
public void apply() {
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
} catch (InterruptedException ignored) {
}
}
};
QueuedWork.addFinisher(awaitCommit); //ANR元凶在这里,每个SP写入任务对应一个awaitCommit等待runnable
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit); //这一步是在writeToFile完成后执行,移除QueuedWork里等待runnable
}
};
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);
}
8.commitToMemory
从mModified集合获取修改的k-v,与原有数据集合并。
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<SharedPreferences.OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
// We optimistically don't make a deep copy until
// a memory commit comes in when we're already
// writing to disk.
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;
synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
mClear = false;
}
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
// "this" is the magic value for a removal mutation. In addition,
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) { //如果value是this或者null,会移除remove掉key
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v); //存在key,value不相等或者不存在key
}
changesMade = true;
if (hasListeners) {
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk); //构造一个需要commit的MemoryCommitResult对象
}
9.enqueueDiskWrite
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null); //是否同步,apply和commit的区别
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run(); //apply会走这一步,移除等待runnable
}
}
};
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) { //commit提交走到这里
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run(); //如果当前只有一个任务,直接执行,不需入队了
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit); //多个任务,需要入队等待执行
}
10. writeToFile写文件
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
...
boolean fileExists = mFile.exists();
// Rename the current file so it may be used as a backup during the next read
if (fileExists) {
boolean needsWrite = false;
// Only need to write if the disk state is older than this commit
// 只有磁盘上文件状态比当前文件更旧时,才执行更新操作
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
needsWrite = true;
} else {
synchronized (mLock) {
// No need to persist intermediate states. Just wait for the latest state to
// be persisted.
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) { //创建一个备份文件,如果写如失败,下一次startLoadFromDisk时候使用备份文件。
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
+ " to backup file " + mBackupFile);
mcr.setDiskWriteResult(false, false);
return;
}
} else {
mFile.delete();
}
}
// Attempt to write the file, delete the backup and return true as atomically as
// possible. If any exception occurs, delete the new file; next time we will restore
// from the backup.
try {
FileOutputStream str = createFileOutputStream(mFile);
if (str == null) {
mcr.setDiskWriteResult(false, false);
return;
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
writeTime = System.currentTimeMillis();
FileUtils.sync(str);
fsyncTime = System.currentTimeMillis();
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (mLock) {
mStatTimestamp = stat.st_mtim;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete(); //写入成功,删除备份文件
mDiskStateGeneration = mcr.memoryStateGeneration;
mcr.setDiskWriteResult(true, true);
long fsyncDuration = fsyncTime - writeTime;
mSyncTimes.add((int) fsyncDuration);
mNumSync++;
if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");
}
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// Clean up an unsuccessfully written file
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false, false);
}
11.几个注意点
1.apply是将修改提交到内存,再异步提交到磁盘文件,commit是同步的提交到磁盘文件;
2.多并发的提交commit时,需等待正在处理的commit数据更新到磁盘文件后才会继续往下执行,容易阻塞;
3.apply只是原子更新到内存,后调用apply函数会直接覆盖前面内存数据,从一定程度上提高很多效率;
3.每次edit都会new一个EditImpl对象,应该获取一次获取edit(),然后多次执行putxxx(), 避免内存抖动;
4.sp每次写文件都是全量更新,效率低下;
5.mBackupFile使用场景有二:
5.1 loadFromDisk方法如是是从reloadSharedPreferences调过来的,如果mBackupFile存在,直接改名使用
5.2 writeToFile写文件时保存mBackupFile文件,如果写入成功,删除备份文件;如果写入失败,mBackupFile文件保留。
6.每次有k-v变动,都会保存在内存中,不需要再次从磁盘读取:
if (mDiskWritesInFlight > 0) {
// We can't modify our mMap as a currently
// in-flight write owns it. Clone it before
// modifying it.
// noinspection unchecked
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap; //在mapToWriteToDisk(mapToWriteToDisk和mMap指向同一个地址)做增删查改
7.跨进程。android targetSdkVersion < 11(3.0以前)或者设置了MODE_MULTI_PROCESS的时候,在getSharedPreferences会reload一遍重新读取k-v,但是如果getSharedPreferences执行结束后,A进程修改了k-v,B进程是获取不到更新的。所以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();
}
12.SP缺点
1.getString等操作在loadFromDisk执行完前是阻塞的,会阻塞主线程;
2.修改k-v是全量更新,性能不好;
3.commit会阻塞主线程,apply不会阻塞当前调用线程,但会引起ANR;
4.读写文件涉及两次IO操作,性能不好;
5.解析sp的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿;
6.k-v会永远存在于内存之中,占用大量内存。
网友评论