01.Sp简单介绍php
02.Sp初始化操做java
04.put和get方法源码android
05.commit和applygit
sp做用说明github
分析sp包含那些内容面试
分析sp包含那些源码segmentfault
代码以下所示缓存
long startA = System.currentTimeMillis(); for (int i=0 ; i<200 ; i++){ SharedPreferences preferences = this.getSharedPreferences("testA", 0); SharedPreferences.Editor edit = preferences.edit(); edit.putString("yc"+i,"yangchong"+i); edit.commit(); } long endA = System.currentTimeMillis(); long a = endA - startA; Log.i("测试A","----"+a); long startB = System.currentTimeMillis(); SharedPreferences preferencesB = this.getSharedPreferences("testB", 0); SharedPreferences.Editor editB = preferencesB.edit(); for (int i=0 ; i<200 ; i++){ editB.putString("yc"+i,"yangchong"+i); } editB.commit(); long endB = System.currentTimeMillis(); long b = endB - startB; Log.i("测试B","----"+b); long startC = System.currentTimeMillis(); SharedPreferences.Editor editC = null; for (int i=0 ; i<200 ; i++){ SharedPreferences preferencesC = this.getSharedPreferences("testC", 0); if (editC==null){ editC = preferencesC.edit(); } editC.putString("yc"+i,"yangchong"+i); } editC.commit(); long endC = System.currentTimeMillis(); long c = endC - startC; Log.i("测试C","----"+c);
而后开始执行操做安全
而后看一下执行结果markdown
2019-08-30 15:08:16.982 3659-3659/com.cheoo.app I/测试A: ----105 2019-08-30 15:08:17.035 3659-3659/com.cheoo.app I/测试B: ----52 2019-08-30 15:08:17.069 3659-3659/com.cheoo.app I/测试C: ----34 2019-08-30 15:08:20.561 3659-3659/com.cheoo.app I/测试A: ----25 2019-08-30 15:08:20.562 3659-3659/com.cheoo.app I/测试B: ----1 2019-08-30 15:08:20.564 3659-3659/com.cheoo.app I/测试C: ----2
结果分析
而后看看里面存储值
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="yc110">yangchong110</string> <string name="yc111">yangchong111</string> <string name="yc118">yangchong118</string> <string name="yc119">yangchong119</string> <string name="yc116">yangchong116</string> <string name="yc117">yangchong117</string> <string name="yc114">yangchong114</string> <string name="yc115">yangchong115</string> <string name="yc112">yangchong112</string> <string name="yc113">yangchong113</string> <string name="yc121">yangchong121</string> <string name="yc122">yangchong122</string> <string name="yc120">yangchong120</string> <string name="yc129">yangchong129</string> <string name="yc127">yangchong127</string> <string name="yc128">yangchong128</string> <string name="yc125">yangchong125</string> <string name="yc126">yangchong126</string> <string name="yc123">yangchong123</string> <string name="yc124">yangchong124</string> <string name="yc1">yangchong1</string> <string name="yc109">yangchong109</string> <string name="yc0">yangchong0</string> <string name="yc3">yangchong3</string> </map>
代码以下所示
long startA = System.currentTimeMillis(); for (int i=0 ; i<200 ; i++){ SharedPreferences preferences = activity.getSharedPreferences("testA", 0); SharedPreferences.Editor edit = preferences.edit(); edit.putString("yc"+i,"yangchong"+i); edit.apply(); } long endA = System.currentTimeMillis(); long a = endA - startA; Log.i("测试A","----"+a); long startB = System.currentTimeMillis(); SharedPreferences preferencesB = activity.getSharedPreferences("testB", 0); SharedPreferences.Editor editB = preferencesB.edit(); for (int i=0 ; i<200 ; i++){ editB.putString("yc"+i,"yangchong"+i); } editB.apply(); long endB = System.currentTimeMillis(); long b = endB - startB; Log.i("测试B","----"+b); long startC = System.currentTimeMillis(); SharedPreferences.Editor editC = null; for (int i=0 ; i<200 ; i++){ SharedPreferences preferencesC = activity.getSharedPreferences("testC", 0); if (editC==null){ editC = preferencesC.edit(); } editC.putString("yc"+i,"yangchong"+i); } editC.apply(); long endC = System.currentTimeMillis(); long c = endC - startC; Log.i("测试C","----"+c);
而后看一下执行结果
2019-08-30 15:17:07.341 5522-5522/com.cheoo.app I/测试A: ----54 2019-08-30 15:17:07.346 5522-5522/com.cheoo.app I/测试B: ----5 2019-08-30 15:17:07.352 5522-5522/com.cheoo.app I/测试C: ----6 2019-08-30 15:17:10.541 5522-5522/com.cheoo.app I/测试A: ----32 2019-08-30 15:17:10.542 5522-5522/com.cheoo.app I/测试B: ----1 2019-08-30 15:17:10.543 5522-5522/com.cheoo.app I/测试C: ----1
得出结论
能够看出屡次执行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(); }
首先看ContextWrapper源码
@Override public SharedPreferences getSharedPreferences(String name, int mode) { return mBase.getSharedPreferences(name, mode); }
而后看一下ContextImpl类
@Override public SharedPreferences getSharedPreferences(String name, int mode) { // At least one application in the world actually passes in a null // name. This happened to work because when we generated the file name // we would stringify it to "null.xml". Nice. if (mPackageInfo.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.KITKAT) { if (name == null) { name = "null"; } } File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); } file = mSharedPrefsPaths.get(name); if (file == null) { // 建立一个对应路径 /data/data/packageName/name 的 File 对象 file = getSharedPreferencesPath(name); mSharedPrefsPaths.put(name, file); } } // 这里调用了 getSharedPreferences(File file, int mode) 方法 return getSharedPreferences(file, mode); }
而后接着看一下getSharedPreferences(file, mode)方法源码
@Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; // 这里使用了 synchronized 关键字,确保了 SharedPreferences 对象的构造是线程安全的 synchronized (ContextImpl.class) { // 获取SharedPreferences 对象的缓存,并复制给 cache final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); // 以参数 file 做为 key,获取缓存对象 sp = cache.get(file); if (sp == null) { // 若是缓存中不存在 SharedPreferences 对象 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 "
} } // 构造一个 SharedPreferencesImpl 对象 sp = new SharedPreferencesImpl(file, mode); // 放入缓存 cache 中,方便下次直接从缓存中获取 cache.put(file, sp); // 返回新构造的 SharedPreferencesImpl 对象 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. // 若是由其余进程修改了这个 SharedPreferences 文件,咱们将会从新加载它 sp.startReloadIfChangedUnexpectedly(); } // 程序走到这里,说明命中了缓存,SharedPreferences 已经建立,直接返回 return sp; } ```
这段源码的流程仍是清晰易懂的,注释已经说得很明白,这里咱们总结一下这个方法的要点:
看SharedPreferencesImpl的构造方法,源码以下所示
// SharedPreferencesImpl.java // 构造方法 SharedPreferencesImpl(File file, int mode) { mFile = file; // 建立灾备文件,命名为prefsFile.getPath() + ".bak" mBackupFile = makeBackupFile(file); mMode = mode; // mLoaded表明是否已经加载完数据 mLoaded = false; // 解析 xml 文件获得的键值对就存放在mMap中 mMap = null; // 顾名思义,这个方法用于加载 mFile 这个磁盘上的 xml 文件 startLoadFromDisk(); } // 建立灾备文件,用于当用户写入失败的时候恢复数据 private static File makeBackupFile(File prefsFile) { return new File(prefsFile.getPath() + ".bak"); }
而后看一下调用startLoadFromDisk()方法加载数据
// SharedPreferencesImpl.java private void startLoadFromDisk() { synchronized (this) { mLoaded = false; } //注意:这里咱们能够看出,SharedPreferences 是经过开启一个线程来异步加载数据的 new Thread("SharedPreferencesImpl-load") { public void run() { // 这个方法才是真正负责从磁盘上读取 xml 文件数据 loadFromDisk(); } }.start(); } private void loadFromDisk() { synchronized (SharedPreferencesImpl.this) { // 若是正在加载数据,直接返回 if (mLoaded) { return; } // 若是备份文件存在,删除原文件,把备份文件重命名为原文件的名字 // 咱们称这种行为叫作回滚 if (mBackupFile.exists()) { mFile.delete(); mBackupFile.renameTo(mFile); } } // Debugging if (mFile.exists() && !mFile.canRead()) { Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission"); } Map map = null; StructStat stat = null; try { // 获取文件信息,包括文件修改时间,文件大小等 stat = Os.stat(mFile.getPath()); if (mFile.canRead()) { BufferedInputStream str = null; try { // 读取数据而且将数据解析为jia str = new BufferedInputStream( new FileInputStream(mFile), *); map = XmlUtils.readMapXml(str); } catch (XmlPullParserException | IOException e) { Log.w(TAG, "getSharedPreferences", e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { /* ignore */ } synchronized (SharedPreferencesImpl.this) { // 加载数据成功,设置 mLoaded 为 true mLoaded = true; if (map != null) { // 将解析获得的键值对数据赋值给 mMap mMap = map; // 将文件的修改时间戳保存到 mStatTimestamp 中 mStatTimestamp = stat.st_mtime; // 将文件的大小保存到 mStatSize 中 mStatSize = stat.st_size; } else { mMap = new HashMap<>(); } // 通知唤醒全部等待的线程 notifyAll(); } }
对startLoadFromDisk()方法进行了分析,有分析咱们能够获得如下几点总结:
源码方法以下所示
@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(); }
就以putString为例分析源码。经过sharedPreferences.edit()方法返回的SharedPreferences.Editor,全部咱们对SharedPreferences的写操做都是基于这个Editor类的。在 Android 系统中,Editor是一个接口类,它的具体实现类是EditorImpl:
public final class EditorImpl implements Editor { // putXxx/remove/clear等写操做方法都不是直接操做 mMap 的,而是将全部 // 的写操做先记录在 mModified 中,等到 commit/apply 方法被调用,才会将 // 全部写操做同步到 内存中的 mMap 以及磁盘中 private final Map<String, Object> mModified = Maps.newHashMap(); // private boolean mClear = false; public Editor putString(String key, @Nullable String value) { synchronized (this) { mModified.put(key, value); return this; } } ...... 其余方法 ...... }
从EditorImpl类的源码咱们能够得出如下总结:
就以getString为例分析源码
@Nullable public String getString(String key, @Nullable String defValue) { // synchronize 关键字用于保证 getString 方法是线程安全的 synchronized (this) { // 方法 awaitLoadedLocked() 用于确保加载完数据并保存到 mMap 中才进行数据读取 awaitLoadedLocked(); // 根据 key 从 mMap中获取 value String v = (String)mMap.get(key); // 若是 value 不为 null,返回 value,若是为 null,返回默认值 return v != null ? v : defValue; } } 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(); } // 前面咱们说过,mLoaded 表明数据是否已经加载完毕 while (!mLoaded) { try { // 等待数据加载完成以后才返回继续执行代码 wait(); } catch (InterruptedException unused) { } } }
getString方法代码很简单,其余的例如getInt,getFloat方法也是同样的原理,直接对这个疑问进行总结:
commit()方法分析
public boolean commit() { // 前面咱们分析 putXxx 的时候说过,写操做的记录是存放在 mModified 中的 // 在这里,commitToMemory() 方法就负责将 mModified 保存的写记录同步到内存中的 mMap 中 // 而且返回一个 MemoryCommitResult 对象 MemoryCommitResult mcr = commitToMemory(); // enqueueDiskWrite 方法负责将数据落地到磁盘上 SharedPreferencesImpl.this.enqueueDiskWrite( mcr, null /* sync write on this thread okay */); try { // 同步等待数据落地磁盘工做完成才返回 mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } // 通知观察者 notifyListeners(mcr); return mcr.writeToDiskResult; }
commit()方法的主体结构很清晰简单:
接着来看一下它调用的commitToMemory()方法:
private MemoryCommitResult commitToMemory() { MemoryCommitResult mcr = new MemoryCommitResult(); synchronized (SharedPreferencesImpl.this) { // 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); } // 将 mMap 赋值给 mcr.mapToWriteToDisk,mcr.mapToWriteToDisk 指向的就是最终写入磁盘的数据 mcr.mapToWriteToDisk = mMap; // mDiskWritesInFlight 表明的是“此时须要将数据写入磁盘,但还未处理或未处理完成的次数” // 将 mDiskWritesInFlight 自增1(这里是惟一会增长 mDiskWritesInFlight 的地方) mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { mcr.keysModified = new ArrayList<String>(); mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { // 只有调用clear()方法,mClear才为 true if (mClear) { if (!mMap.isEmpty()) { mcr.changesMade = true; // 当 mClear 为 true,清空 mMap mMap.clear(); } mClear = false; } // 遍历 mModified for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); // 获取 key Object v = e.getValue(); // 获取 value // 当 value 的值是 "this" 或者 null,将对应 key 的键值对数据从 mMap 中移除 if (v == this || v == null) { if (!mMap.containsKey(k)) { continue; } mMap.remove(k); } else { // 不然,更新或者添加键值对数据 if (mMap.containsKey(k)) { Object existingValue = mMap.get(k); if (existingValue != null && existingValue.equals(v)) { continue; } } mMap.put(k, v); } mcr.changesMade = true; if (hasListeners) { mcr.keysModified.add(k); } } // 将 mModified 同步到 mMap 以后,清空 mModified 历史记录 mModified.clear(); } } return mcr; }
commitToMemory()方法主要作了这几件事:
对调用的enqueueDiskWrite方法进行分析:
private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { // 建立一个 Runnable 对象,该对象负责写磁盘操做 final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { // 顾名思义了,这就是最终经过文件操做将数据写入磁盘的方法了 writeToFile(mcr); } synchronized (SharedPreferencesImpl.this) { // 写入磁盘后,将 mDiskWritesInFlight 自减1,表明写磁盘的需求减小一个 mDiskWritesInFlight--; } if (postWriteRunnable != null) { // 执行 postWriteRunnable(提示,在 apply 中,postWriteRunnable 才不为 null) postWriteRunnable.run(); } } }; // 若是传进的参数 postWriteRunnable 为 null,那么 isFromSyncCommit 为 true // 舒适提示:从上面的 commit() 方法源码中,能够看出调用 commit() 方法传入的 postWriteRunnable 为 null final boolean isFromSyncCommit = (postWriteRunnable == null); // Typical #commit() path with fewer allocations, doing a write on the current thread. if (isFromSyncCommit) { boolean wasEmpty = false; synchronized (SharedPreferencesImpl.this) { // 若是此时只有一个 commit 请求(注意,是 commit 请求,而不是 apply )未处理,那么 wasEmpty 为 true wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { // 当只有一个 commit 请求未处理,那么无需开启线程进行处理,直接在本线程执行 writeToDiskRunnable 便可 writeToDiskRunnable.run(); return; } } // 将 writeToDiskRunnable 方法线程池中执行 // 程序执行到这里,有两种可能: // 1. 调用的是 commit() 方法,而且当前只有一个 commit 请求未处理 // 2. 调用的是 apply() 方法 QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); } private void writeToFile(MemoryCommitResult mcr) { // Rename the current file so it may be used as a backup during the next read if (mFile.exists()) { if (!mcr.changesMade) { // If the file already exists, but no changes were // made to the underlying map, it's wasteful to // re-write the file. Return as if we wrote it // out. mcr.setDiskWriteResult(true); return; } if (!mBackupFile.exists()) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile
mcr.setDiskWriteResult(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 (str == null) { mcr.setDiskWriteResult(false); return; } XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); FileUtils.sync(str); str.close(); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); try { final StructStat stat = Libcore.os.stat(mFile.getPath()); synchronized (this) { mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } } catch (ErrnoException e) { // Do nothing } // Writing was successful, delete the backup file if there is one. mBackupFile.delete(); mcr.setDiskWriteResult(true); 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); } ``` - writeToFile这个方法大体分为三个过程: - 先把已存在的老的 SP 文件重命名(加“.bak”后缀),而后删除老的 SP 文件,这至关于作了备份(灾备) - 向mFile中一次性写入全部键值对数据,即mcr.mapToWriteToDisk(这就是commitToMemory所说的保存了全部键值对数据的字段) 一次性写入到磁盘。 - 若是写入成功则删除备份(灾备)文件,同时记录了此次同步的时间若是往磁盘写入数据失败,则删除这个半成品的 SP 文件
apply()方法分析
public void apply() { // 将 mModified 保存的写记录同步到内存中的 mMap 中,而且返回一个 MemoryCommitResult 对象 final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; QueuedWork.add(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.remove(awaitCommit); } }; // 将数据落地到磁盘上,注意,传入的 postWriteRunnable 参数不为 null,因此在 // enqueueDiskWrite 方法中会开启子线程异步将数据写入到磁盘中 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); }
总结一下apply()方法: