Android SharePreferences持久化机制
Posted Nipuream
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android SharePreferences持久化机制相关的知识,希望对你有一定的参考价值。
android SharePreferences持久化机制
SharePreferences作为Android开发中比较常见的持久化工具类,很多人平时工作中没有对它起到足够的重视,包括我。但是互联网公司面试好像很喜欢考察这个知识点,感觉有必要对它进行一个梳理和总结。
一、加载 getSharedPreferences
@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)
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");
//1. 如果没有缓存则会创建sp的实现类
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
//2. 多进程的情况下
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;
//如果有修改意图(pendIntent),则更新失败
void startReloadIfChangedUnexpectedly()
synchronized (mLock)
// TODO: wait for any pending writes to disk?
if (!hasFileChangedUnexpectedly())
return;
startLoadFromDisk();
会有缓存类对sp实现类对象进行缓存,如果没有则新建;多进程情况下会判断此文件是否有修改的任务类,如果有修改的意图(pendIntent)则会打开失败(可以先简单这么理解)。下面看看sp的初始化工作:
SharedPreferencesImpl(File file, int mode)
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
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;
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<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;
//... 加载失败的处理
这里会创建个 backup 文件,主要后续操作失败了不影响原来的文件,操作成功则将原来的文件删除掉,将备份文件修改为目标文件。
二、commit apply的区别
@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
//等待执行完毕
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;
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable)
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable()
@Override
public void run()
synchronized (mWritingToDiskLock)
writeToFile(mcr, isFromSyncCommit);
synchronized (mLock)
mDiskWritesInFlight--;
if (postWriteRunnable != null)
postWriteRunnable.run();
;
//commit的话,则还是在当前线程执行,不会传递给QueueWork的。
if (isFromSyncCommit)
boolean wasEmpty = false;
synchronized (mLock)
wasEmpty = mDiskWritesInFlight == 1;
if (wasEmpty)
writeToDiskRunnable.run();
return;
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
commit的操作其实还在在当前线程执行完成持久化操作(写入文件后续再说)。
@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);
// 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操作其实是将持久化操作交给QueueWork的线程去处理的,并将等待的操作放到finish的队列中。这样做的好处是不会阻塞主线程,但是呢,也会存在一定的问题,这也是面试中喜欢问的地方。
@Override
public void handleStopActivity(IBinder token, boolean show, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason)
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
reason);
if (localLOGV) Slog.v(
TAG, "Finishing stop of " + r + ": show=" + show
+ " win=" + r.window);
updateVisibility(r, show);
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb())
QueuedWork.waitToFinish();
stopInfo.setActivity(r);
stopInfo.setState(r.state);
stopInfo.setPersistentState(r.persistentState);
pendingActions.setStopInfo(stopInfo);
mSomeActivitiesChanged = true;
问题就出在这,当快速打开关闭一个Activity的时候,假设sp中存的内容比较多,在执行stop的生命周期方法的时候,会等待这些apply的任务完成,这时候很容易导致Anr的产生。解决的办法就是在stop之前将QueueWork中finish队列中的消息全都移除掉。
三、写入文件操作
![sp](d:\\Users\\Administrator\\Desktop\\Android学习总结\\Android基础\\sp.png)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;
//这里会检查文件修改时间和内存修改时间点(apply,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();
//和前文所说一致,会先修改备份文件,如果修改成功,则更新原文件,若更新失败,删除
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_mtim;
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((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, 以上是关于Android SharePreferences持久化机制的主要内容,如果未能解决你的问题,请参考以下文章
Android - SharePreferences数据存储
Android - SharePreferences数据存储的简单使用
Android应用Preference相关及源代码浅析(SharePreferences篇)