1. 获取 SharedPreferences 对象
SharedPreferences 是一个接口,其实现类是 SharedPreferencesImpl。通过 getSharedPreferences 获取到的是一个 SharedPreferencesImpl 对象:
// android/app/ContextImpl.java
// SharedPreferences file 标识名为 key
private ArrayMap<String, File> mSharedPrefsPaths;
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
...
File file;
synchronized (ContextImpl.class) {
if (mSharedPrefsPaths == null) {// 首次获取都为空
mSharedPrefsPaths = new ArrayMap<>();
}
file = mSharedPrefsPaths.get(name);
if (file == null) {
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
// file:/data/user/0/<包名>/shared_prefs/<包名>.xml
}
}
return getSharedPreferences(file, mode);
}
首次获取都为空,接着调用了 getSharedPreferencesPath 方法,根据传入的 name,获取 SharedPreferences file 路径名:
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
@UnsupportedAppUsage
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");// /data/user/0/<包名>/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);
BlockGuard.getVmPolicy().onPathAccess(res.getPath());
return res;
}
throw new IllegalArgumentException(
"File " + name + " contains a path separator");
}
实际上存储 SharedPreferences file 的路径是 /data/user/0/<包名>/shared_prefs。
具体文件名是 /data/user/0/<包名>/shared_prefs/name.xml。这里可以看出 SharedPreferences file 实际上是一个 xml 文件。
这样就获取到了 name 对应的 file,接着将其存到 mSharedPrefsPaths 中。接着调用 getSharedPreferences(file, mode):
// android/app/ContextImpl.java
@Override
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
// 返回一个 cache 对象,空初始化
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
sp = cache.get(file);
if (sp == null) {// sp 空
checkMode(mode);// 简单判断,传入的 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 (id "
+ UserHandle.myUserId() + ") is unlocked");
}
}
// 创建 SharedPreferencesImpl 对象
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;
}
通过 getSharedPreferencesCacheLocked 方法获取到了一个 ArrayMap<File, SharedPreferencesImpl> 对象:
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
@GuardedBy("ContextImpl.class")
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;
}
最终创建并返回一个 SharedPreferencesImpl 对象。
2. SharedPreferencesImpl 的初始化,将磁盘文件缓存至内存中
看一下 SharedPreferencesImpl 的构造方法:
SharedPreferencesImpl(File file, int mode) {
mFile = file;// 存储 SharedPreferences 对应的 XML 文件
mBackupFile = makeBackupFile(file);// 创建备份文件(如 "file.xml.bak"),备份文件用于在写入失败时恢复数据
mMode = mode;// 存储文件访问模式(如 MODE_PRIVATE)
mLoaded = false;// 数据是否已经从磁盘加载到内存中
mMap = null;// 内存中存储键值对的 Map,初始为 null
mThrowable = null;// 存储加载过程中可能抛出的异常
startLoadFromDisk();
}
首先 SharedPreferencesImpl 对象中存储了 SharedPreferences 对应的 XML 文件。并通过 makeBackupFile 方法创建了一个备份文件:
static File makeBackupFile(File prefsFile) {
return new File(prefsFile.getPath() + ".bak");
}
该文件路径为 /data/user/0/<包名>/shared_prefs/name.xml.bak
接着调用 startLoadFromDisk 方法,将 xml 文件从磁盘加载至内存中:
private void startLoadFromDisk() {
synchronized (mLock) {
mLoaded = false;// 重置加载状态
}
new Thread("SharedPreferencesImpl-load") {
public void run() {
loadFromDisk();// 开启新线程加载数据
}
}.start();
}
开启子线程开始加载数据:
private final Object mLock = new Object();
private void loadFromDisk() {
synchronized (mLock) {
if (mLoaded) {
return;
}
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);// 解析 XML 到 Map
} 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();// 唤醒所有等待的线程,如 edit
}
}
}
如果备份文件存在(可能因上次写入失败遗留),删除主文件并重命名备份文件为主文件,确保数据完整性。
以上代码创建 BufferedInputStream 读取数据,并将 XML 解析存储至 map 中。解析成功后调用 notifyAll 唤醒你所有等待的线程。
3. 获取 SharedPreferences.Editor 对象
通过调用 edit 方法获取 SharedPreferences.Editor 对象:
@Override
public Editor edit() {
synchronized (mLock) {
awaitLoadedLocked();// 确保 SharedPreferences 数据已从磁盘加载完成,否则阻塞当前线程。
}
// 创建内部类 `EditorImpl` 实例,用于批量操作键值对。
return new EditorImpl();
}
SharedPreferences 数据已从磁盘加载完成之前,不能创建 Editor 对象。首先通过 awaitLoadedLocked 方法,确保 SharedPreferences 数据已从磁盘加载完成,否则阻塞当前线程:
@GuardedBy("mLock")
private void awaitLoadedLocked() {
if (!mLoaded) {// 未加载完成
// StrictMode 标记磁盘读操作
BlockGuard.getThreadPolicy().onReadFromDisk();
}
while (!mLoaded) {
try {
mLock.wait();// 等待数据加载完
} catch (InterruptedException unused) {
}
}
if (mThrowable != null) {
throw new IllegalStateException(mThrowable);
}
}
StrictMode 标记,在数据未加载时触发 onReadFromDisk(),帮助开发者发现潜在的主线程磁盘操作。通过 mLock.wait() 释放锁并等待,直到其他线程调用 mLock.notifyAll()(数据加载完成后触发)。不断循环判断是否已完成数据加载,若加载过程中发生异常(如文件解析错误),抛出 IllegalStateException。
若加载完成,创建并返回一个 EditorImpl 对象,该对象用来将数据写入至文件中。
4. Editor put
以 putString 方法为例:
private final Object mEditorLock = new Object();
private final Map<String, Object> mModified = new HashMap<>();
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
向 Editor 的 Map mModified 中存入想要保存的值,并返回了一个 Editor 对象。
5. Editor commit
commit 方法是一个同步方法,返回值为 boolean:
@Override
public boolean commit() {
...
// 将 SharedPreferences 修改提交至内存,返回提交结果
MemoryCommitResult mcr = commitToMemory();
// 将内存提交结果 `mcr` 加入磁盘写入队列。第二个参数为 `null` 表示这是一个同步提交(`commit`),需要立即执行。
SharedPreferencesImpl.this.enqueueDiskWrite(
mcr, null /* sync write on this thread okay */);
try {
// 同步等待:通过 `CountDownLatch` 阻塞当前线程,直到磁盘写入完成。
mcr.writtenToDiskLatch.await();
} catch (InterruptedException e) {
return false;// 若等待过程中线程被中断,返回 `false`
} finally {
if (DEBUG) {
Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
+ " committed after " + (System.currentTimeMillis() - startTime)
+ " ms");
}
}
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
调用 commitToMemory 方法,将 SharedPreferences 修改提交至内存,返回提交结果:
// EditorImpl
// Returns true if any changes were made
private MemoryCommitResult commitToMemory() {
...
synchronized (SharedPreferencesImpl.this.mLock) {
if (mDiskWritesInFlight > 0) {
// 如果当前有未完成的磁盘写入(`mDiskWritesInFlight > 0`),克隆 `mMap` 避免并发修改。
mMap = new HashMap<String, Object>(mMap);
}
mapToWriteToDisk = mMap;
mDiskWritesInFlight++;// `mDiskWritesInFlight` 记录正在进行的磁盘写入操作次数。
// 如果有注册的监听器,初始化 `keysModified` 和 `listeners` 集合
boolean hasListeners = mListeners.size() > 0;
if (hasListeners) {
keysModified = new ArrayList<String>();
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
synchronized (mEditorLock) {// 加锁
boolean changesMade = false;
// 若 `mClear` 为 `true`,清空 `mapToWriteToDisk`,标记 `keysCleared`
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
keysCleared = true;
mClear = false;
}
// 遍历 `mModified`,移除值为 `this` 或 `null` 的键,否则更新值。记录变更的键到 `keysModified`
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
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`
mCurrentMemoryStateGeneration++;
}
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
mDiskWritesInFlight > 0 时,表示当前有未完成的磁盘写入,克隆 mMap 避免并发修改。遍历 mModified,移除值为 this 或 null 的键,否则更新值。记录变更的键到 keysModified。最后新建并返回了一个 MemoryCommitResult 对象,该对象封装了提交数据时的相关参数。
接着调用了 SharedPreferencesImpl 的 enqueueDiskWrite 方法,commit 中传入的第二个参数为 null,将内存提交结果 mcr 加入磁盘写入队列。第二个参数为 null 表示这是一个同步提交(commit),需要立即执行。
若为同步提交且无其他写入任务,直接执行写入;否则通过 QueuedWork 加入队列。
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();// 执行后续回调(如用于 apply())
}
}
};
if (isFromSyncCommit) {// commit 方法这里为 true
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;// 判断是否无其他写入任务
}
if (wasEmpty) {
writeToDiskRunnable.run();// 直接在当前线程执行写入任务
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);// 加入队列异步执行
}
最后调用 notifyListeners 通知变化,保证 onSharedPreferenceChanged 的回调在主线程,将 keysModified 回调至主线程:
private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
}
}
}
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
} else {
// Run this function on the main thread.
ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}
6. Editor apply
@Override
public void apply() {
...
// 将 SharedPreferences 修改提交至内存,返回提交结果
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);
}
enqueueDiskWrite 方法将任务加入队列:
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();
}
}
};
// 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) {
writeToDiskRunnable.run();
return;
}
}
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
通过 HandlerThread 在子线程中执行:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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);
}
}
}
@UnsupportedAppUsage
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;
}
}
确保监听器回调在主线程执行,避免 UI 操作问题,可能在数据写入磁盘之前就调用了 notifyListeners:
private void notifyListeners(final MemoryCommitResult mcr) {
if (mcr.listeners == null || (mcr.keysModified == null && !mcr.keysCleared)) {
return;
}
if (Looper.myLooper() == Looper.getMainLooper()) {
// 主线程直接触发监听器
if (mcr.keysCleared && Compatibility.isChangeEnabled(CALLBACK_ON_CLEAR_CHANGE)) {
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, null);
}
}
}
for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
final String key = mcr.keysModified.get(i);
for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
if (listener != null) {
listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
}
}
}
} else {
// 非主线程通过 Handler 切换到主线程
// Run this function on the main thread.
ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}