Android數據存儲原理分析

  Android上常見的數據存儲方式爲:android

  SharedPreferences是 Android 中比較經常使用的存儲方法,本篇將從源碼角度帶你們分析一下Android中經常使用的輕量級數據存儲工具SharedPreferences。緩存

  1.什麼是SharedPreferences?官方說法爲:安全

  它能夠用來存儲一些比較小的鍵值對集合;併發

  對於任何一類的preference,SharedPreferences是惟一的;app

  會影響到主線程,形成卡頓,甚至形成anr;異步

  SharedPreferences不支持多進程;ide

  2.SharedPreferences經常使用使用方法:函數

  1)將數據保存至SharedPreferences工具

  /*post

  *Context.MODE_PRIVATE: 默認操做模式,表明該文件是私有數據,只能被應用自己訪問, 在該模式下,寫入

  *的內容會覆蓋原文件的內容

  *Context.MODE_APPEND: 該模式會檢查文件是否存在,存在就往文件追加內容,不然就建立新文件

  *Context.MODE_WORLD_READABLE: 當前文件能夠被其餘應用讀取

  *Context.MODE_WORLD_WRITEABLE:當前文件能夠被其餘應用寫入

  */

  SharedPreferences preferences=getSharedPreferences("user",Context.MODE_PRIVATE);

  Editor editor=preferences.edit();

  String name="測試";

  editor.putString("name", name);

  editor.commit();

  2)從SharedPreferences讀取數據

  SharedPreferences preferences=getSharedPreferences("user", Context.MODE_PRIVATE);

  String name=preferences.getString("name", "123");

  3.1 獲取getSharedPreferences對象,作了哪些操做?

  如下節選至ContextImpl.getSharedPreferences源碼片斷:

  @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) {

  file = getSharedPreferencesPath(name);

  mSharedPrefsPaths.put(name, file);

  }

  }

  return getSharedPreferences(file, mode);

  }

  @Override

  public SharedPreferences getSharedPreferences(File file, int mode) {

  checkMode(mode);

  SharedPreferencesImpl sp;

  synchronized (ContextImpl.class) {

  final ArrayMap cache = getSharedPreferencesCacheLocked();

  sp = cache.get(file);

  if (sp == null) {

  sp = new SharedPreferencesImpl(file, mode);

  cache.put(file, sp);

  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.

  sp.startReloadIfChangedUnexpectedly();

  }

  return sp;

  }

  接着,咱們看 sp = new SharedPreferencesImpl(file, mode);

  如下節選至SharedPreferencesImpl源碼:

  SharedPreferencesImpl(File file, int mode) {

  mFile = file;

  mBackupFile = makeBackupFile(file);

  mMode = mode;

  mLoaded = false;

  mMap = null;

  startLoadFromDisk();

  }

  private void startLoadFromDisk() {

  synchronized (this) {

  mLoaded = false;

  }

  new Thread("SharedPreferencesImpl-load") {

  public void run() {

  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 {

  str = new BufferedInputStream(

  new FileInputStream(mFile), 16*1024);

  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;

  if (map != null) {

  mMap = map;

  mStatTimestamp = stat.st_mtime;

  mStatSize = stat.st_size;

  } else {

  mMap = new HashMap<>();

  }

  notifyAll();

  }

  }

  由以上可知SharedPreferences的流程爲:getSharedPerferences(String,int) ---> getSharedPerferences(File,int) ---> new SharedPerferencesImpl ---> startLoadFromDisk ---> new Thread ---> loadFromDisk ---> notifyAll ---> 返回一個SharedPerferencesImpl對象 ---> 獲取SharedPerferences成功

  3.2 putXxx方法解析:

  由以上可知咱們的寫操做首先須要經過sharedPreferences.edit()方法返回拿到SharedPreferences.Editor,咱們知道Editor是一個接口類,因此它的具體實現類是EditorImpl,如下爲部分方法片斷:

  3.3 getXxx方法解析:

  以getString爲例:

  @Nullable

  public String getString(String key, @Nullable String defValue) {

  synchronized (this) {

  awaitLoadedLocked();

  String v = (String)mMap.get(key);

  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();

  }

  while (!mLoaded) {

  try {

  wait();

  } catch (InterruptedException unused) {

  }

  }

  }

  由以上可知:

  由於使用了synchronize關鍵字,咱們知道getXxx方法是線程安全的

  getXxx方法是直接操做內存的,直接從內存中的mMap中根據傳入的key讀取value

  3.4 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(將mModified同步到mMap) ---> commitToMemory返回 ---> 調用enqueueDiskWrite --->異步任務放入線程池等待調度 ---> enqueueDiskWrite返回 ---> await等待喚醒 ---> 任務被線程池執行 ---> 喚醒await等待;

  3.5 apply() 解析:

  public void apply() {

  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);

  }

  };

  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);

  }

  // Returns true if any changes were made

  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(mMap);

  }無錫看男科醫院哪家好 https://yyk.familydoctor.com.cn/20612/

  mcr.mapToWriteToDisk = mMap;

  mDiskWritesInFlight++;

  boolean hasListeners = mListeners.size() > 0;

  if (hasListeners) {

  mcr.keysModified = new ArrayList();

  mcr.listeners =

  new HashSet(mListeners.keySet());

  }

  synchronized (this) {

  if (mClear) {

  if (!mMap.isEmpty()) {

  mcr.changesMade = true;

  mMap.clear();

  }

  mClear = false;

  }

  for (Map.Entry 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);

  }

  mcr.changesMade = true;

  if (hasListeners) {

  mcr.keysModified.add(k);

  }

  }

  mModified.clear();

  }

  }

  return mcr;

  }

  apply流程爲:調用commitToMemory(將mModified同步到mMap) ---> commitToMemory返回 ---> 調用enqueueDiskWrite --->異步任務放入線程池等待調度 ---> enqueueDiskWrite返回 ---> 任務被線程池執行(備份/寫入磁盤,清理備份,記錄時間/處理失敗狀況) ---> 任務完成;

  注意:apply與commit的區別爲:

  commit()方法是同步的,直接將偏好值(Preference)寫入磁盤;而apply()方法是異步的,會先把修改內容提交到SharedPreferences內容緩存中,而後開始異步存儲到磁盤;

  commit效率低,在多個併發的提交commit的時候,他們會等待正在處理的commit保存到磁盤後再進行下一步操做;而apply只是原子的提交到內容,後面有調用apply的函數的將會直接覆蓋前面的內存數據,這樣從必定程度上提升了不少效率。

  因爲apply方法會將等待寫入到文件系統的任務放在QueuedWork的等待完成隊列裏。因此若是咱們使用SharedPreference的apply方法, 雖然該方法能夠很快返回, 並在其它線程裏將鍵值對寫入到文件系統, 可是當Activity的onPause等方法被調用時,會等待寫入到文件系統的任務完成,因此若是寫入比較慢,主線程就會出現ANR問題。

  commit是在調用線程時就等待寫入任務完成,因此不會將等待的時間轉嫁到主線程;

  因爲在一個進程中,sharedPreference是單實例,通常不會出現併發衝突,若是對提交的結果不關心的話,建議使用apply,固然須要確保提交成功且有後續操做的話,仍是須要用commit的。

相關文章
相關標籤/搜索