SharedPreferences(后續簡稱SP)為我們提供了輕量級存儲能力,方便了少量數據的持久化。
但是由於項目越來越龐大,SP操作使用不當會導致app卡頓,乃至ANR問題。
下面介紹一下操作SP的優化點。
SP性能優化點
SP性能變差的原因有很多。
1.原生API的限制主要有以下兩方面:
(1)IO瓶頸
(2)鎖性能差
2.對SP的不當封裝也會間接造成數據讀寫性能差。
下面會對以上三方面進行分析。
IO瓶頸
IO瓶頸造成SP性能差是最大的原因,解決了IO瓶頸,80%的性能問題就解決了。
SP的IO瓶頸包括讀取數據到內存與數據寫入磁盤兩部分。
1.讀取數據到內存有兩個場景會觸發:
(1)SP文件沒有被加載到內存時,調用getSharedPreferences方法會初始化文件並讀入內存。
(2)版本低於android_H或使用了MULTI_PROCESS標志時,每次調用getSharedPreferences方法時都會讀入。
我們可以優化的便是(2)了。每次加載數據到內存太過影響效率。
H以下版本留存率已經很低了,基本可以忽略。
對於MULTI_PROCESS,可以采用ContentProvider等其他方式,效率更好,而且可避免SP數據丟失的情況。
2.數據寫入磁盤也有兩個場景會觸發:
(1)Editor的commit方法,每次執行時同步寫入磁盤。
(2)Editor的apply方法,每次執行時在單線程池中加入寫入磁盤Task,異步寫入。
commit和apply的方法區別在於同步寫入和異步寫入,以及是否需要返回值。
在不需要返回值的情況下,使用apply方法可以極大的提高性能。
同時,多個寫入操作可以合並為一個commit/apply,將多個寫入操作合並后也能提高IO性能。
鎖性能差
SP的get操作,會鎖定SharedPreferences對象,互斥其他操作。
SP的put操作,getEditor及commitToMemory會鎖定SharedPreferences對象,put操作會鎖定Editor對象,寫入磁盤更會鎖定一個寫入鎖。
由於鎖的緣故,SP操作並發時,耗時會徒增。減少鎖耗時,是另一個優化點。
由於讀寫操作的鎖均是針對SP實例對象的,將數據拆分到不同的sp文件中,便是減少鎖耗時的直接方案。
降低單文件訪問頻率,多文件均攤訪問,以減少鎖耗時。
用開發機進行了簡單的性能測試(寫入均使用apply,若使用commit則多線程耗時更高):
讀寫同一文件,10個線程每個讀寫10次數據:
耗時80-130ms
讀寫10個文件,每個文件由1個線程讀寫10次數據:
耗時30-70ms
對SP操作的不當封裝
由於我們項目采用了插件化,所以對SP的操作涉及到了跨進程訪問。
我們采用ContentProvider方案支持跨進程訪問,並對所有SP操作均套上了ContentProvider進行訪問。
隨着項目越來越龐大,通過ContentProvider訪問造成的耗時性能也成了問題。
對ContentProvider操作SP測試,耗時是直接操作SP的4倍左右。
所以,最近項目中進行了SP的處理,對於不需要跨進程的SP操作去掉了ContentProvider,盡可能減少無謂耗時。
SP優化的建議
1.盡量不要直接調用SharedPreferences進行讀寫操作。
若直接調用getSharedPreferences(fileName,mode).edit().putString(key,value),則對數據的操作直接耦合了fileName和key,后續想調整file和key會比較困難。
可以考慮封裝一下,譬如:
public void saveUserId(){
getSharedPreferences(fileName,mode).edit().putString(“user_id”,value);
}
這樣做可以直接對數據訪問,而與fileName與key解耦,后續拆分與調整時會很方便。
2.將SP作為耗時操作對待,盡量減少無謂的調用。
譬如以下代碼,SP讀一次即可:
if(sp.getUserId()>0){
int id=sp.getUserId();
...
}
