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