Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
SharedPreferences 原理 源碼分析 進程間通訊 MDandroid
目錄
SharedPreferences
簡介
在Android中, SharePreferences是一個輕量級
的存儲類,特別適合用於保存軟件配置參數
。使用SharedPreferences保存數據,其背後是用xml
文件存放數據,文件 存放在/data/data/ <package name> /shared_prefs
目錄下.git
之因此說SharedPreference是一種輕量級的存儲方式,是由於它在建立的時候會一次性把整個文件所有加載進內存
,若是SharedPreference文件比較大,會帶來如下問題:github
- 第一次從sp中獲取值的時候,有可能阻塞主線程,使界面卡頓、掉幀。
- 解析sp的時候會產生大量的臨時對象,致使頻繁GC,引發界面卡頓。
- 這些key和value會永遠存在於內存之中,佔用大量內存。
優化建議數據庫
- 不要存放大的key和value,會引發界面卡、頻繁GC、佔用內存等等。
- 絕不相關的配置項就不要放在在一塊兒,文件越大讀取越慢。
- 讀取頻繁的key和不易變更的key儘可能不要放在一塊兒,影響速度。
- 不要亂edit和apply,儘可能批量修改一次提交,屢次apply會阻塞主線程。
- 儘可能不要存放JSON和HTML,這種場景請直接使用JSON。
- SharePreferences的commit與apply一個是同步一個是異步
SharedPreference沒法進行跨進程通訊
跨進程通訊時的問題
問題
先啓動主線程並獲取SharedPreferences對象,而後對值進行修改,而後再啓動其它進程並獲取SharedPreferences對象,可以獲取修改後的值,但此時若是對此值進行修改,不能對其餘進程產生做用
,必須等到進程重啓或者app重啓才能與其餘進程進行數據同步。緩存
緣由
只有在建立SharedPreferences對象的時候纔會從磁盤中進行讀取,讀取完之後值保存在內存(HashMap)當中,下次獲取SharedPreferences對象優先從緩存當中獲取,因此在當前進程修改了SharedPreferences的值,其餘進程的SharedPreferences對象的值並不會改變。只有把當前另外的進程關閉(如:關閉手機、或殺死該app從新進入),再次建立進程時纔會從新從磁盤中再次讀取文件。安全
源碼分析
一般咱們獲取SharedPreferences都是經過Context中的getSharedPreference方法來獲取SharedPreferences對象,在Context中,getSharedPreference方法是一個抽象方法,沒有具體實現:微信
public abstract SharedPreferences getSharedPreferences(String name, @PreferencesMode int mode);
檢索並保存首選項文件「name」的內容,返回SharedPreferences,您能夠經過它來檢索和修改其值。只有一個SharedPreferences對象實例返回給任何相同名稱的調用者,這意味着他們一旦完成就會看到彼此的編輯。併發
Retrieve and hold the contents of the preferences file 'name', returning a SharedPreferences through which you can retrieve and modify its values. Only
one instance
of the SharedPreferences object is returned to any callers for the same name, meaning they will see each other's edits as soon as they are made.app
咱們知道Context的實現類是ContextImpl,因此直接找到ContextImpl的getSharedPreference方法。異步
@Override public SharedPreferences getSharedPreferences(String name, int mode) { File file; synchronized (ContextImpl.class) { if (mSharedPrefsPaths == null) { mSharedPrefsPaths = new ArrayMap<>(); //class ArrayMap implements Map } file = mSharedPrefsPaths.get(name); //根據 name 獲取對應的文件 if (file == null) { //首次訪問可能連文件都不存在,那麼還須要建立 xml 文件 file = getSharedPreferencesPath(name); //建立文件,文件名爲【name + ".xml"】 mSharedPrefsPaths.put(name, file); //保存起來 } } return getSharedPreferences(file, mode); }
@Override public SharedPreferences getSharedPreferences(File file, int mode) { SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); //獲取緩存對象 if (sp == null) { //若是內存中不存在,會建立SharedPreferencesImpl checkMode(mode); sp = new SharedPreferencesImpl(file, mode); //建立SharedPreferences的實例對象 cache.put(file, sp); //緩存起來 return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { //11 //If somebody else (some other process) changed the prefs file behind our back, we reload it. //This has been the historical (if ndocumented) behavior. //若是其餘人(其餘進程)在咱們後面更改了prefs文件,咱們會從新加載它。這是歷史(若是沒有文檔記錄)的行爲。 sp.startReloadIfChangedUnexpectedly(); } return sp; //若是內存中已經存在,那麼直接返回 }
能夠看到,這裏將SharedPreferences的實例對象SharedPreferencesImpl的先經過Map緩存起來,之後每次獲取若是內存已經存在,那麼直接返回,若是不存在纔會從新建立。
持久化數據的更新
一般更新SharedPreferences的時候是首先獲取一個SharedPreferences.Editor
,利用它緩存一批操做,以後當作事務提交,有點相似於數據庫的批量更新。
Editor是一個接口,這裏的實現是一個EditorImpl
對象,它首先批量預處理更新操做,以後再提交更新。在提交事務的時候有兩種方式,一種是apply,另外一種commit,二者的區別在於:前者是異步的,後者是同步的。Google推薦使用前一種,由於,就單進程而言,只要保證內存緩存正確就能保證運行時數據的正確性,而持久化,沒必要太及時,這種手段在Android中使用仍是很常見的,好比權限的更新也是這樣,何況,Google並不但願SharePreferences用於多進程,由於不安全。
不管調用哪個方法都會調用 commitToMemory 和 enqueueDiskWrite 方法。commitToMemory 方法就是將值提交到內存當中,enqueueDiskWrite 將修改後的內容寫入到磁盤當中,因此下一次取出的值是正確的。
@Override public void apply() { final MemoryCommitResult mcr = commitToMemory(); //提交到內存當中 final Runnable awaitCommit = new Runnable() { @Override public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; QueuedWork.addFinisher(awaitCommit); Runnable postWriteRunnable = new Runnable() { //提交一個事務到已給線程池,以後直接返回 @Override public void run() { awaitCommit.run(); QueuedWork.removeFinisher(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); }
MODE_MULTI_PROCESS 標記
結論
MODE_MULTI_PROCESS
只是保證了在 API 11 之前的系統上,若是sp已經被讀取進內存,再次獲取這個SharedPreference的時候,若是有這個flag,會從新讀一遍文件,僅此而已,並不能保證跨進程通訊。
解釋
SharePreferences在新建時有個mode參數,能夠指定它的加載模式,MODE_MULTI_PROCESS
是Google提供的一個多進程模式,可是這種模式並非咱們說的支持多進程同步更新等,它的做用只會在getSharedPreferences
的時候,纔會從新從xml重加載,若是咱們在一個進程中更新xml,可是沒有通知另外一個進程,那麼另外一個進程的SharePreferences是不會自動更新的。
@Override public SharedPreferences getSharedPreferences(File file, int mode) { //... if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { //11 //If somebody else (some other process) changed the prefs file behind our back, we reload it. //This has been the historical (if ndocumented) behavior. //若是其餘人(其餘進程)在咱們後面更改了prefs文件,咱們會從新加載它。這是歷史(若是沒有文檔記錄)的行爲。 sp.startReloadIfChangedUnexpectedly(); } return sp; //若是內存中已經存在,那麼直接返回 }
也就是說MODE_MULTI_PROCESS
只是個雞肋Flag,對於多進程的支持幾乎爲0,下面是Google文檔,簡而言之,就是:不要用。
文檔描述
@Deprecated public static final int MODE_MULTI_PROCESS = 0x0004;
SharedPreference loading標誌:設置後,即便已在此過程當中加載了共享首選項實例,也會檢查磁盤上的文件是否已修改。 在應用程序具備多個進程的狀況下,有時須要此行爲,全部進程都寫入相同的SharedPreferences文件。 可是,一般在進程之間存在更好的通訊形式。
SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.
這是Gingerbread(Android 2.3)以前的遺留(但未記錄)行爲,而且在針對此類版本時隱含了此標誌。 對於針對SDK版本大於 Android 2.3的應用程序,若是須要,必須明確設置此標誌。
This was the legacy (but undocumented) behavior in and before Gingerbread (Android 2.3) and this flag is implied when targetting such releases. For applications targetting SDK versions greater than Android 2.3, this flag must be explicitly set if desired.
MODE_MULTI_PROCESS
在某些Android版本中沒法可靠地工做,並且沒有提供任何協調跨進程的併發修改的機制。 應用程序不該嘗試使用它。 相反,他們應該使用明確的跨進程數據管理方法,例如ContentProvider。
MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as {@link android.content.ContentProvider ContentProvider}.
2019-2-20