1. 程式人生 > >Android App效能優化之UI流暢度優化

Android App效能優化之UI流暢度優化

一、卡頓的問題本質

UI流暢度的優化主要是解決UI卡頓的現象,而UI卡頓的源頭就是渲染效能的問題。佈局太複雜或者是一個元素重複繪製多次等原因,Android系統無法及時完成那些複雜的介面渲染操作,這樣就發生了丟幀,使用者就會感覺到不流暢,卡頓。

Android系統每隔16ms發出VSYNC訊號,觸發對UI進行渲染,如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,為了能夠實現60fps,這意味著程式的大多數操作都必須在16ms內完成。

為什麼是60fps?
我們通常都會提到60fps與16ms,可是知道為何會是以程式是否達到60fps來作為App效能的衡量標準嗎?這是因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。

12fps大概類似手動快速翻動書籍的幀率,這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續線性的運動,這其實是歸功於運動模糊的效果。24fps是電影膠圈通常使用的幀率,因為這個幀率已經足夠支撐大部分電影畫面需要表達的內容,同時能夠最大的減少費用支出。但是低於30fps是無法順暢表現絢麗的畫面內容的,此時就需要用到60fps來達到想要的效果,當然超過60fps是沒有必要的。

開發app的效能目標就是保持60fps,這意味著每一幀你只有16ms=1000/60的時間來處理所有的任務。

如果你的某個操作花費時間是24ms,系統在得到VSYNC訊號的時候就無法進行正常渲染,這樣就發生了丟幀現象。那麼使用者在32ms內看到的會是同一幀畫面。

二、檢測工具

1.手機自帶的Show GPU Overdraw

設定-開發者選項-Show GPU Overdraw,開啟可以觀察UI上的Overdraw情況。

藍色,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標就是儘量減少紅色Overdraw,看到更多的藍色區域

2.手機自帶的Profile GPU Rendering

設定-開發者選項-Profile GPU Rendering-選中On screen as bars
設定之後可以在手機畫面上看到豐富的GPU繪製圖形資訊,分別關於StatusBar,NavBar,啟用的程式Activity區域的GPU Rending資訊。

隨著介面的重新整理,介面上會滾動顯示垂直的柱狀圖來表示每幀畫面所需要渲染的時間,柱狀圖越高表示花費的渲染時間越長。
中間有一根綠色的橫線,代表16ms,我們需要確保每一幀花費的總時間都低於這條橫線,這樣才能夠避免出現卡頓的問題。
每一條柱狀線都包含三部分,藍色代表測量繪製Display List的時間,紅色代表OpenGL渲染Display List所需要的時間,黃色代表CPU等待GPU處理的時間。

Android M版本開始,GPU Profiling工具把渲染操作拆解成如下8個詳細的步驟進行顯示。

  • Swap Buffers:表示處理任務的時間,也可以說是CPU等待GPU完成任務的時間,線條越高,表示GPU做的事情越多;
  • Command Issue:表示執行任務的時間,這部分主要是Android進行2D渲染顯示列表的時間,為了將內容繪製到螢幕上,Android需要使用Open GL ES的API介面來繪製顯示列表,紅色線條越高表示需要繪製的檢視更多;
  • Sync & Upload:表示的是準備當前介面上有待繪製的圖片所耗費的時間,為了減少該段區域的執行時間,我們可以減少螢幕上的圖片數量或者是縮小圖片的大小;
  • Draw:表示測量和繪製檢視列表所需要的時間,藍色線條越高表示每一幀需要更新很多檢視,或者View的onDraw方法中做了耗時操作;
  • Measure/Layout:表示佈局的onMeasure與onLayout所花費的時間,一旦時間過長,就需要仔細檢查自己的佈局是不是存在嚴重的效能問題;
  • Animation:表示計算執行動畫所需要花費的時間,包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦這裡的執行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執行的過程中是不是觸發了讀寫操作等等;
  • Input Handling:表示系統處理輸入事件所耗費的時間,粗略等於對事件處理方法所執行的時間。一旦執行時間過長,意味著在處理使用者的輸入事件的地方執行了複雜的操作;
  • Misc Time/Vsync Delay:表示在主執行緒執行了太多的任務,導致UI渲染跟不上vSync的訊號而出現掉幀的情況;出現該線條的時候,可以在Log中看到這樣的日誌:

注:GPU配置渲染工具雖然可以定位出問題發生在某個步驟,但是並不能定位到具體的某一行;當我們定位到某個步驟之後可以使用工具TraceView進行更加詳細的定位。

3.Hierarchy Viewer

Hierarchy Viewer是Android Studio自帶的用於檢視佈局層級關係的工具。如下圖可以開啟Hierarchy Viewer。

Hierarchy Viewer使用如下圖,可以看到點選LinearLayout,然後點選Profile Node(紅色圓圈圈住的),你會發現所有的子View上面都有了3個圈圈,
(取色範圍為紅、黃、綠色),這三個圈圈分別代表measure 、layout、draw的速度,並且你也可以看到實際的執行的速度,如果你發現某個View上的圈是紅色,那麼說明這個View相對其他的View,該操作執行最慢,注意只是相對別的View,並不是說就一定很慢。

4.Tiny Dancer

這個工具可以實時的顯示app的幀率(GPU在一秒內繪製操作的幀數,標準是60fps)

5.Takt

同樣是實時顯示幀率

6.BlockCanary/Android Performance Monitor

用於檢測主執行緒上的各種卡慢問題,並通過元件提供的各種資訊分析出原因並進行修復。

三、優化方案

1.避免過度繪製

1.1 移除不必要的background

1.2 clipRect

clipRect用來控制canvas的繪製區域,可以避免canvas繪製那些不會顯示出來的區域。示例:

明顯示卡片重疊的部分重複繪製了,所以是紅色。這時候的程式碼如下:

@Override
protected void onDraw(Canvas canvas)
{

    super.onDraw(canvas);

    canvas.save();
    canvas.translate(20, 120);
    for (Bitmap bitmap : mCards)
    {
        canvas.translate(120, 0);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
    canvas.restore();

}

使用clipRect控制繪製區域,不繪製重疊的區域,程式碼如下:

@Override
protected void onDraw(Canvas canvas)
{

    super.onDraw(canvas);

    canvas.save();
    canvas.translate(20, 120);
    for (int i = 0; i < mCards.length; i++)
    {
        canvas.translate(120, 0);
        canvas.save();
        if (i < mCards.length - 1)
        {
            canvas.clipRect(0, 0, 120, mCards[i].getHeight());
        }
        canvas.drawBitmap(mCards[i], 0, 0, null);
        canvas.restore();
    }
    canvas.restore();

}

這時候效果如下:

可以看到卡片都只繪製了一次,變成了藍色。因為Activity加了背景色,而DecorView也有背景色(主題設定),而Activity又會放入DecorView中,所以Activity的背景繪製了兩次,我們可以不設定DecorView的背景色。在Activity的onCreate中呼叫:

getWindow().setBackgroundDrawable(null);

2.減少佈局的層級

可使用Hierarchy Viewer工具檢視佈局的樹狀View圖,減少佈局之間的巢狀,巢狀層級越深,耗費時間越久。儘量使佈局扁平化。

3.使用標籤

3.1 merge

merge標籤一般和include配合使用,可以降低減少佈局的層級。merge只能用於根佈局。

3.2 ViewStub

ViewStub提供了按需載入的功能,當需要時才會將ViewStub中的佈局載入到記憶體.ViewStub是輕量級且不可見的檢視,它沒有大小,沒有繪製功能,也不參與measure和layout,資源消耗非常低。

    <ViewStub
      android:id="@+id/stub"
      android:inflatedId="@+id/panel"
      android:layout:"@layout/layout_err"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" />

panel是layout/layout_err佈局的根元素的id,可以在需要時將此佈局加載出來。載入方式:

    ((ViewStub)findViewById(R.id.stub)).setVisibility(View.VISIBLE);

或者

    View panel = ((ViewStub)findViewById(R.id.stub)).inflate();

App裡常見的檢視如蒙層、小紅點,以及網路錯誤、沒有資料等公共檢視,使用頻率並不高,如果每一次都參與繪製其實是浪費資源的,都可以藉助ViewStub標籤進行延遲初始化,僅當使用時才去初始化。

4.Alpha

直接setAlpha在把檢視繪製到幀快取之前,先在離屏快取上繪製這個檢視,實際上是增加了另一個未被發現的過度繪製分層。

  • 文字檢視(TextView)—— 用 setTextColor() 代替 setAlpha() 方法。文字顏色如果使用 alpha 通道,會導致直接用 alpha 繪製文字。
  • 影象檢視(ImageView)—— 用 setImageAlpha() 代替 setAlpha() 方法。原因同文本檢視。
  • 自定義檢視 —— 如果你自定義的檢視不支援檢視重疊,這個複雜的行為就和我們無關。那就沒有辦法,如上面例子所示,子檢視會混在一起。通過過載 hasOverlappingRendering() 方法並返回錯誤,我們可以通知系統對檢視採用直接和簡單的通道。通過過載 onSetAlpha() 方法並返回正確,我們就有一個選擇來手動處理設定一個 alpha 值會發生什麼。

5.硬體加速

參考