Android---SharedPreferences解析

SharedPreferences真正實現的類是:SharedPreferencesImpl

構造函數中:
會創建出該XML的文件,以及一個bak的備份文件。并且調用startLoadFromDisk開啟一個線程從本地文件中讀取配置文件。

其中每個SharedPreferencesImpl中都會有一個mLoaded變量,它標志著是否已經從本地文件中讀取過了。而對它進行同步的時候,都是通過SharedPreferencesImpl.this來上鎖進行的。

在loadFromDiskLocked函數中:

  1. 檢測.bak備份文件是否存在,如果存在的話,那么則將原來的文件刪除,然后將.bak文件renameto正常文件,也就意味著,如果在寫的時候,出問題了,導致中斷了,就使用原來沒問題的備份文件。
  2. 接著會通過Native檢查文件是否存在,并且進行權限檢查,看該文件是否可讀
  3. 通過XmlUtils.readMapXml讀取對應的XML文件,然后將數據放到Map中保存
  4. 讀取完后,將mLoaded變量設置成true,標識已經讀取完畢,并且調用notifyAll,讓其他線程被喚醒

而在getStringgetInt等函數中,都會調用:

public String getString(String key, String defValue) {
    synchronized (this) {
        awaitLoadedLocked();
        String v = (String)mMap.get(key);
        return v != null ? v : defValue;
    }
}

首先調用awaitLoadedLocked方法,該函數體是一個while循環,一直等待mLoaded被設置成true,否則無論當前線程是否拋出了InterruptedException,都會一直處于wait狀態。所以,如果SharedPreferences太大的話,讀取的時間會越來越長,如果在主線程調用了getString等方法的話,會等待子線程把數據讀取完之后才會返回值,建議不要讓一個XML太大,可以分多個XML存儲,這樣不會有ANR的風險。

讀取的過程比較簡單,而寫入的過程會非常復雜,因為需要考慮多線程,多進程,什么時候寫入,同時寫入等等非常規情況的處理。

等待讀取完成后,創建出一個EditorImpl對象,該對象中只有一個HashMap用來保存變更的Key-Value,并且可以調用clear方法將mClear設置成true

調用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);
    }

從apply函數中可以看到:

  1. 調用commitToMemory方法
    a) 創建一個MemoryCommitResult對象
    b) 判斷mDiskWritesInFlight是否大于0,如果大于0的話,說明之前已經有正在往磁盤里寫的任務了,那么則將改變的值copy到一個新的Map中
    c) 將map放入MemoryCommitResult,并且將mDiskWritesInFlight加1
    d) 判斷當前SharedPreferences是否已經注冊過Listener,如果注冊過的話,那么將Listener放到MemoryCommitResult中,以便后續的回調使用
    e) 判斷mClear是否被設置,如果被設置的話,那么就會將當前SharedPreferencesImpl中的Map清空,并將結果的changesMade設置成true,標識在內存中值已經發生改變
    f) 遍歷改變的Map對象,判斷要改變的值與當前值是否相同,不同的話,則改變當前值。并將結果的changesMade設置成true,標識在內存中值已經發生改變
    g) 判斷是否有Listener,如果有,則將有改變的Key添加到MemoryCommitResult中的keysModified這個Set中,最后將EditorImpl中的Map給清空

  2. 獲取到了內存改變的值之后,會創建awaitCommit以及postWriteRunnable兩個Runnable:
    awaitCommit:該Runnable中執行CountDownlaunch的await方法進行等待,并且會被添加到QueueWork中
    postWriteRunnable:該Runnable執行awaitCommit的run方法,并且將awaitCommit從QueueWork中移除

  3. 調用enqueueDiskWrite方法,開始往寫磁盤隊列中插入。
    a) 創建一個writeToDiskRunnable對象,該Runnable中完成將MemoryCommitResult寫入文件的操作
    b) 判斷postWriteRunnable是否為空,該判斷主要是用來判斷當前的操作是commit還是apply,因為commit的話,是一個同步操作,需要等待寫完磁盤之后,才能繼續往下運行,而apply是個異步操作,當異步寫完磁盤之后,會調用postWriteRunnable.run方法。
    c) 如果是調用commit的話,則判斷mDiskWritesInFlight是否為1,如果為1的話,那么就說明當前沒有寫磁盤任務,那么就直接調用writeToDiskRunnable.run方法,執行完之后返回,否則將寫入文件的操作放到一個單線程池中慢慢執行。

writeToFile中,會將每一個MemoryCommitResult都寫到文件中

  1. 判斷XML文件是否存在,如果存在的話,那么判斷當前內存值是否有改變,如果沒有改變的話,就調用setDiskWriteResult方法,將之前的CountDownLaunch減一,讓原來等待的線程處于就緒狀態,并且將寫入成功的標志位設置成true,標識寫入成功
  2. 判斷.bak文件是否存在,如果不存在的話,那么則將xml文件renameTo備份文件,如果備份失敗的話,那么則標記寫入失敗,返回。如果bak文件存在的話,那么則將原來的XML文件刪除
  3. 得到XML對應的FileOutputStream,如果獲取失敗的話(如無權限,創建文件失敗等等),則標記寫入失敗并且返回
  4. 將完整的Map對象寫入XML中,并且調用FileUtils.sync進行文件同步,同步完之后,關閉輸出流,并且設置文件權限
  5. 寫入成功后,刪除.bak文件,并且標記寫入文件成功,并且返回,如果寫文件過程中遇到異常,則直接將XML文件刪除,并且標記寫入失敗。

在寫完文件之后,將mDiskWritesInFlight減一,并且判斷postWriteRunnable是否存在,如果有的話,則執行postWriteRunnable。

最后沒有搞明白為什么要有個CountDownLaunch,因為CountDownLaunch.await是在最后被執行的,而那時候早就已經調用了CountDownLaunch.countdown了,所以貌似沒什么用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,117評論 25 708
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,759評論 18 399
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,360評論 11 349
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,881評論 18 139
  • 想要對比一下《肖申克的救贖》(斯蒂芬·金中篇小說改編而成)里的主人公安迪與《面紗》(毛姆同名小說改編而成)里的主人...
    童九莉閱讀 845評論 1 3