Android SharedPreferences最佳實踐

       Android開發中,咱們常常會用到SharedPreferences,它是一種輕量的數據存儲方式,一般用來存儲一些簡單的配置信息。看了網絡上的一些文章,感受都不是特別滿意,所以但願能結合本身的經驗和理解寫一篇分析SharedPreferences的文章。本文不會講解SharedPreferences的基本用法,而是會結合源碼來分析SharedPreferences的工做原理,以及使用中存在的一些問題。緩存

       經過這篇文章,你能夠了解到:安全

  • SharedPreferences是怎麼工做的微信

  • SharedPreferences使用中有哪些坑網絡

  • 怎麼來避免SharedPreferences的那些問題app

      首先,咱們要搞清楚SharedPreferences的本質是什麼。它的本質是基於xml文件存儲的key-value鍵值對數據,其存儲位置在/data/data/包名/shared_prefs目錄下。因爲它是存儲在應用程序的私有目錄下,外部是沒法直接訪問的。也就是說它實際上就是一個xml文件,和普通的xml沒有本質區別,內容也和咱們工程代碼裏的strings.xml文件的內容相似。異步

源碼分析

      下面咱們經過對源碼的分析,講解一下它的工做原理。先來看一下SharePreferences的基本用法。源碼分析

SharedPreferences sp = context.getSharedPreferences(「file1」, Context.MODE_PRIVATE);post

sp.edit().putBoolean(「key1」, false).commit();線程

sp.getBoolean(「key1」)orm

      那麼咱們就從getSharedPreferences()方法開始講起,實際上Context最終調用的是ContextImpl中的getSharedPreferences方法,咱們看下這個方法。

      其中包含一個mSharedPrefsPaths對象,它是ArrayMap類型,咱們能夠在App中建立多個sp文件,mSharedPrefsPaths中就是存儲了不一樣sp文件名和sp文件的對應關係。這裏的getSharedPreferencesPath方法實際上就是在磁盤上建立了一個xml文件。查看上圖最後一行的getSharedPreferences方法。

      咱們看到這個方法實際返回了一個SharedPreferencesImpl對象,看下SharedPreferencesImpl的構造方法。

      其中調用了startLoadFromDisk方法,startLoadFromDisk在子線程裏調用了loadFromDisk,執行線程以前將mLoaded設置爲false,再來看下這個loadFromDisk方法。

      這個方法的代碼不少,咱們只看最核心的部分,它經過XmlUtils.readMapXml()將文件讀取到mMap中,mMap是一個HashMap,而且將mLoaded設置爲true,你們記住mLoaded這個變量,後面還會遇到它。也就是說,sp文件的內容被讀取到內存而且緩存到mMap中了,後續對sp的操做都與內存中的緩存有關。既然sp文件的內容會緩存到內存中,若是文件中存儲了大量數據,就會佔用很大的內存空間,這點須要特別注意。

      SharedPreferences的建立過程講完了,下面咱們來看一下put過程。put操做首先要調用edit()方法,

      又見到了mLoaded這個變量,咱們回憶一下以前的邏輯,在開始開啓線程讀取sp文件到內存的時候,這個變量被置爲false,等線程執行完會置爲true,在上圖的awaitLoadedLocked方法中,若是發現mLoaded爲false,則調用wait方法,此時會阻塞當前線程,直到sp文件讀取完成,才調用notifyAll()通知這裏被阻塞的線程繼續執行。也就是說,若是讀取sp文件的操做執行時間很長的話,這裏就可能會阻塞主線程致使ANR。

      怎麼才能儘量的避免這個問題呢?首先,咱們須要將sp文件根據功能和特色分解爲多個小文件,好比根據不一樣的功能模塊進行劃分,或者根據讀寫的頻率,也能夠根據是否App啓動的時候就須要加載。若是每一個文件足夠小,那麼在讀取文件到內存的時候,耗時天然也就少了。尤爲是在App啓動的時候,只須要加載啓動時須要的sp配置,能夠必定程度上減小啓動時間。

      下面繼續看源碼。edit()方法返回了一個Editor對象,實際的類型是EditorImpl。

      EditorImpl中包含一個HashMap類型成員mModified,調用Editor的方法如putString以後,都只是將數據存儲在mModified中。這裏只是數據的暫存區,所以若是忘記調用commit或者apply方法,數據其實並無寫入磁盤。有一點須要注意的是咱們每次調用edit方法,都會建立一個mModified對象,所以,有必要減小edit方法的調用。

      最後,就是調用commit或者apply方法了。咱們知道commit是同步寫入,會返回執行結果;而apply方法是異步寫入,並不會返回執行結果。下面經過源碼來分析下它們的實現。

      commit方法中前後調用了commitToMemory和enqueueDiskWrite。commitToMemory方法的做用是將前面提到的mModified中緩存的數據更新到前面提到的mMap中,這個mMap會被最終寫入文件。咱們看enqueueDiskWrite方法,它的第二個參數傳了null,所以,isFromSyncCommit爲true,而後直接執行了writeToDiskRunnable.run()方法,其中經過調用writeToFile將mMap中的配置內容寫入sp文件。

      從源碼中咱們能夠看出,commit的執行是同步的,並且是全量的寫入。若是不是必要的狀況,儘可能不要使用commit去保存sp的配置,以防止寫文件阻塞主線程。

      咱們再來看apply方法的實現有什麼不一樣。

      這裏所不一樣的是enqueueDiskWrite的第二個參數不爲null,因此方法內部將寫入文件的操做放入了單線程的線程池異步執行:

      QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable)。

      因爲是單線程,來不及執行的Runnable都被放在隊列中等待執行 。writeToDiskRunnable裏面執行了writeToFile將sp寫入文件,而後調用了postWriteRunnable的run()方法,這裏面又調用了awaitCommit的run()方法,最後調用了mcr.writtenToDiskLatch.await()。那麼這個writtenToDiskLatch又有什麼做用呢?經過代碼,咱們發現writeToFile方法裏面最終會調用writtenToDiskLatch的countDown方法,也就是說,若是sp文件的寫入一直沒有執行完,writtenToDiskLatch.await()這個調用就會阻塞在這裏,但從實際的執行時序上來看writtenToDiskLatch的countDown的調用又確定是在await以前的,那麼這個await的調用到底有什麼做用呢?咱們又注意到,這裏有一行代碼:QueuedWork.add(awaitCommit)。 咱們看下這個QueuedWork是什麼?

      圖中略去了部分代碼,add方法實際上就是將runnable加入到一個ConcurrentLinkedQueue中。下面的waitToFinish方法裏會去遍歷queue中的每一個Runnable,並執行它的run方法。那麼waitToFinish方法又是在哪裏調用的呢?咱們根據註釋找到了ActivityThread類的handlePauseActivity、handleStopActivity方法,咱們來看其中的一個。

      咱們看到,在Activity調用onStop的時候,會調用QueuedWork.waitToFinish(),遍歷執行其中的runnable。假設咱們頻繁的調用了apply方法,並緊接着調用了onStop,那麼就可能會發生onStop一直等待QueuedWork.waitToFinish執行完成而產生ANR。也就是說,即便是調用了apply方法去異步提交,也不是徹底安全的。若是apply方法使用不當,也許會遇到與下圖相似的問題。

      上面講了put操做,因爲get操做相對簡單一些,這裏就不單獨分析了。

總結

      從上面的分析咱們發現SharedPreferences的使用並非那麼簡單的,使用不當可能會致使程序異常,咱們對上面提到的一些問題進行一下總結:

  • sp配置不要所有都寫在一個文件中,這樣不只第一次加載會很慢,也會佔用大量內存。最好是根據必定規則分紅多個sp文件。好比頻繁和不頻繁寫入的配置就分別存儲在兩個不一樣的文件中。

  • sp文件的寫入是全量寫入,即便改了一條配置,寫入的時候也會對整個文件進行操做,所以最好能批量操做,不要每次都commit。

  • 啓動的時候須要讀取sp的配置最好異步進行,若是必定要同步讀取,啓動的sp文件要儘量的小。

  • 不要將太大的配置項(包括key和value)存儲在sp中,不然會佔用大量內存。

  • 獲取SharedPreferences對象的時候會讀取sp文件,若是文件沒有讀取完,就執行了get和put操做,可能會出現須要等待的狀況,所以最好提早獲取SharedPreferences對象。

  • 每次調用edit方法都會建立一個新的EditorImpl對象,不要頻繁調用edit方法。

  • apply方法雖然是在線程中異步將配置寫入文件,可是若是任務不少,並且每一個任務執行時間很長,也可能會致使Activity或Service在stop的時候出現ANR。

歡迎關注個人微信公衆號,收到最新的推送文章

相關文章
相關標籤/搜索