文章目录
- Android SharedPreferences源码分析
- 概述
- 基本使用
- 源码分析
- 获取SP对象
- 初始化和读取数据
- 写入数据
- MemoryCommitResult
- commitToMemory()
- commit()
- apply()
- enqueueDiskWrite()
- writeToFile()
- 主动等待写回任务结束
- 总结
Android SharedPreferences源码分析
概述
SharedPreferences 是 Android 平台上轻量级的 K-V 存储框架。
SharedPreferences 采用 XML 文件格式持久化键值对数据,文件的存储位置位于应用沙盒的内部存储 /data/data/<包名>/shared_prefs/
位置,每个 XML 文件对应于一个 SharedPreferences 对象。
一个sp文件(XML文件) 对应一个SharedPreferences对象。
基本使用
SharedPreferences sp = context.getSharedPreferences("app", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("name", "小明")
.putInt("age", 18)
.putBoolean("sex", true)
.apply();
String name = sp.getString("name", "");
int age = sp.getInt("age", 0);
boolean sex = sp.getBoolean("sex", false);
查看 /data/data/com.example.myapplication/shared_prefs/app.xml 文件:
源码分析
获取SP对象
ContextImpl 类是 Context 抽象类的实现类。
// ContextImpl.java
class ContextImpl extends Context {
// sp文件的根目录
private File mPreferencesDir;
// 缓存name和file对象的对应关系
private ArrayMap<String, File> mSharedPrefsPaths;
// 缓存包名、file对象和SharedPreferencesImpl对象的对应关系
private static ArrayMap<String, ArrayMap<File, SharedPreferencesImpl>> sSharedPrefsCache;
// 通过name获取sp对象
public SharedPreferences getSharedPreferences(String name, int mode) {
File file;
synchronized (ContextImpl.class) {
// mSharedPrefsPaths缓存对象为null则创建
if (mSharedPrefsPaths == null) {
mSharedPrefsPaths = new ArrayMap<>();
}
// 从mSharedPrefsPaths缓存中获取file对象
file = mSharedPrefsPaths.get(name);
// 如果file对象为null,则创建file对象
if (file == null) {
// 通过路径获取file对象
file = getSharedPreferencesPath(name);
mSharedPrefsPaths.put(name, file);
}
}
return getSharedPreferences(file, mode);
}
// 创建file对象
public File getSharedPreferencesPath(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
// 获取sp文件的根目录
private File getPreferencesDir() {
synchronized (mSync) {
if (mPreferencesDir == null) {
mPreferencesDir = new File(getDataDir(), "shared_prefs");
}
return ensurePrivateDirExists(mPreferencesDir);
}
}
// 通过file对象获取sp对象
public SharedPreferences getSharedPreferences(File file, int mode) {
SharedPreferencesImpl sp;
synchronized (ContextImpl.class) {
// 从sSharedPrefsCache缓存获取ArrayMap
final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();
// 从缓存中获取sp对象
sp = cache.get(file);
if (sp == null) {
// 如果sp对象为null,则创建并缓存
checkMode(mode);
sp = new SharedPreferencesImpl(file, mode);
cache.put(file, sp);
return sp;
}
}
return sp;
}
// 从sSharedPrefsCache缓存获取sp对象
private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() {
// sSharedPrefsCache缓存对象为null则创建
if (sSharedPrefsCache == null) {
sSharedPrefsCache = new ArrayMap<>();
}
// 获取包名
final String packageName = getPackageName();
// sSharedPrefsCache通过包名获取ArrayMap
ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);
// packagePrefs为null则创建
if (packagePrefs == null) {
packagePrefs = new ArrayMap<>();
sSharedPrefsCache.put(packageName, packagePrefs);
}
return packagePrefs;
}
}
初始化和读取数据
// SharedPreferencesImpl.java
final class SharedPreferencesImpl implements SharedPreferences {
// 目标文件
private final File mFile;
// 锁
private final Object mLock = new Object();
// 文件是否加载
private boolean mLoaded = false;
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);
}
}
Map<String, Object> map = null;
if (mFile.canRead()) {
// 读取文件
BufferedInputStream str = new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);
// 解析XML文件并转为Map
map = (Map<String, Object>) XmlUtils.readMapXml(str);
}
synchronized (mLock) {
mLoaded = true;
if (map != null) {
// 使用新Map
mMap = map;
} else {
mMap = new HashMap<>();
}
// 解析完文件后唤醒锁
mLoaded = true;
mLock.notifyAll();
}
}
// 获取数据
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
// 查询数据时可能会阻塞等待
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
// 等待
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
mLock.wait();
} catch (InterruptedException unused) {
}
}
}
}
写入数据
虽然 ContextImpl 中使用了内存缓存,但是最终数据还是需要执行磁盘 IO 持久化到磁盘文件中。如果每一次 “变更操作” 都对应一次磁盘 “写回操作” 的话,不仅效率低下,而且没有必要。所以 SharedPreferences 会使用 “事务” 机制,将多次变更操作聚合为一个 “事务”,一次事务最多只会执行一次磁盘写回操作。
SharedPreferences 的事务操作由 Editor 接口实现。
// SharedPreferencesImpl.java
final class SharedPreferencesImpl implements SharedPreferences {
public Editor edit() {
// 等待文件加载完成
synchronized (mLock) {
awaitLoadedLocked();
}
// 创建编辑器
return new EditorImpl();
}
// 编辑器
public final class EditorImpl implements Editor {
// 锁对象
private final Object mEditorLock = new Object();
// 修改记录
private final Map<String, Object> mModified = new HashMap<>();
// 清除全部数据的标记
private boolean mClear = false;
// 存放String类型数据
@Override
public Editor putString(String key, @Nullable String value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
// 存放int类型数据
@Override
public Editor putInt(String key, int value) {
synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
// 删除数据
@Override
public Editor remove(String key) {
synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
// 清除所有数据
@Override
public Editor clear() {
synchronized (mEditorLock) {
mClear = true;
return this;
}
}
// 异步提交
@Override
public void apply() {
...
}
// 同步提交,并返回操作结果是否成功
@Override
public boolean commit() {
...
}
}
}
MemoryCommitResult
MemoryCommitResult 是一个事务对象,收集需要写入磁盘的数据。
// MemoryCommitResult.java
private static class MemoryCommitResult {
// 内存版本号
final long memoryStateGeneration;
// 写入磁盘的数据
final Map<String, Object> mapToWriteToDisk;
// 同步计数器
final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
@GuardedBy("mWritingToDiskLock")
volatile boolean writeToDiskResult = false;
boolean wasWritten = false;
void setDiskWriteResult(boolean wasWritten, boolean result) {
this.wasWritten = wasWritten;
writeToDiskResult = result;
// 唤醒等待锁
writtenToDiskLatch.countDown();
}
}
commitToMemory()
// SharedPreferencesImpl.java
final class SharedPreferencesImpl implements SharedPreferences {
// 每次提交数据自增 1,事务结束自减 1
private int mDiskWritesInFlight = 0;
// 内存版本
private long mCurrentMemoryStateGeneration;
// 磁盘版本
private long mDiskStateGeneration;
// 修改记录
private final Map<String, Object> mModified = new HashMap<>();
// 清除全部数据的标记
private boolean mClear = false;
// 全量提交到内存
private MemoryCommitResult commitToMemory() {
long memoryStateGeneration;
boolean keysCleared = false;
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; // 全量Map
// 自增加1
mDiskWritesInFlight++;
synchronized (mEditorLock) {
// 是否发生有效修改
boolean changesMade = false;
// 清除全部数据
if (mClear) {
if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
mapToWriteToDisk.clear();
}
keysCleared = true;
mClear = false;
}
// 将内存中的mModified都数据整合到mapToWriteToDisk中
for (Map.Entry<String, Object> e : mModified.entrySet()) {
String k = e.getKey();
Object v = e.getValue();
if (v == this || v == null) {
// 如果value是this,表示删除这个key
if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
mapToWriteToDisk.remove(k);
} else {
// 修改key-value值
if (mapToWriteToDisk.containsKey(k)) {
Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
mapToWriteToDisk.put(k, v);
}
// 标记修改完成
changesMade = true;
// 记录发生修改的key
if (hasListeners) {
keysModified.add(k);
}
}
// 重置内存中的mModified
mModified.clear();
// 修改完成,自增加1
if (changesMade) {
mCurrentMemoryStateGeneration++;
}
// 修改内存版本号
memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return new MemoryCommitResult(memoryStateGeneration, keysCleared, keysModified,
listeners, mapToWriteToDisk);
}
}
commit()
// SharedPreferencesImpl.EditorImpl#commit()
public boolean commit() {
// 写入到内存,并获取事务对象
MemoryCommitResult mcr = commitToMemory();
// 写入到磁盘
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, null);
// 写入等待
mcr.writtenToDiskLatch.await();
// 触发回调监听
notifyListeners(mcr);
return mcr.writeToDiskResult;
}
apply()
// SharedPreferencesImpl.EditorImpl#apply()
public void apply() {
// 写入到内存,并获取事务对象
final MemoryCommitResult mcr = commitToMemory();
// 创建一个Runnable
final Runnable awaitCommit = new Runnable() {
@Override
public void run() {
// 阻塞线程,直到磁盘操作执行完毕
mcr.writtenToDiskLatch.await();
}
};
// 将Runnable提交到QueuedWork中
QueuedWork.addFinisher(awaitCommit);
// 创建一个Runnable,写入成功后QueuedWork删除awaitCommit任务
Runnable postWriteRunnable = new Runnable() {
@Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
}
};
// 提交写入磁盘任务
SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
// 触发回调监听
notifyListeners(mcr);
}
enqueueDiskWrite()
// SharedPreferencesImpl#enqueueDiskWrite()
// postWriteRunnable为null表示commit同步提交,不为null表示apply异步提交
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) {
// 自减1
mDiskWritesInFlight--;
}
if (postWriteRunnable != null) {
postWriteRunnable.run();
}
}
};
// 如果是同步操作
if (isFromSyncCommit) {
// 判断mDiskWritesInFlight变量,如果存在并发写入则交给QueuedWork
boolean wasEmpty = false;
synchronized (mLock) {
wasEmpty = mDiskWritesInFlight == 1;
}
// wasEmpty为true,表示没有并发写入,则直接执行写入任务
if (wasEmpty) {
writeToDiskRunnable.run();
return;
}
}
// 如果是异步操作,提交到QueuedWork执行
QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
}
writeToFile()
// SharedPreferencesImpl#writeToFile()
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// 判断旧文件是否存在
boolean fileExists = mFile.exists();
if (fileExists) {
// 是否执行写入操作
boolean needsWrite = false;
// 如果磁盘版本小于内存版本,执行磁盘写入操作
if (mDiskStateGeneration < mcr.memoryStateGeneration) {
if (isFromSyncCommit) {
// 如果是同步执行,一定执行写入操作
needsWrite = true;
} else {
// 如果是异步执行,只有最新的内存版本菜执行写入操作
synchronized (mLock) {
if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
needsWrite = true;
}
}
}
}
// 无效的异步写回,直接结束
if (!needsWrite) {
mcr.setDiskWriteResult(false, true);
return;
}
// 文件备份
boolean backupFileExists = mBackupFile.exists();
if (!backupFileExists) {
// 如果没有备份文件,将旧文件改为备份文件
if (!mFile.renameTo(mBackupFile)) {
mcr.setDiskWriteResult(false, false);
return;
}
} else {
// 如果有备份文件,则删除旧文件
mFile.delete();
}
}
try {
// 执行写入操作,写入到xml文件中
FileOutputStream str = createFileOutputStream(mFile);
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
// 写入成功,直接删除备份文件
mBackupFile.delete();
// 同步磁盘版本号
mDiskStateGeneration = mcr.memoryStateGeneration;
// 写入成功
mcr.setDiskWriteResult(true, true);
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
} catch (IOException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
}
// 写入失败会执行这里
if (mFile.exists()) {
if (!mFile.delete()) {
Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
}
}
mcr.setDiskWriteResult(false, false);
}
主动等待写回任务结束
在 apply() 方法中,在执行 enqueueDiskWrite() 前创建了 awaitCommit 任务并加入到 QueudWork 等待队列,直到磁盘写回结束才将 awaitCommit 移除。这个 awaitCommit 任务是做什么的呢?
mcr.writtenToDiskLatch.await();
可以看到,在主线程的 Activity#onPause、Activity#onStop、Service#onStop、Service#onStartCommand 等生命周期状态变更时,会调用 QueudeWork.waitToFinish():
// ActivityThread.java
public void handlePauseActivity(...) {
performPauseActivity(r, finished, reason, pendingActions);
if (r.isPreHoneycomb()) {
QueuedWork.waitToFinish();
}
}
private void handleStopService(IBinder token) {
QueuedWork.waitToFinish();
ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}
waitToFinish() 会执行所有 sFinishers 等待队列中的 aWaitCommit 任务,主动等待所有磁盘写回任务结束。在写回任务结束之前,主线程会阻塞在等待锁上,这里也有可能发生 ANR。
总结
SharedPreferences 是一个轻量级的 K-V 存储框架。从源码分析中,可以看到 SharedPreferences 在读写性能、可用性方面都有做一些优化,例如:锁细化、事务化、事务过滤、文件备份等。
建议:
- 因为SP初始化时会加载全部sp文件和全量提交,因此大文件尽可能的拆分多个文件,修改多个数据时尽可能一起提交。
- 因为 commit() 是同步操作,apply() 是异步操作,因此推荐使用 apply()。