Android系統每隔16ms發出VSYNC信號,對UI進行渲染,若是每次渲染都成功,就可以達到流暢的畫面所須要的60fps,爲了可以實現60fps,這意味着程序的大多數操做都必須在16ms內完成,時間超出16ms越多,丟的幀就越多。java
假設咱們更新屏幕的背景圖片,須要24ms來作此次運算。當系統在第一個16ms時刷新界面,然而咱們的運算尚未結束,沒法繪出圖片。當系統隔16ms再發一次VSYNC信息重繪界面時,用戶纔會看到更新後的圖片。也就是說用戶是32ms後看到了此次刷新(注意,並非24ms),這就是丟幀。git
大多數多用感知到卡頓等問題最主要的根源是渲染問題,而致使渲染問題的緣由是性能問題,爲了保證程序正常的使用,性能方面須要着重注意,本篇針對的性能優化是從一些平時常見的細節入手,github
丟幀只是用戶能感知到的表面現象,嚴重的會引發程序卡頓甚至ANR,深層次的緣由是代碼中有比較耗時的操做阻塞到了主線程,也就是性能問題。緩存
過分繪製(Overdraw)是指屏幕上同一個像素點在同一幀時間內被繪製屢次。在層級複雜的UI結構中,若是不可見的UI也被繪製,會致使某些像素區被繪製屢次,浪費大量的CPU以及GPU資源。性能優化
在手機的開發者選項中,打開顯示佈局邊界便可查看頁面的繪製信息。網絡
有四中顏色,藍色,淡綠,淡紅,深紅分別表明着不一樣的繪製信息。藍色表明一次繪製,淡綠表明兩次繪製,淡紅表明三次繪製,深紅表明四及以上次繪製。架構
咱們的目的是儘可能減小紅色的繪製信息,方法有兩種。app
簡化頁面UI結構,複雜的UI佈局會致使大量View重疊,出現過分繪製的可能性比較大,要避免佈局嵌套過多,例如通常狀況下,優先使用LinearLayout佈局。
複用背景色,例如若是父佈局和子View背景色是相同的,只須要父佈局設置背景色便可,子View不用設置。框架
上面提到簡化佈局能夠減小過分繪製的問題,佈局優化能夠從下面幾方面入手。異步
*佈局的選擇,能知足需求的狀況下優先選擇LinearLayout,由於RelativeLayout在measure會比LinearLayout多出一次,
即便LinearLayout使用了weight後,性能依然會比RelativeLayout好
*邊距的設置,RelativeLayout的measure過程當中,若是出現子View和佈局自己高度不一樣時候,還會觸發measure過程,解決方法很簡單,使用padding代替marigin
*複用佈局,include
*延遲加載,ViewStub
*合併佈局層級,merge
關於這塊的具體詳情,參考下面連接:
這裏I/O操做僅針對SharedPreferences,由於這個基本是在Android中使用最多的儲存庫了。
讀操做通常不會阻塞到主線程,若是讀取的數據比較大並且須要有大量的處理操做,直接開子線程,在子線程處理。
寫操做比讀操做複雜一些,向SharedPreferences寫入數據時候,有commit和apply兩個方法。
commit方法,直接將數據同步寫入磁盤
apply方法,先將數據寫入內存,再異步寫入磁盤
commit和apply方法區別在於同步寫入和異步寫入,以及是否須要返回值。在不須要返回值的狀況下,使用apply方法能夠極大的提升性能。
在使用lint靜態掃描代碼時候,會建議使用apply去替代commit,說明官方也是支持使用apply方法的。
可是!!!
並非是說apply不會阻塞到主線程,它也有可能會阻塞到主線程。詳情請看這篇文章SharedPreferences調用致使的ANR分析。
長話短說。
使用apply方法時候,會向QueuedWork隊列中添加一個等待寫入操做完成的線程,只有當寫入操做完成後,纔會從QueuedWork將等待線程線程移除掉。
而主線程中,Service的啓動和stop以及Activity的onPause()和onStop()生命週期都會等待其餘異步線程完成,纔會繼續執行。
例如中止Service的時候代碼以下(ActivityThread類中):
private void handleStopService(IBinder token) {
···
QueuedWork.waitToFinish(); // 等待其餘異步線程完成
···
}
複製代碼
能夠看到,若是使用apply方法寫入大量複雜數據,確實有可能會阻塞到主線程,甚至可能致使ANR。
*複雜數據
*即時性較弱(距離下次使用時間較長),新建子線程使用commit方法,防止阻塞到主線程
*即時性較強,能夠考慮直接放在內存中
*簡單數據,使用apply方法
大部分狀況下,與後臺交互使用的數據是gson格式。相信不少是直接使用google官方的Gson類來序列化和反序列化的。
我在項目中就碰見了有使用Gson反序列化複雜數據時候,形成的卡頓現象。
Gson序列化和反序列化是能夠優化的。
stackoverflow,問題/答案,stackoverflow.com/questions/1…
序列化方法對比,github.com/eishay/jvm-…
Gson序列話優化,sites.google.com/site/gson/s…
使用流配合Gson序列化,性能能提升25%
// 反序列化
public List<Message> readJsonStream(InputStream in) throws IOException {
JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
List<Message> messages = new ArrayList<Message>();
reader.beginArray();
while (reader.hasNext()) {
Message message = gson.fromJson(reader, Message.class);
messages.add(message);
}
reader.endArray();
reader.close();
return messages;
}
// 序列化
public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
writer.setIndent(" ");
writer.beginArray();
for (Message message : messages) {
gson.toJson(message, Message.class, writer);
}
writer.endArray();
writer.close();
}
複製代碼
大部分框架或者SDK爲了解耦,或多或少的會使用到反射,反射自己會性能就會比直接調用差不少。
有時候會大批量使用反射去實例化對象,因此不要在那些會被反射實例化的類的構造函數中作太多的事情。
建議在獲取實例化對象後,再使用對象直接調用初始化方法。
在代碼中有時候可能會發生異常,因此通常使用try/catch來作保護,避免程序崩潰。
一旦有異常發生,系統會耗費資源去處理,自己對內存和CPU就會有消耗。
因此不能由於有了try/catch而不顧代碼質量,要儘可能保證沒有大量的異常出現,我在項目中見過大量空指針異常出現,這種異常咱們能夠在代碼層面就直接避免。
頻繁的GC也會對性能形成影響,嚴重的會致使卡頓或者ANR。
頻繁GC緣由有兩個
內存抖動,大量的對象被建立又在短期內立刻被釋放
瞬間產生大量的對象會嚴重佔用Young Generation的內存區域,當達到閥值,剩餘空間不夠的時候,也會觸發GC。即便每次分配的對象佔用了不多的內存,可是他們疊加在一塊兒會增長 Heap的壓力,從而觸發更多其餘類型的GC。
在項目中避免短期內忽然建立大量的對象。
獲取設備&應用基本信息,好比說包名、IMEI信息等等,是比較耗時的操做,能夠進行一些優化。
固定信息獲取採用緩存策略,例如版本號、版本名稱、手機mac地址、運營商信息等等,一次獲取,緩存到內存,下次直接使用
不部分不固定信息,例如網絡狀況(2G/3G/4G/WIFI),能夠採用監聽網絡變動方式,來即時更新網絡狀況
其餘不固定信息,好比基站信息,只能每次獲取
關於性能優化方面知識,官方也出過視頻Android Performance Patterns,網上也有不少翻譯的文章,寫得都比較詳細,這篇文章是自身從平時一些常見點入手作的一些總結,但願對你們有幫助。