做网站公司佛山,网站内部资源推广的基本方法,资料共享的网站开发,个人做网站seo文章目录 Android SharedPreferences源码分析概述基本使用源码分析获取SP对象初始化和读取数据写入数据MemoryCommitResultcommitToMemory()commit()apply()enqueueDiskWrite()writeToFile() 主动等待写回任务结束 总结 Android SharedPreferences源码分析
概述
SharedPrefer… 文章目录 Android SharedPreferences源码分析概述基本使用源码分析获取SP对象初始化和读取数据写入数据MemoryCommitResultcommitToMemory()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 ArrayMapString, File mSharedPrefsPaths;// 缓存包名、file对象和SharedPreferencesImpl对象的对应关系private static ArrayMapString, ArrayMapFile, 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 ArrayMapFile, 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 ArrayMapFile, SharedPreferencesImpl getSharedPreferencesCacheLocked() {// sSharedPrefsCache缓存对象为null则创建if (sSharedPrefsCache null) {sSharedPrefsCache new ArrayMap();}// 获取包名final String packageName getPackageName();// sSharedPrefsCache通过包名获取ArrayMapArrayMapFile, 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);}}MapString, Object map null; if (mFile.canRead()) {// 读取文件BufferedInputStream str new BufferedInputStream(new FileInputStream(mFile), 16 * 1024);// 解析XML文件并转为Mapmap (MapString, 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 MapString, 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 MapString, 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 MapString, Object mModified new HashMap();// 清除全部数据的标记private boolean mClear false;// 全量提交到内存private MemoryCommitResult commitToMemory() {long memoryStateGeneration;boolean keysCleared false;ListString keysModified null;SetOnSharedPreferenceChangeListener listeners null;MapString, Object mapToWriteToDisk;synchronized (SharedPreferencesImpl.this.mLock) {// 表示有多个写入操作if (mDiskWritesInFlight 0) {mMap new HashMapString, 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.EntryString, 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, Couldnt 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()。