SharedPreferences 部分源碼閱讀心得

之前一直沒有讀源碼的習慣,直到今年年初才開始慢慢養成多看源碼的習慣,不過之前在使用 SharedPreferences 時總覺得為啥要通過 Editor 去寫,而在獲取寫入的值的時候卻用 SharedPreferences 直接讀出來,這次看過源碼之后才知道是為什么。

說到 SharedPreferences,我們肯定想到的是它為我們提供簡單數據的存儲功能,因為 Android 本身支持多種類型的數據的持久化,比如文件、數據庫、SharedPreferences 等等,而如果只是存出一些簡單的數據類型,那么 SharedPreferences 是不錯的選擇,它的存儲形式是基于 xml 的,通過鍵值對的方式保存數據,而且數據可以是私有的。

當然 SharedPreferences 的使用很簡單,我們先獲取 SharedPreferences 實例,然后通過 Editor 保存和提交數據,而讀取的時候可以直接通過 SharedPreferences 的各種 getxxx 的方式讀取。

SharedPreferences 本身是接口,里面還有一個 Editor 的接口,它的實現類是 SharedPreferencesImpl,里面實現了 SharedPreferences 各種方法,還是實現了 Editor 接口。

先來說說 SharedPreferences 的獲取,有兩種方式:
1.通過 PreferenceManager 獲取。
2.通過 Context 中的 getSharedPreferences() 獲取。
其實第一種也只是封裝了一下 Context 中的 getSharedPreferences()。

這里有一點需要注意,我們應該減少 SharedPreferences 的大小,因為它本質是以 xml 存在本地,如果 SharedPreferences 數據量過大,那么初始化 SharedPreferences 時,會減慢讀取速度。

這次的源碼閱讀只閱讀了部分,幫我搞清了一些方法,但并沒有通讀 SharedPreferences 源碼,下面就來分享一下我的收獲。

先來說說 SharedPreferences 的存儲形式,我一開始只是認為 SharedPreferences 將值存入磁盤中,而其實它分為兩部分,首先它會先存在內存中一次,然后再提交到磁盤中。

存到內存中的部分.png

截圖中的 mMap 的作用有兩個,一個是初始化的時候把磁盤中讀到的數據賦值給它,然后我們再次提交的新提交的部分時,也會直接存到這里,這也就是先存到內存中一次。

在 SharedPreferences 構造中,會調用 startLoadFromDisk(),這個方法會新起一個線程,執行 loadFromDisk(),這個方法就是讀取磁盤中 xml 文件,然后將讀取到的值賦給 mMap。

那么接下來來看看我們在 Editor 中提交的時候,代碼都做了什么。

SharedPreferencesImpl 有一個內部類 EditorImpl,它實現了 SharedPreferences 中的定義的 Editor 接口,然后當我們獲取 Editor 時,就是得到了一個 EditorImpl 實例。

EditorImpl 中的 mModified 是用來存儲本次想要提交的內容,各種 putxxx 這里就不在多說了,看看兩個重要的提交方式,一個是 commit(),另一個是 apply()。

EditorImpl.png

官方文檔中也明確的說明了這兩種方式的明確區別,除了返回值,還有提交到磁盤的方式,commit() 是同步,而 apply() 是異步。

但無論提交到磁盤的方式如何,它們都會首先將本次想要提交的內容通過 commitToMemory() 存到內存中,也就是存到 SharedPreferencesImpl 的 mMap 中。通過 MemoryCommitResult 拿到提交內容的內存的結果,然后再將拿到的結果寫入磁盤。
final MemoryCommitResult mcr = commitToMemory();

寫到磁盤時,都是通過調用 SharedPreferencesImpl 的 enqueueDiskWrite() 來完成的,apply() 調用 enqueueDiskWrite() 時會傳入一個實例化的 Runnable,而 commit() 則傳 null 值過去,enqueueDiskWrite() 通過傳遞過來 Runnable 是否為 null,來判斷是否以什么方式提交。

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

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

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

這就是 enqueueDiskWrite() 的源碼,如果傳遞過來的 postWriteRunnable 為 null 則說明同步寫入到磁盤中,而非 null 時利用單線程的線程池來將結果寫入到磁盤中。

至此一個基本上寫入的流程就完成了。

那些讀的時候就直接調用 SharedPreferencesImpl 中的 getxxx 就可以了,而這些方法會線程安全的從 mMap 中拿到我們需要的值。

讀取時.png

SharedPreferences 源碼基本上就讀到了這里,基本的一些操作也都梳理清楚,接下來說幾點使用時需要注意的地方。

1.單個 SharedPreferences 文件不宜過大,所以最好不使用默認的存儲目錄,而是根據需求,自己定義存儲的文件名,如果單個 xml 文件過大,那么初始化時會影響讀取速度。
2.SharedPreferences 是進程內單例,當然它也可以讀到別人進程寫入的內容,但是會有些小問題,所以跨進程使用 SharedPreferences 還有很多需要注意的地方。
3.提交時如果不需要返回結果(是否提交成功),那么直接調用 commit()。
4.單次提交內容不易過大過多,那么同步可能會阻塞,雖然也可以異步,但是異步其實也只是單線程。
5.它會同時存在磁盤中,也會存在內存中。

所以是否會存在這種問題?內容不足時,回收了 mMap,下次使用時之前的存過的值會返回 null 或者默認值了呢?
如果真的會的話就要重新再去實例化一個 SharedPreferences 來使用了。

以及內容就是本次閱讀心得,僅供參考,如果問題,可以留言溝通。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容