SharedPreference源码分析
<p>SharePreference属于轻量级的键值存储方式,以XML文件方式保存数据。</p> <p>老规矩,先上图:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9776cef6b89eb0d38193bc7c6238348d.png"></p> <p style="text-align:center">SharedPreference.png</p> <h2><strong>获取SharedPreferences</strong></h2> <p>我们一般有两种方式获取SharedPreference:</p> <pre> <code class="language-java">Activity的public SharedPreferences getPreferences(int mode)方法 或者 ContextImpl的public SharedPreferences getSharedPreferences(String name, int mode)方法</code></pre> <p>Activity的public SharedPreferences getPreferences(int mode)方法实际上调用了ContextImpl的public SharedPreferences getSharedPreferences(String name, int mode)方法:</p> <pre> <code class="language-java">public SharedPreferences getPreferences(int mode) { return getSharedPreferences(getLocalClassName(), mode); }</code></pre> <p>public abstract SharedPreferences getSharedPreferences(String name, int mode)是Context抽象类的抽象方法,它的实现类是哪个呢?这个涉及到Activity的启动,感兴趣的可以看下ActivityThread的performLaunchActivity方法。这个Context的实现类是ContextImpl类。</p> <p>看下它的getSharedPreferences方法。</p> <pre> <code class="language-java">@Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { if (sSharedPrefs == null) { sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>(); //创建一个map,键是包名,值还是一个map(j键是传入的name,值是SharedPreferencesImpl对象) } final String packageName = getPackageName(); ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName); //获取当前包名对应的map if (packagePrefs == null) { //若为空则创建一个 packagePrefs = new ArrayMap<String, SharedPreferencesImpl>(); sSharedPrefs.put(packageName, packagePrefs); } // 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"; } } sp = packagePrefs.get(name); //根据传入的name获取SharedPreferencesImpl对象 if (sp == null) { //若为空,则新建一个SharedPreferencesImpl对象 File prefsFile = getSharedPrefsFile(name); //使用name创建一个文件 sp = new SharedPreferencesImpl(prefsFile, mode); //新建一个SharedPreferencesImpl对象 packagePrefs.put(name, sp); //放入当前包名对应的map中 return sp; //方法SharedPreferencesImpl对象 } } 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; }</code></pre> <p>这里建议看下类图,弄明白sSharedPrefs这个map的含义。这个方法从sSharedPrefs中根据包名获取一个和这个包名对应的map,然后从这个map中获取和传入的name关联的SharedPreferencesImpl对象,若不存在则创建这个对象。我们看下这个类。</p> <pre> <code class="language-java">final class SharedPreferencesImpl implements SharedPreferences ------------------------------成员变量----------------------------------- private final File mFile; //保存数据的文件 private final File mBackupFile; private final int mMode; private Map<String, Object> mMap; //保存和该name关联的键值对 ------------------------------构造方法-------------------------------------- SharedPreferencesImpl(File file, int mode) { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; mLoaded = false; mMap = null; startLoadFromDisk(); //从文件获取map数据 } private void startLoadFromDisk() { synchronized (this) { mLoaded = false; } new Thread("SharedPreferencesImpl-load") { public void run() { synchronized (SharedPreferencesImpl.this) { loadFromDiskLocked();//子线程中加载文件中的数据到成员变量map中 } } }.start(); } private void loadFromDiskLocked() { 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 { str = new BufferedInputStream( new FileInputStream(mFile), 16*1024); //获取文件数据流 map = XmlUtils.readMapXml(str); //解析xml数据,保存到map中 } catch (XmlPullParserException e) { Log.w(TAG, "getSharedPreferences", e); } catch (FileNotFoundException e) { Log.w(TAG, "getSharedPreferences", e); } catch (IOException e) { Log.w(TAG, "getSharedPreferences", e); } finally { IoUtils.closeQuietly(str); } } } catch (ErrnoException e) { } mLoaded = true; if (map != null) { mMap = map; //赋值给成员变量 mStatTimestamp = stat.st_mtime; mStatSize = stat.st_size; } else { mMap = new HashMap<String, Object>(); } notifyAll(); }</code></pre> <p>创建SharedPreferencesImpl对象的同时,会从对应的xml文件中解析数据,保存到成员变量mMap中。</p> <p>到这里,我就就创建好了SharedPreferences,所以我们调用getSharedPreferences后返回的SharedPreferences就是SharedPreferencesImpl对象。</p> <h2><strong>读写数据</strong></h2> <p>首先我们要获取一个Editor对象</p> <pre> <code class="language-java">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 (this) { awaitLoadedLocked(); } return new EditorImpl(); }</code></pre> <p>这个Editor的实现是EditorImpl,我们看下这个类:</p> <pre> <code class="language-java">public final class EditorImpl implements Editor -----------------------------成员变量------------------------------- private final Map<String, Object> mModified = Maps.newHashMap(); ----------------------------核心方法--------------------------- public Editor putBoolean(String key, boolean value) 等</code></pre> <p>比如要写入一个boolean值我们调用Editor的如下方法:</p> <pre> <code class="language-java">public Editor putBoolean(String key, boolean value) { synchronized (this) { mModified.put(key, value); return this; } }</code></pre> <p>很简单,将本次增加的键值对保存到mModified中,暂时缓存在内存中。</p> <h2><strong>同步数据到文件</strong></h2> <p>想要把数据同步到文件要调用commit()方法:</p> <pre> <code class="language-java">public boolean commit() { MemoryCommitResult mcr = commitToMemory(); //mModified和mMap中的数据一并保存到MemoryCommitResult对象中 SharedPreferencesImpl.this.enqueueDiskWrite( //写入到文件 mcr, null); try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException e) { return false; } notifyListeners(mcr); return mcr.writeToDiskResult; } private void notifyListeners(final MemoryCommitResult mcr) { if (mcr.listeners == null || mcr.keysModified == null || mcr.keysModified.size() == 0) { return; } if (Looper.myLooper() == Looper.getMainLooper()) { 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(new Runnable() { public void run() { notifyListeners(mcr); } }); } } }</code></pre> <p>这里注意commitToMemory方法,该方法将本次修改的数据mModified和以前的数据mMap一并保存到MemoryCommitResult对象中,然后调用enqueueDiskWrite方法将内存中的数据写入到文件。</p> <p>看下commitToMemory方法:</p> <pre> <code class="language-java">private MemoryCommitResult commitToMemory() { MemoryCommitResult mcr = new MemoryCommitResult(); //新建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); } mcr.mapToWriteToDisk = mMap; //将mMap保存到mcr的mapToWriteToDisk这个map中 mDiskWritesInFlight++; boolean hasListeners = mListeners.size() > 0; if (hasListeners) { mcr.keysModified = new ArrayList<String>(); mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { if (mClear) { if (!mMap.isEmpty()) { mcr.changesMade = true; mMap.clear(); } mClear = false; } for (Map.Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); // "this" is the magic value for a removal mutation. In addition, // setting a value to "null" for a given key is specified to be // equivalent to calling remove on that key. 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); //将本次修改增加的数据也加入到mMap中 } mcr.changesMade = true; if (hasListeners) { mcr.keysModified.add(k); } } mModified.clear(); } } return mcr; }</code></pre> <p>接着看下enqueueDiskWrite是如何将数据写入文件的:</p> <pre> <code class="language-java">private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) { final Runnable writeToDiskRunnable = new Runnable() { public void run() { synchronized (mWritingToDiskLock) { writeToFile(mcr); //将mcr写到文件,mcr保存着键值对 } synchronized (SharedPreferencesImpl.this) { mDiskWritesInFlight--; } if (postWriteRunnable != null) { postWriteRunnable.run(); } } }; 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) { wasEmpty = mDiskWritesInFlight == 1; } if (wasEmpty) { writeToDiskRunnable.run(); //commit走这里,在当前线程执行 return; } } QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); }</code></pre> <p>commit方法会直接调用 writeToDiskRunnable.run(),也就是说在当前线程进行写文件操作,我们看下writeToFile方法:</p> <pre> <code class="language-java">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 + " to backup file " + mBackupFile); 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); //使用XmlUtils.writeMapXml方法将内存中的键值对写入文件 FileUtils.sync(str); str.close(); ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); try { final StructStat stat = 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); }</code></pre> <p>这里主要关注两步,首先获取文件流,然后使用XmlUtils.writeMapXml将map写入文件。到这里,内存中的数据就写入到了文件。</p> <h2><strong>总结</strong></h2> <p>SharedPreference保存数据的形式是xml文件,并且创建时不同的name对应不同的xml文件。只有执行commit()操作才会将数据同步到文件,并且commit是同步的,会阻塞当前线程。想异步写入可以考虑apply方法。</p> <p>如果觉得写得还不错可以关注我哦,后面会将更多笔记的内容整理成博客。支持原创,转载请注明出处。</p> <p> </p> <p>来自:http://www.jianshu.com/p/1be4eb02f6a8</p> <p> </p>
本文由用户 yongheng 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!