SharedPreference源码分析

yongheng 8年前
   <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>