相信很多人都有這種經歷,在使用app的過程中,突然間發現程序雖然在運行,但是這里停頓一下,那里停頓一下的卡頓現象,就像看上網看視頻一樣,緩沖不過來,視頻很卡,不能連續的看下去。造成這樣原因有很多,其中一種就是UI被過度繪制了。
UI過度繪制簡單的來說是指在一個界面中有很多元素,但是我們只需要更新某一小塊的元素,app卻把所有的元素都刷新一遍,這就造成過度繪制。
過度繪制造成UI卡頓的原因是因為它浪費大量的CPU以及GPU資源。手機原本為了保持視覺的流暢度,其屏幕刷新頻率是60hz,即在1000/60=16.67ms內更新一幀。如果沒有完成任務,就會發生掉幀的現象,也就是我們所說的卡頓。
這其中的原理比較復雜,大家可以看看大神是怎么說的,這里給個胡凱大神的文章地址:
Android性能優化之渲染篇
http://hukai.me/android-performance-render/
debug GPU overdraw
有問題就必然有解決辦法,在Android系統內部也有一個神器可以查看app的UI的過度繪制情況,在開發者選項中有個debug GPU overdraw(調試GPU過度繪制),打開之后有off(關閉),show overdraw areas(顯示過度繪制區域),show areas for Deuteranomaly(為紅綠癥患者顯示過度繪制區域)
我們選擇show overdraw areas,發現整個手機界面的顏色變了,在打開過度繪制選項后,其中的藍色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標就是盡量減少紅色Overdraw,看到更多的藍色區域。
Profile GPU rendering
其次android系統還內置了Profile GPU rendering工具,這個工具也是在開發者選項中打開,它能夠以柱狀圖的方式顯示當前界面的渲染時間
- 藍色代表測量繪制的時間,或者說它代表需要多長時間去創建和更新你的DisplayList.在Android中,一個視圖在可以實際的進行渲染之前,它必須被轉換成GPU所熟悉的格式,簡單來說就是幾條繪圖命令,復雜點的可能是你的自定義的View嵌入了自定義的Path. 一旦完成,結果會作為一個DisplayList對象被系統送入緩存,藍色就是記錄了需要花費多長時間在屏幕上更新視圖(說白了就是執行每一個View的onDraw方法,創建或者更新每一個View的Display List對象).
- 橙色部分表示的是處理時間,或者說是CPU告訴GPU渲染一幀的地方,這是一個阻塞調用,因為CPU會一直等待GPU發出接到命令的回復,如果柱狀圖很高,那就意味著你給GPU太多的工作,太多的負責視圖需要OpenGL命令去繪制和處理.
- 紅色代表執行的時間,這部分是Android進行2D渲染 Display List的時間,為了繪制到屏幕上,Android需要使用OpenGl ES的API接口來繪制Display List.這些API有效地將數據發送到GPU,最總在屏幕上顯示出來.
在這里也放一個大神關于Profile GPU rendering的介紹
http://androidperformance.com/2015/04/19/Android-Performance-Patterns-4.html
下面我們開始對UI多度繪制開始實戰吧!
實戰項目地址
初始界面的問題
剛打開這個項目,我們就發現了在第一個有過度繪制問題,效果如下
存在問題
- 在按鈕overdraw上面就有個紅色的過度繪制區域
- 在文本框This is test的布局中也是紅色過度繪制區域
解決方法
-
要解決這個問題,我們首先需要分析這是怎么引起的。分析到activity_main.xml的布局文件時,發現這里使用了多個嵌套的LinearLayout布局,而且每個LinearLayout都會使用一次android:background設置一次自己的背景顏色,他們造成了過度繪制。
仔細分析在其中一個嵌套ImageView的LinearLayout布局背景顏色與最外層的背景顏色是一樣的,屬于不需要的背景色,因此將這個LinearLayout中的android:background屬性刪除,這時發現文本框布局已經不再是紅色了
第一次優化.png - 咋看之下一切都很完美,但其實整個ui其實還含有一個隱含的繪制效果,那邊是在activity中,使用setContentView(R.layout.activity_main)設置布局的時候,android會自動填充一個默認的背景,而在這個UI中,我們使用了填充整個app的背景,因此不需要默認背景,取消也很簡單,只需要在activity中的onCreate方法中添加這么一句就行了
getWindow().setBackgroundDrawable(null);
現在看最終優化效果
OVERDRAWVIEW頁面的問題
在overdrawviewactivity中只有一個自定義的圖案,而這個自定義的圖案引起了過度繪制的問題
解決方法
- 首先這個也是填充了整個ui界面的繪制圖片,因此我們也在activity中的onCreate方法中添加getWindow().setBackgroundDrawable(null);取消默認繪制。
- 繼續研究,發現過度繪制問題是由于OverDrawView類中的ondraw方法中多次繪制了矩形導致的,代碼如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
mPaint.setColor(Color.GRAY);
canvas.drawRect(0, 0, width, height, mPaint);
mPaint.setColor(Color.CYAN);
canvas.drawRect(0, height/4, width, height, mPaint);
mPaint.setColor(Color.DKGRAY);
canvas.drawRect(0, height/3, width, height, mPaint);
mPaint.setColor(Color.LTGRAY);
canvas.drawRect(0, height/2, width, height, mPaint);
}
通過分析得知,顏色為GRAY的矩形的高度其實不需要設置為整個屏幕的高度,它的高度只需要設置為它所顯示范圍的高度就可以了,因此可以設為height/4。
其他的矩形也是同樣的道理,因此更改這里的代碼為:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
mPaint.setColor(Color.GRAY);
canvas.drawRect(0, 0, width, height/4, mPaint);
mPaint.setColor(Color.CYAN);
canvas.drawRect(0, height/4, width, height/3, mPaint);
mPaint.setColor(Color.DKGRAY);
canvas.drawRect(0, height/3, width, height/2, mPaint);
mPaint.setColor(Color.LTGRAY);
canvas.drawRect(0, height/2, width, height, mPaint);
}
優化的界面
BUSYONDRAW頻繁繪制
當我們點擊BUSYONDRAW按鈕的時候,我們發現明顯的卡頓現象。在開發者選項中打開Profile GPU rendering選項,然后在次點擊BUSYONDRAW按鈕,發現這個頁面繪制渲染時間已經突破天際了!
在初始的時候,藍色繪制時間占滿整個屏幕高度,這是造成卡頓的重要原因。
解決方法
- 首先卡頓現象是由ondraw方法中的for循環中的打印字符串引起的
for (int i = 0; i < 1000; i++) {
System.out.println("canvas = [" + canvas + "]" + i);
}
我們將其提取出來放在另一個線程中運行。
先是創建一個線程池:
private ExecutorService pool = Executors.newCachedThreadPool();
將要運行的耗時操作封裝成Runable對象并通過一個方法獲取:
@NonNull
private Runnable getCommand(final Canvas canvas) {
return new Runnable() {
@Override
public void run() {
for (int i = 0; i &lt; 1000; i++) {
System.out.println("canvas = [" + canvas + "]" + i);
}
}
};
}
最后在onDraw里運行放在子線程里運行:
pool.execute(getCommand(canvas));
現在進入也不會卡頓了。
其次在ondraw中也不宜創建Paint()對象,因為app會頻繁調用ondraw對象,會造成內存泄漏,因此需要將其提取為全局變量。
最后繪制了多個圓形圖案也造成了一定程度的卡頓,但由于功力不夠,暫時無法優化。