SharedPreferences源碼之我見

說在前面:SharedPreferences是Android中幾種重要的存儲數據的方式,Android開發不會沒有人歷來沒有使用過,可是卻很好人會關注它是怎麼實現的,確實SharedPreferences實現起來比較簡單,本質是基於文件存儲,格式是XML形勢的,本篇文章並無太深的技術,但絕對是不可缺乏的知識。java

概述

SharedPreferences能夠經過Context獲取到,它是一個接口,實如今frameworksbasecorejavaandroidappSharedPreferencesImpl.javaandroid

源碼也僅僅只有630行,相對於龐大的Android源碼,小不少。可是你真的瞭解它嗎?緩存

問題

  • 一、不一樣的Activity裏面Context.getSharedPreferences()獲取的SharedPreferences相同嗎?多線程

  • 二、支持併發嗎?併發

  • 三、SharedPreferences.putXXX()直接保存了嗎?app

  • ......ide

針對上面的問題,咱們一點點來分析。函數

得到SharedPreferences

咱們得到SharedPreferences是經過Context.getSharedPreferences(),源碼以下:post

@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
  SharedPreferencesImpl sp;
  synchronized (ContextImpl.class) {
    // 靜態的 全局惟一,緩存獲得的SharedPreferences,key是app包名
    if (sSharedPrefs == null) {
          sSharedPrefs = new ArrayMap<String, ArrayMap<String, SharedPreferencesImpl>>();
    }

    // 經過每個app的包名緩存不一樣的SharedPreferences,key是文件名
    // 這裏就明白前面提到的第一個問題了,一個文件對應一個SharedPreferences
    final String packageName = getPackageName();
    ArrayMap<String, SharedPreferencesImpl> packagePrefs = sSharedPrefs.get(packageName);
    if (packagePrefs == null) {
      packagePrefs = new ArrayMap<String, SharedPreferencesImpl>();
      sSharedPrefs.put(packageName, packagePrefs);
    }
    
    // 略...
    // 若是以建立過,直接從緩存中獲取返回
    sp = packagePrefs.get(name);
    if (sp == null) {
      // 若是沒有建立一個 文件名爲data/data/packege/shared_prefs/xxx.xml
      File prefsFile = getSharedPrefsFile(name); 
      sp = new SharedPreferencesImpl(prefsFile, mode); // 建立實例對象
      packagePrefs.put(name, sp); // 放到緩存中
      return sp;
    }
  }
// 略...
  return sp;
}

建立SharedPreferences

SharedPreferences的建立很簡單,咱們先看看構造方法:ui

// 僅有一個
SharedPreferencesImpl(File file, int mode) {
  mFile = file;
  mBackupFile = makeBackupFile(file); // 建立備份
  mMode = mode;
  mLoaded = false;
  mMap = null; // 存放鍵值對的map
  startLoadFromDisk();  // 從本地load出來
}

從構造函數咱們不難看出,SharedPreferences實際是以Map(HashMap)存儲數據,建立的時候就從本地文件中讀取數據到內存中,因此,不能存儲太多的數據 ,不然內存會爆掉的哦。

讀取本地文件中的數據

當實例化的時候就會觸發,一個讀取本地已存儲的數據,

private void loadFromDiskLocked() {
 // 讀取過就不在讀取
 if (mLoaded) {
        return;
    }
    //...
    
    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);
                // 格式爲xml,解析xml獲得map
                map = XmlUtils.readMapXml(str);
            } 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) {
    }
    // 加載事後更改狀態,若是沒有建立一個HashMap
    mLoaded = true;
    if (map != null) {
        mMap = map;
        mStatTimestamp = stat.st_mtime;
        mStatSize = stat.st_size;
    } else {
        mMap = new HashMap<String, Object>();
    }
    notifyAll();
}

得到存儲的數據

獲得數據一般是getXXX(),實際很簡單,就是存map中取數據。

// 已getString()爲例,其餘同樣
@Nullable
public String getString(String key, @Nullable String defValue) {
    // 同步哦
    synchronized (this) {
        // 確保,已從文件中讀取過數據
        awaitLoadedLocked();
        // 從map中的獲得數據
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

保存數據

你們都知道保存數據是經過Editor來存儲的,事實上也確實是這樣的。可是putXXX()僅僅是保存到了map中,正真的是要在commit()或apply()以後。

public Editor putString(String key, @Nullable String value) {
    // 同步哦
    synchronized (this) {
            mModified.put(key, value);
            return this;
        }
    }

一、

public boolean commit() {
        MemoryCommitResult mcr = commitToMemory();
        // 寫數據
        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;
    }

二、

private void enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable) {
    final Runnable writeToDiskRunnable = new Runnable() {
            public void run() {
                synchronized (mWritingToDiskLock) {
                    // 往文件中寫入數據
                    writeToFile(mcr);
                }
                synchronized (SharedPreferencesImpl.this) {
                    mDiskWritesInFlight--;
                }
                if (postWriteRunnable != null) {
                    postWriteRunnable.run();
                }
            }
        };

      // ...

    QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}

三、

// Note: must hold mWritingToDiskLock
private void writeToFile(MemoryCommitResult mcr) {
   // ...
    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 = 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);
}

總結

一、SharedPreferences有緩存

二、數據是經過map來快速訪問,因此不用擔憂文件IO耗時

三、支持多線程併發

四、本質是基於文件xml的形式

相關文章
相關標籤/搜索