在安卓开发中经常被用到,它是一个轻量级的存储类,通过key——value的形式用于保存一些配置参数。下面通过源码分析SharePreferences
首先介绍源码的分析过程中涉及到的一些类
Context
获取一个SharedPreferences
实例SharedPreferences
的类OnSharedPreferenceChangeListener
和Editor
SharedPreferences
接口里的方法,EditorImpl
实现了Editor
的方法从PreferenceManager
的方法getDefaultSharedPreferences
开始获取 SharedPreferences
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
getDefaultSharedPreferencesMode());
}
public static String getDefaultSharedPreferencesName(Context context) {
return context.getPackageName() + "_preferences";
}
private static int getDefaultSharedPreferencesMode() {
return Context.MODE_PRIVATE;
}
传递了一个packageName_preferences
和MODE_PRIVATE,调用ContextImpl
的getSharedPreferences
private ArrayMap<String, File> mSharedPrefsPaths;
public SharedPreferences getSharedPreferences(String name, int mode) {
if (mPackageInfo.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT) {
if (name == null) {
name = "null";
}
}
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
//根据传进来的文件名(用getDefaultSharedPreferences的话是[packageName]_preferences)查找文件
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
//创建一个文件
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
private File makeFilename(File base, String name) {
if (name.indexOf(File.separatorChar) < 0) {
final File res = new File(base, name);
// We report as filesystem access here to give us the best shot at
// detecting apps that will pass the path down to native code.
BlockGuard.getVmPolicy().onPathAccess(res.getPath());
return res;
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}
通过上一步传进来的name查找file, mSharedPrefsPaths是一个ArrayMap
类型,如果找不到file创建一个/data/data/
File文件
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
//通过包名获取 ArrayMap对象
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
//通过文件名获取ArrayMap对象里的对应的SharedPreferencesImpl
sp = cache.get(file);
if (sp == null) {
//检查mode,当目标版本>=N版本时,不在支持MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE
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");
}
}
// 缓存中没有时,构造一个SP对象
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
//多进程的情况下,会进行Check,如果有其他进程修改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();
}
return sp;
}
根据file获取SharedPreferences的实现类SharedPreferencesImpl
//以包名为 key ,以一个存储了 sp 文件及其 SharedPreferencesImp 对象的 ArrayMap 为 value
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
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;
}
通过包名获取 ArrayMap
SharedPreferencesImpl(File file, int mode) {
// sp 文件
mFile = file;
// 创建备份文件
mBackupFile = makeBackupFile(file);
mMode = mode;
// 标识 sp 文件是否已经加载到内存
mLoaded = false;
// 存储 sp 文件中的键值对
mMap = null;
mThrowable = null;
// 将SP文件加载到内存中
startLoadFromDisk();
}
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();
}
}.start();
}
在新的线程中加载
private void loadFromDisk() {
synchronized (mLock) {
//已经加载到内存了啥也不干
if (mLoaded) {
return;
}
// 如果BackupFile存在,实际读取的是备份文件
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);
//将XML以map的形式装载进内存
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
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 {
// 这个通知需要持有SharedPreferencesImpl.this执行
// 一般时所有的getXXX()
mLock.notifyAll();
}
}
}
读取SP文件放入内存mMap
中,异步执行,线程安全读取过程中持有锁 mLock
。整个获取SharedPreferences
到这里就结束了,之后会调用getXXX()方法获取数据。
读取过程很简单,getXXX方法有很多支持String,Set,int,long,float,boolean,还有全部读取getAll。这里以getString为例
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// 此处等待加载完毕,否则一直阻塞
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
等待已经加载到内存mLoaded的锁,前面说过loadFromDisk加载完后会唤醒mLock.notifyAll();
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) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
执行,putXXX之前需要先调用edit()获取EditorImpl
对象。Editor
和 SharedPreferences
一样,都是接口,它们的实现类分别是 EditorImpl
和 SharedPreferencesImpl
。然后才能
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
注意:每次edit()都会实例化一个新的EditorImpl
对象,所以多次putXXX时只需要一次edit就够了,避免浪费内存
//暂存用户设置的数据,待commit,或者apply()时进行写入
private final Map<String, Object> mModified = new HashMap<>();
private boolean mClear = false;
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
@Override
public Editor remove(String key) {
synchronized (mEditorLock) {
//value 为this,下面分析
mModified.put(key, this);
return this;
}
}
@Override
public Editor clear() {
synchronized (mEditorLock) {
// 写入一个clear标志,在提交时会进行chec
mClear = true;
return this;
}
}
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
//将上一步mModified同步到内存
MemoryCommitResult mcr = commitToMemory();
//写入到SP文件
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");
}
}
//通知监听者,回调 OnSharedPreferenceChangeListener
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
写入到内存
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
List<String> keysModified = null;
Set<OnSharedPreferenceChangeListener> listeners = null;
Map<String, Object> mapToWriteToDisk;
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
// mDiskWritesInFlight这个变量用来表示还有多少待写入Disk的请求
// 我们使用commitToMemory()修改一个值,这个变量会加1
mDiskWritesInFlight++;
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {
boolean changesMade = false;
//处理clear
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) {
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);
}
changesMade = true;
if (hasListeners) {
// 将所有需要回调Listener的字段加入ArrayList
keysModified.add(k);
}
}
// 清空暂存缓存
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
mapToWriteToDisk);
}
mapToWriteToDisk
对原来的mMap
和putXXX完后mModified
做了一个合并。然后下一步enqueueDiskWrite
会将mapToWriteToDisk
写入到SP文件中去
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) {
//写入到Sp文件
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
//每次写入文件成功,会将mDiskWritesInFlight这个变量-1
mDiskWritesInFlight--;
}
// 如果在写入文件完毕后有回调,在这里执行
// commit()时这个变量会为null
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// commit() 直接在当前线程进行写入操作 .
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
//apply() 方法执行此处,由 QueuedWork.QueuedWorkHandler 处理
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
commit()
方法中调用 enqueueDiskWrite()
方法传入postWriteRunnable为null:
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
所以commit()
写文件操作enqueueDiskWrite是直接在当前调用线程执行的,你在主线程调用该方法,就会直接在主线程进行 IO 操作。这样的话可能会造成卡顿或者ANR。一起来看看最终的写入到SP文件方法 writeToFile(mcr, isFromSyncCommit);
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;
}
//将mapToWriteToDisk写入到mFile中
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, "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);
}
代码看着很长,其实核心代码没几句,原理和获取SharePreferences
时的loadFromDisk
方法相反。
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);
notifyListeners(mcr);
}
和commit一样,先commitToMemory
,再enqueueDiskWrite
,这里传入postWriteRunnable
。前面在 enqueueDiskWrite
方法中讲过apply会走QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit)
public static void queue(Runnable work, boolean shouldDelay) {
Handler handler = getHandler();
synchronized (sLock) {
sWork.add(work);
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
private static Handler getHandler() {
synchronized (sLock) {
if (sHandler == null) {
HandlerThread handlerThread = new HandlerThread("queued-work-looper",
Process.THREAD_PRIORITY_FOREGROUND);
handlerThread.start();
sHandler = new QueuedWorkHandler(handlerThread.getLooper());
}
return sHandler;
}
}
通过QueuedWork
执行异步任务,当我们的Activity执行onPause()的时候,也就是ActivityThread类执行handleStopActivity方法是,看看它干了啥 它会执行 QueuedWork.waitToFinish()方法,而waitToFinish方法中有个while循环,如果我们还有没有完成的异步落盘操作时,它会调用到我们在apply方法中创建的awaitCommit,让我们主线程处于等待状态,直到所有的落盘操作完成,才会跳出循环,这也就是apply造成anr的元凶。
@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()) {
// 可能因等待写入造成卡顿甚至 ANR
QueuedWork.waitToFinish();
}
//...省略
}
public static void waitToFinish() {
...省略
try {
while (true) {
Runnable finisher;
synchronized (sLock) {
finisher = sFinishers.poll();
}
if (finisher == null) {
break;
}
finisher.run();
}
} finally {
sCanDelay = true;
}
...省略
}
阅读源码的过程中发现一些小问题,平时开发需要注意:
SharedPreferences
的时候(getSharedPreferences
)会将初始化SP文件并将SP文件加载到内存,可能导致后续 getXXX()
方法阻塞,提前异步初始化 SharedPreferences
,也不要存放大数据在SP文件中mapToWriteToDisk
全部写入,所以说如果针对高频改变的配置项我们应该存储在单独的Sp文件中,提高性能EditorImpl
对象,所以应该多次putXXX
的情况下一次edit就够了MODE_MULTI_PROCESS
也没用。跨进程频繁读写可能导致数据损坏或丢失。