细推物理须行乐,何用浮名绊此身java
SharedPreferences(简称SP)是Android轻量级的键值对存储方式。对于开发者来讲它的使用很是的方便,可是也是一种被你们诟病不少的一种存储方式。有所谓的七宗罪:android
带着这些结论咱们一步步的从代码中找出它的依据,固然了,本文的内容不止如此,还包裹整个SharedPreferences的运行机理等,固然这一切都是我我的的理解,中间难免有错误的地方,也欢迎你们指证。c++
SharedPreferences的建立能够有多种方式:git
# ContextWrapper.java
public SharedPreferences getSharedPreferences(String name, int mode) {
return mBase.getSharedPreferences(name, mode);
}
复制代码
由于咱们的Activity,Service,Application都会继承ContextWrapper,因此它们也能够获取到SharedPreferencesgithub
# PreferenceManager.java
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
复制代码
经过PreferenceManager中静态方法获取,固然根据需求不通,PreferenceManager中还提供了别的方法,你们能够去查阅。缓存
虽然上面获取SharedPreferences的方式不少,可是他们最终都会调用到ContextImpl.getSharedPreferences的方法,而且 SharedPreferences真正的建立也是在这里,关于ContextImpl和Activity、Service等的关系,我会另外写篇文章介绍,其实使用的是装饰器模式.安全
# ContextImpl.java
public SharedPreferences getSharedPreferences(String name, int mode) {
// At least one application in the world actually passes in a null
// name. This happened to work because when we generated the file name
// we would stringify it to "null.xml". Nice.
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//从mSharedPrefsPaths缓存中查询文件
file = mSharedPrefsPaths.get(name);
if (file == null) {
//若是文件不存在,根据name建立 [见2.3.2]
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
//[见2.3.3]
return getSharedPreferences(file, mode);
}
复制代码
# ContextImpl.java
@Override
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
//建立目录/data/data/package name/shared_prefs/
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
复制代码
# ContextImpl.java
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
//[见2.3.4]
checkMode(mode);
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.O) {
if (isCredentialProtectedStorage()
&& !getSystemService(StorageManager.class).isUserKeyUnlocked(
UserHandle.myUserId())
&& !isBuggy()) {
throw new IllegalStateException("SharedPreferences in credential encrypted "
+ "storage are not available until after user is unlocked");
}
}
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
 //获取SharedPreferencesImpl的缓存集合[见2.3.5]
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {
//若是缓存中没有咱们就会建立SharedPreferencesImpl实例[见2.3.6]
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();[见2.3.7]
}
return sp;
}
复制代码
上面的代码有个MODE_MULTI_PROCESS模式,也就是咱们若是要在多进程时使用SharedPreferences时须要指定这个mode,可是这种方式google是不推荐使用的,由于在线上大概有万分之一的几率形成 SharedPreferences的数据所有丢失,由于它没有使用任何进程锁的操做,这时从新加载可一次文件,具体见startReloadIfChangedUnexpectedly方法。bash
# ContextImpl.java
private void checkMode(int mode) {
if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.N) {
if ((mode & MODE_WORLD_READABLE) != 0) {
throw new SecurityException("MODE_WORLD_READABLE no longer supported");
}
if ((mode & MODE_WORLD_WRITEABLE) != 0) {
throw new SecurityException("MODE_WORLD_WRITEABLE no longer supported");
}
}
}
复制代码
在Android24以后的版本 SharedPreferences的mode不能再使用MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE。并发
# ContextImpl.java
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
复制代码
经过上面的代码,咱们发现ContextImpl中维护了一个存储SharedPreferencesImpl map的map缓存 sSharedPrefsCache,而且他是静态的,也就是说整个应用独此一份,而它的键是应用的包名。app
前面讲了那么一大堆大可能是关于SharedPreferences的各类缓存流程的,以及各类前期的准备,走到这里才真正把SharedPreferences建立出来,因为SharedPreferences是个接口,因此它的所有实现都是由 SharedPreferencesImpl来完成的。
# SharedPreferencesImpl.java
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
//[见2.3.8]
startLoadFromDisk();
}
复制代码
当设置MODE_MULTI_PROCESS模式, 则每次getSharedPreferences过程, 会检查SP文件上次修改时间和文件大小, 一旦全部修改则会从新加载文件.
# SharedPreferencesImpl.java
void startReloadIfChangedUnexpectedly() {
synchronized (mLock) {
// TODO: wait for any pending writes to disk?
if (!hasFileChangedUnexpectedly()) {
return;
}
startLoadFromDisk();
}
}
// Has the file changed out from under us? i.e. writes that
// we didn't instigate. 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;
}
}
final StructStat stat;
try {
/*
* Metadata operations don't usually count as a block guard * violation, but we explicitly want this one. */ BlockGuard.getThreadPolicy().onReadFromDisk(); stat = Os.stat(mFile.getPath()); } catch (ErrnoException e) { return true; } synchronized (mLock) { return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size; } } 复制代码
这个方法的主要目的就是加载xml文件到mFile对象中,同时为了保证这个加载过程为异步操做,这个地方使用了线程。另外当xml文件未加载时,SharedPreferences的getString(),edit()等方法都会处于阻塞状态(阻塞和挂起的区别...),直到mLoaded的状态变为true,后面的分析会验证这一点。
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
//使用线程去加载xml
loadFromDisk();
}
}.start();
}
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
//若是容灾文件存在,则使用容灾文件
if (mBackupFile.exists()) {
mFile.delete();
mBackupFile.renameTo(mFile);
}
}
// Debugging
if (mFile.exists() && !mFile.canRead()) {
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
Map map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
if (mFile.canRead()) {
BufferedInputStream str = null;
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
//从xml中全量读取内容,保存在内存中
map = XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
IoUtils.closeQuietly(str);
}
}
} catch (ErrnoException e) {
/* ignore */
}
synchronized (mLock) {
mLoaded = true;
if (map != null) {
mMap = map;
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
} else {
mMap = new HashMap<>();
}
mLock.notifyAll();
}
}
复制代码
这样SharedPreference的实例建立已经完成了,而且咱们也发现SharedPreference将从文件中读取的数据保存在了mMap的全局变量中,而后后面的读取操做其实都只是在mMap中拿数据了,下面分析获取数据和添加数据的流程。
前面的章节已经成功的建立了SharedPreferences实例,下面看看怎么使用它来获取数据,下面以getString为例分析。
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
//阻塞判断,须要等到数据从xml中加载到内存中,才会继续执行[见3.2]
awaitLoadedLocked();
//直接从内存中获取数据
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
复制代码
从这里咱们能够验证当咱们在上文中的结论,那就是在 SharedPreferences被建立后,咱们全部的读取数据都是在内存中获取的,可是这里可能就有个疑问了,加入如今咱们put一条数据,是否要从新加载一次文件呢,其实在单进程中是不须要的,可是在多进程中就可能须要了。下面咱们继续带着这些疑惑去寻找答案。
private void awaitLoadedLocked() {
if (!mLoaded) {
// Raise an explicit StrictMode onReadFromDisk for this
// thread, since the real read will be in a different
// thread and otherwise ignored by StrictMode.
//[见参考文档]
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
}
从上面的操做能够看出当mLoaded为false时,也就是内容没有从xml文件中加载到内存时,该方法一直会处于阻塞状态。
复制代码
SharedPreferences中还有个Editor和EditorImpl,它们的做用是添加数据和修改数据。可是这里要注意,咱们对Editor作操做,其实只是把数据保存在Editor的一个成员变量中,真正把数据更新到SharedPreferencesImpl而且写入文件是在Editor的commit或者apply方法被调用以后.
# SharedPreferencesImpl.java
public final class EditorImpl implements Editor {
private final Object mLock = new Object();
@GuardedBy("mLock")
private final Map<String, Object> mModified = Maps.newHashMap();
@GuardedBy("mLock")
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putStringSet(String key, @Nullable Set<String> values) {
synchronized (mLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
public Editor putInt(String key, int value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putLong(String key, long value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putFloat(String key, float value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor putBoolean(String key, boolean value) {
synchronized (mLock) {
mModified.put(key, value);
return this;
}
}
public Editor remove(String key) {
synchronized (mLock) {
mModified.put(key, this);
return this;
}
}
public Editor clear() {
synchronized (mLock) {
mClear = true;
return this;
}
}
}
复制代码
从Editor的put操做来看,它是把数据添加到mModified这个成员变量中,并未写入文件。而写入的操做是在commit和apply中执行的,下面就解析 SharedPreferences中两个核心的方法commit和apply
commit和apply是Editor中的方法,实如今EditorImpl中,那么他们两有什么区别,又是怎么实现的呢?首先,他们两最大的区别是commit是一个同步方法,它有一个boolean类型的返回值,而apply是一个异步方法,没有返回值。简单理解就是,commit须要等待提交结果,而apply不须要。因此commit以牺牲必定的性能而换来准确性的提升。另一点就是对于apply方法,官方的注释告诉咱们不用担忧Android组件的生命周期会对它形成的影响,底层的框架帮咱们作了处理,可是真的是这样的吗?[见4.2.6]分解。下面看具体的分析。
# SharedPreferencesImpl.java
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//将数据保存在内存中[见4.2.3]
MemoryCommitResult mcr = commitToMemory();
//同步将数据写到硬盘中[见4.2.4]
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
//等待写入操做的完成
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
//用于onSharedPreferenceChanged的回调提醒
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
复制代码
在commit中首先调用commitToMemory将数据保存在内存中,而后会执行写入操做,而且让当前commit所在的线程处于阻塞状态。当写入完成后会经过onSharedPreferenceChanged提醒数据发生的变化。这个过程当中有个注意的地方, mcr.writtenToDiskLatch.await(),若是非并发调用commit方法,这个操做是不须要的,可是若是并发commit时,就必须有mcr.writtenToDiskLatch.await()操做了,由于写入操做可能会被放到别的子线程中执行.而后就是notifyListeners()方法,当咱们写入的数据发生变化后给咱们的回调,这个回调咱们能够经过注册下面的代码拿到。
sp.registerOnSharedPreferenceChangeListener { sharedPreferences, key -> }
复制代码
# SharedPreferencesImpl.java
public void apply() {
final long startTime = System.currentTimeMillis();
//将数据保存在内存中[见4.2.3]
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
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() {
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
 // 执行文件写入操做,传入的 postWriteRunnable 参数不为 null,因此在
// enqueueDiskWrite 方法中会开启子线程异步将数据写入文件
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的写入操做会被放在一个单独的线程中执行,而且不会阻塞当前apply所在的线程。当时有中特殊的请求是会阻塞的,那就是在Activity的onStop方法被调用,而且apply的写入操做还未完成时,会阻塞主线程,更详情的分析[见4.2.6]
# SharedPreferencesImpl.java
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<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++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList<String>(); listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (mLock) { boolean changesMade = false; if (mClear) { if (!mMap.isEmpty()) { changesMade = true; mMap.clear(); } mClear = false; } //mModified 保存的写记录同步到内存中的 mMap 中 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) { if (!mMap.containsKey(k)) { continue; } mMap.remove(k); } else { if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mMap.put(k, v); } changesMade = true; if (hasListeners) { keysModified.add(k); } } // 将 mModified 同步到 mMap 以后,清空 mModified mModified.clear(); if (changesMade) { mCurrentMemoryStateGeneration++; } memoryStateGeneration = mCurrentMemoryStateGeneration; } } return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners, mapToWriteToDisk); } 复制代码
经过上面的注释和代码,咱们了解到每次有写操做的时候,都会同步mMap,这样咱们就不须要每次在读取的时候从新load文件了,可是这个结论在多进程中不适用。另外须要关注的是mDiskWritesInFlight这个变量,当mDiskWritesInFlight大于0时,会拷贝一份mMap,把它存到MemoryCommitResult类的成员mapToWriteToDisk中,而后再把mDiskWritesInFlight加1。在把mapToWriteDisk写入到文件后,mDiskWritesInFlight会减1,因此mDiskWritesInFlight大于0说明以前已经有调用过commitToMemory了,而且尚未把map写入到文件,这样先后两次要准备写入文件的mapToWriteToDisk是两个不一样的内存对象,后一次调用commitToMemory时,再更新mMap中的值时不会影响前一次的mapToWriteToDisk的写入文件
# SharedPreferencesImpl.java
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
final boolean isFromSyncCommit = (postWriteRunnable == null);
// 建立Runnable,负责将数据接入文件
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
//写入文件操做[见4.2.5]
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
// 写入文件后将mDiskWritesInFlight值减一
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) {
// 当只有一个 commit 请求未处理,那么无需开启线程进行处理,直接在本线程执行 //writeToDiskRunnable 便可
writeToDiskRunnable.run();
return;
}
}
//单线程执行写入操做
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
复制代码
从这里咱们能够得出commit操做,若是只有一次操做的时候,只会在当前线程中执行,可是若是并发commit时,剩余的writeToDiskRunnable则会被放在单独的线程中执行,而第一次commit所在的线程则进入阻塞状态。它须要等后面的commit都成功后才能算真正的成功,而返回的状态也是最后一次commit的状态。
终于,迎来了最后真正的写操做,包括在写入成功的时候将容灾文件删除,或者在写入失败时将半成品文件删除等,最后将写结果保存在MemoryCommitResult中。
# SharedPreferencesImpl.java
// Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
long startTime = 0;
long existsTime = 0;
long backupExistsTime = 0;
long outputStreamCreateTime = 0;
long writeTime = 0;
long fsyncTime = 0;
long setPermTime = 0;
long fstatTime = 0;
long deleteTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
boolean fileExists = mFile.exists();
if (DEBUG) {
existsTime = System.currentTimeMillis();
// Might not be set, hence init them to a default value
backupExistsTime = existsTime;
}
// 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 (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();
}
}
// 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 (DEBUG) {
outputStreamCreateTime = System.currentTimeMillis();
}
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);
if (DEBUG) {
setPermTime = System.currentTimeMillis();
}
try {
final StructStat stat = Os.stat(mFile.getPath());
synchronized (mLock) {
mStatTimestamp = stat.st_mtime;
mStatSize = stat.st_size;
}
} catch (ErrnoException e) {
// Do nothing
}
if (DEBUG) {
fstatTime = System.currentTimeMillis();
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
if (DEBUG) {
deleteTime = System.currentTimeMillis();
}
mDiskStateGeneration = mcr.memoryStateGeneration;
mcr.setDiskWriteResult(true, true);
if (DEBUG) {
Log.d(TAG, "write: " + (existsTime - startTime) + "/"
+ (backupExistsTime - startTime) + "/"
+ (outputStreamCreateTime - startTime) + "/"
+ (writeTime - startTime) + "/"
+ (fsyncTime - startTime) + "/"
+ (setPermTime - startTime) + "/"
+ (fstatTime - startTime) + "/"
+ (deleteTime - startTime));
}
long fsyncDuration = fsyncTime - writeTime;
mSyncTimes.add(Long.valueOf(fsyncDuration).intValue());
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);
}
复制代码
还记得在介绍apply时,咱们了解到apply是异步的,不会阻塞咱们的主线程,官方的注释页说过android组件的生命周期不会对aplly的异步写入形成影响,告诉咱们不用担忧,但它却会有必定的概率引发anr,好比有一种状况,当咱们的Activity执行onPause()的时候,也就是ActivityThread类执行handleStopActivity方法是,看看它干了啥 它会执行 QueuedWork.waitToFinish()方法,而waitToFinish方法中有个while循环,若是咱们还有没有完成的异步落盘操做时,它会调用到咱们在apply方法中建立的awaitCommit,让咱们主线程处于等待状态,直到全部的落盘操做完成,才会跳出循环,这也就是apply形成anr的元凶。
# ActivityThread.java
@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
//...省略
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
//...省略
}
复制代码
/**
* Trigger queued work to be processed immediately. The queued work is processed on a separate
* thread asynchronous. While doing that run and process all finishers on this thread. The
* finishers can be implemented in a way to check weather the queued work is finished.
*
* Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
* after Service command handling, etc. (so async work is never lost)
*/
public static void waitToFinish() {
...省略
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
[见4.2.2中的awaitCommit]
finisher.run();
}
} finally {
sCanDelay = true;
}
...省略
}
复制代码
SharedPreferences是一种轻量级的存储方式,使用方便,可是也有它适用的场景。要优雅滴使用sp,要注意如下几点: