Android SharedPreferences源码分析
SharedPreferences源码分析
官方提供的保存键值对的方式,
支持存储类型
- null
- String
- 基本数据类型
- 基本数据类型或String数组
- list
- map
- set
类型源码:XmlUtils#readThisValueXml
文件实例
用法
// 读
context.getSharedPreferences("test", Context.MODE_PRIVATE).getString("test", "defValue");
// 写
context.getSharedPreferences("test", Context.MODE_PRIVATE).edit().putString("key", "value").commit();
context.getSharedPreferences("test", Context.MODE_PRIVATE).edit().putString("key", "value").apply();
Context
CountDownLatch用法示例
// chatgpt生成
import java.util.concurrent.CountDownLatch;
public class MyThread extends Thread {
private CountDownLatch latch;
public MyThread(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
// 执行一些操作
// 操作完成后,调用countDown()方法减少latch的计数器
latch.countDown();
}
}
public class MainActivity extends AppCompatActivity {
private CountDownLatch latch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int threadCount = 3; // 设置线程数量
// 创建CountDownLatch,设置计数器为线程数量
latch = new CountDownLatch(threadCount);
// 启动线程
for (int i = 0; i < threadCount; i++) {
MyThread thread = new MyThread(latch);
thread.start();
}
try {
// 调用await()方法阻塞主线程,直到计数器变为0
latch.await();
// 所有线程操作完成后,继续执行主线程的代码
// ...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
getSharedPrefrences
从上面Context的结构可以看出,Context.getSharedPreferences都会调用到ContextImpl.getSharedPreferences,也就是:
sSharedPrefsCache到mSharedPrefsPaths
@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) {
// 检查mode,不允许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 = 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();
}
return sp;
}
@GuardedBy("ContextImpl.class")
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
final String packageName = getPackageName();
// 以包名作为key从静态变量map中尝试取出SharedPreferencesImpl实例
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
mode支持传入四种
MODE_PRIVATE
MODE_WORLD_READABLE
MODE_WORLD_WRITEABLE
MODE_MULTI_PROCESS
传入MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE抛出异常:
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");
}
}
}
MODE_MULTI_PROCESS注释中描述用于target sdk Android2.3以上,有些Android版本不生效且认为多进程不应该提供这种机制被弃用。
总结:
静态变量sSharedPrefsCache以包名做key
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl» sSharedPrefsCache;
ContextImpl成员变量mSharedPrefsPaths以"app路径/shared_prefs/name.xml"做key
private ArrayMap<String, File> mSharedPrefsPaths;
读
从读开始,既没有写复杂,又衔接上写部分的内容
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
mMode = mode;
mLoaded = false;
mMap = null;
mThrowable = null;
startLoadFromDisk();
}
涉及到的变量:
mFile:sp对应xml文件
mBackupFile:写入sp后保存
mMode:模式,上面说过
mLoaded:标识有没有加载过
mMap:存储着要取出来的键值对
mThrowable:保存从磁盘加载时的异常
mLock:用来加锁的对象
startLoadFromDisk:读取xml文件解析到mMap中,保存文件上次修改时间和文件大小
所有类型流程都是类似的,解析xml文件得到类型,以String为例:
@Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
sp构造方法中异步读取xml文件并尝试解析,有可能get时还没有结束,这时要等到结束才能继续下一步(不然从mMap取不到)
@GuardedBy("mLock")
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);
}
}
写
整理一下:
- mMap中存着所有的键值对
保存一个键值对时首先调用edit()方法:
@Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
// Editor, but then we should be able to do:
//
// context.getSharedPreferences(..).edit().putString(..).apply()
//
// ... all without blocking.
synchronized (mLock) {
awaitLoadedLocked();
}
return new EditorImpl();
}
为什么要等到解析完了?先跳过,看后边。
如果不需要等待解析,创建EditorImpl对象,EditorImpl中只有三个成员变量。分别是:
- mEditorLock:往map中添加时防止异步添加两个相同的键值对时有丢失
- mModified:需要修改的键值对
- mClear:表示有没有被清空
上面get是String,这里也对应看String:
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
只是添加到mModified中,同时支持链式调用。既然这样,那实际的逻辑一定在commit和apply。
commit
先看commit:
@Override
public boolean commit() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
// 返回了MemoryCommitResult对象
MemoryCommitResult mcr = commitToMemory();
//
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
// 开始的CountDownLatch,要countdown后才能往下走
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
// notify listeners
notifyListeners(mcr);
// 返回结果
return mcr.writeToDiskResult;
}
为什么返回的是mcr.writeToDiskResult?
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
boolean keysCleared = false;
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 (mEditorLock) {
boolean changesMade = false;
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
keysCleared = true;
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) {
keysModified.add(k);
}
}
mModified.clear();
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
从这可以看出来:
- MemoryCommitResult内部的map是在mMap的基础上,过滤了与存在了的相同的键值对得来的。
- 只有MemoryCommitResult#setDiskWriteResult中对writeToDiskResult赋值,所有调用处都是在实际的写入方法writeToFile中。
- setDiskWriteResult中调用writtenToDiskLatch.countDown(),commit方法可以继续往下执行。
第二条信息回应了上面为什么需要等到解析完成了再创建对象,写入时用到了这些已经存在的键值对。
接着看加入队列,enqueueDiskWrite:
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
// 上面的第二个参数Runnable是null,这种情况都是commit来的
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();
}
}
};
// 唯一需要写入的,直接run方法不会创建新线程,这时候在主线程写入。
if (isFromSyncCommit) {
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
// 加入队列,异步写入
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
除开在主线程写入的情况,其他都需要添加到QueuedWork中:
/**
* Queue a work-runnable for processing asynchronously.
*
* @param work The new runnable to process
* @param shouldDelay If the message should be delayed
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static void queue(Runnable work, boolean shouldDelay) {
// 返回的对象是一个handler,创建对象的参数Loop是handlerThread创建的子线程的。
Handler handler = getHandler();
synchronized (sLock) {
// 加到链表
sWork.add(work);
// 发送消息,sCanDelay留坑
if (shouldDelay && sCanDelay) {
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
} else {
// commit一定走这里
handler.sendEmptyMessage(QueuedWorkHandler.MSG_RUN);
}
}
}
MSG_RUN消息:
public void handleMessage(Message msg) {
if (msg.what == MSG_RUN) {
processPendingWork();
}
}
执行任务:
private static void processPendingWork() {
long startTime = 0;
if (DEBUG) {
startTime = System.currentTimeMillis();
}
synchronized (sProcessingWork) {
LinkedList<Runnable> work;
synchronized (sLock) {
work = sWork;
sWork = new LinkedList<>();
// Remove all msg-s as all work will be processed now
getHandler().removeMessages(QueuedWorkHandler.MSG_RUN);
}
if (work.size() > 0) {
for (Runnable w : work) {
// 虽然是调用run,运行确实在子线程。
w.run();
}
if (DEBUG) {
Log.d(LOG_TAG, "processing " + work.size() + " items took " +
+(System.currentTimeMillis() - startTime) + " ms");
}
}
}
}
运行了,追到:
final Runnable writeToDiskRunnable = new Runnable() {
@Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
}
synchronized (mLock) {
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
@GuardedBy("mWritingToDiskLock")
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
// 前提条件是Editor中mModified变化,与MemoryCommitResult创建时不一致。
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_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);
}
writeToFile主要做了几件事:
- 把map转化成xml格式,写入
- 更新mStatTimestamp和mStatSize状态
- 调用setDiskWriteResult,更新了写入文件结果同时commit中继续执行,能执行下一次提交。
apply
相较commit,enqueueDiskWrite传入了第二个参数postWriteRunnable,其他部分相同。postWriteRunnable对应代码:
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);
}
};
Runnable不为null,isFromSyncCommit为false,所有的任务都要到子线程执行。
QueuedWork#queue中shouldDelay为true,当shouldDelay和sCanDelay同时为true执行
handler.sendEmptyMessageDelayed(QueuedWorkHandler.MSG_RUN, DELAY);
sCanDelay的初始值就是true,改变它的值都在waitToFinish方法中,发现是找不得调用的。官方在注释中给我们的解释: 1)
/**
* 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)
*/
防止丢失异步任务。
开始遍历执行任务时赋值false,这时apply发送消息的不会延迟,执行结束后赋值true,又会延迟。为什么会反常呢?
waitToFinish是要防止丢失任务,在保存的时候来了一条新的任务,执行了才能存下来这次的数据。