文章目录
- 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.javaclass 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缓存获取ArrayMapfinal 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通过包名获取ArrayMapArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName);// packagePrefs为null则创建if (packagePrefs == null) {packagePrefs = new ArrayMap<>();sSharedPrefsCache.put(packageName, packagePrefs);}return packagePrefs;}
}
初始化和读取数据
// SharedPreferencesImpl.javafinal 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文件并转为Mapmap = (Map<String, Object>) XmlUtils.readMapXml(str); }synchronized (mLock) {mLoaded = true;if (map != null) {// 使用新MapmMap = 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.javafinal 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类型数据@Overridepublic Editor putString(String key, @Nullable String value) {synchronized (mEditorLock) {mModified.put(key, value);return this;}}// 存放int类型数据@Overridepublic Editor putInt(String key, int value) {synchronized (mEditorLock) {mModified.put(key, value);return this;}}// 删除数据@Overridepublic Editor remove(String key) {synchronized (mEditorLock) {mModified.put(key, this);return this;}}// 清除所有数据@Overridepublic Editor clear() {synchronized (mEditorLock) {mClear = true;return this;}}// 异步提交@Overridepublic void apply() {...}// 同步提交,并返回操作结果是否成功@Overridepublic boolean commit() {...}}
}
MemoryCommitResult
MemoryCommitResult 是一个事务对象,收集需要写入磁盘的数据。
// MemoryCommitResult.javaprivate 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.javafinal class SharedPreferencesImpl implements SharedPreferences {// 每次提交数据自增 1,事务结束自减 1private 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// 自增加1mDiskWritesInFlight++;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,表示删除这个keyif (!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;// 记录发生修改的keyif (hasListeners) {keysModified.add(k);}}// 重置内存中的mModifiedmModified.clear();// 修改完成,自增加1if (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();// 创建一个Runnablefinal Runnable awaitCommit = new Runnable() {@Overridepublic void run() {// 阻塞线程,直到磁盘操作执行完毕mcr.writtenToDiskLatch.await();}};// 将Runnable提交到QueuedWork中QueuedWork.addFinisher(awaitCommit);// 创建一个Runnable,写入成功后QueuedWork删除awaitCommit任务Runnable postWriteRunnable = new Runnable() {@Overridepublic 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() {@Overridepublic void run() {synchronized (mWritingToDiskLock) {// 写入到磁盘writeToFile(mcr, isFromSyncCommit);}synchronized (mLock) {// 自减1mDiskWritesInFlight--;}if (postWriteRunnable != null) {postWriteRunnable.run();}}};// 如果是同步操作if (isFromSyncCommit) {// 判断mDiskWritesInFlight变量,如果存在并发写入则交给QueuedWorkboolean 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.javapublic 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()。