1. 程式人生 > >Android效能優化--過度繪製

Android效能優化--過度繪製

渲染機制

前提知識

android 的渲染主要分為兩個元件:1.CPU 2.GPU,由這兩者共同完成在螢幕上繪製 。

  • CPU:中央處理器,它集成了運算,緩衝,控制等單元,包括繪圖功能.CPU將物件處理為多維圖形,紋理(Bitmaps、Drawables等都是一起打包到統一的紋理)。

  • GPU:一個類似於CPU的專門用來處理Graphics的處理器,用來幫助加快格柵化操作,當然,也有相應的快取資料(例如快取已經光柵化過的bitmap等)機制。

  • OpenGL ES:手持嵌入式裝置的3DAPI,跨平臺的、功能完善的2D和3D圖形應用程式介面API,有一套固定渲染管線流程。

  • DisplayList:把XML佈局檔案轉換成GPU能夠識別並繪製的物件。這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪製到螢幕上的資料資訊。

  • 格柵化:將圖片等向量資源,轉化為一格格畫素點的畫素圖,顯示到螢幕上。

  • 垂直同步VSYNC:讓顯示卡的運算和顯示器重新整理率一致以穩定輸出的畫面質量。它告知GPU在載入新幀之前,要等待螢幕繪製完成前一幀。下面的三張圖分別是GPU和硬體同步所發生的情況,Refresh
    Rate:螢幕一秒內重新整理螢幕的次數,由硬體決定,例如60Hz.而Frame Rate:GPU一秒繪製操作的幀數,單位fps。如下圖:

這裡寫圖片描述

渲染流程

UI物件—->CPU處理為多維圖形,紋理 —–通過OpeGL ES介面呼叫GPU—-> GPU對圖進行光柵化(Frame Rate ) —->硬體時鐘(Refresh Rate)—-垂直同步—->投射到螢幕

渲染情況

  • 正常情況

Android系統每隔16ms發出VSYNC訊號(1000ms/60=16.66ms),觸發對UI進行渲染, 如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps,為了能夠實現60fps,這意味著計算渲染的大多數操作都必須在16ms內完成。正常情況如下:
這裡寫圖片描述
注意:使順利在60幀,每幀必須小於16毫秒完成。

  • 渲染超時

當這一幀畫面渲染時間超過16ms的時候,垂直同步機制會讓顯示器硬體 等待GPU完成柵格化渲染操作, 這樣會讓這一幀畫面,多停留了16ms,甚至更多.這樣就這造成了 使用者看起來 畫面停頓.。
這裡寫圖片描述
當GPU渲染速度過慢,就會導致,某些幀顯示的畫面內容就會與上一幀的畫面相同。

通過上面的介紹,我們知道渲染中可能出現的問題,那麼如何避免或者說盡量減少這種問題的發生呢?接下來就是本文要說的過度繪製,減少不必要的繪製,儘量會減少渲染的時間。

過度繪製

過度繪製(Overdraw)描述的是螢幕上的某個畫素在同一幀的時間內被繪製了多次。在多層次的UI結構裡面, 如果不可見的UI也在做繪製的操作,這就會導致某些畫素區域被繪製了多次。這就浪費大量的CPU以及GPU資源。

檢測工具

按照以下步驟開啟Show GPU Overrdraw的選項:設定 -> 開發者選項 -> 除錯GPU過度繪製 -> 顯示GPU過度繪製。
這裡寫圖片描述

  • 藍色: 代表1層覆蓋。畫素繪製了兩次。大片的藍色還是可以接受的,若整個視窗是藍色的,可以擺脫一層。
  • 綠色: 代表2層覆蓋。畫素繪製了三次。中等大小的綠色區域是可以接受的但你應該嘗試優化、減少它們。
  • 淡紅: 代表3層覆蓋倍。畫素繪製了四次,小範圍可以接受。
  • 深紅: 代表4層覆蓋。畫素繪製了五次或者更多。這是錯誤的,要修復它們。

渲染耗時呈現工具

開啟“開發者選項”->“GPU呈現模式分析”->“在螢幕上顯示為條形圖”
這裡寫圖片描述
這個會分別顯示關於StatusBar,NavBar,啟用的程式Activity區域的GPU Rendering資訊。Activity區域有一根綠色的橫線,代表16ms,我們需要確保每一幀花費的總時間都低於這條橫線,這樣才能夠避免出現卡頓的問題。

介面上會滾動顯示垂直的柱狀圖來表示每幀畫面所需要渲染的時間,柱狀圖越高表示花費的渲染時間越長。每一條柱狀線都包含三部分:

  • 紅色代表OpenGL渲染Display
    List所需要的時間,假如當前介面的檢視越多,那麼紅色便會“跳”得越高。但其實這可能並不意味著你卡住了。
  • 黃色通常較短,它代表著CPU通知GPU“你已經完成檢視渲染了”,這裡CPU會等待GPU的回話。假如黃色部分很高的話,說明當前GPU過於忙碌,有很多命令需要去處理。
  • 藍色代表了檢視繪製所花費的時間,表示檢視在介面發生變化(更新)的用時情況。藍色對於判斷流暢度的參考意義是較大。當它越長時,說明當前檢視較複雜或者無效需要重繪,即我們通常說的“卡了”。

優化策略

1.減少佈局層級

關閉相關手機上的開發者檢測工具開關,開啟Android Device Monitor, 找到 Hierarychy view 檢視自己的佈局找到,深的層級,是否可以做優化. 最外層父容器 是否需要。

使用線性佈局LinearLayout排版導致UI層次變深,如果有這類問題,我們就使用相對佈局RelativeLayout代替LinearLayout,減少UI的層次;如果不能減少佈局層次推薦使用LinearLayout。

沒有用的父佈局時指沒有背景繪製或者沒有大小限制的父佈局,這樣的佈局不會對UI效果產生任何影響。我們可以把沒有用的父佈局,通過merge標籤合併來減少UI的層次;

2.去除不必要的背景

有時候為了方便會先給Layout設定一個整體的背景,再給子View設定背景,這裡也會造成重疊,如果子View寬度mach_parent,可以看到完全覆蓋了Layout的一部分,這裡就可以通過分別設定背景來減少重繪。再比如如果採用的是selector的背景,將normal狀態的color設定為“@android:color/transparent”,也同樣可以解決問題。適時使用Color.TRANSPARENT,因為透明色Color.TRANSPARENT是不會被渲染的,他是透明的。

所以開發過程中我們為某個View或者ViewGroup設定背景的時候,先思考下是否真的有必要,或者思考下這個背景能不能分段設定在子View上,而不是圖方便直接設定在根View上。

注意:在不同安卓版本的測試機中,去掉相同層次的背景色後,有的顯示正常,有的不正常。

3.優化自定義View的計算

學會裁剪掉View的覆蓋部分,增加cpu的計算量,來優化GPU的渲染,這個API可以很好的幫助那些有多組重疊 元件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約GPU資源,在clipRect區域之外的繪製指令都不會被執行,那些部分內容在矩形區域內的元件,仍然會得到繪製。並且在onDraw方法中減少View的重複繪製。

下面一個運用clipRect的例子

public class CardView extends View {

    private Bitmap[] mCards = new Bitmap[3];

    private int[] mImgId = new int[]{R.mipmap.a, R.mipmap.b, R.mipmap.c};

    public CardView(Context context) {
        super(context);
        for (int i = 0; i < mCards.length; i++) {
            Bitmap bm = BitmapFactory.decodeResource(getResources(), mImgId[i]);
            mCards[i] = Bitmap.createScaledBitmap(bm, 510, 510, false);
        }
    setBackgroundColor(Color.WHITE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save();
        canvas.translate(50, 200);
        for (Bitmap bitmap : mCards) {
            canvas.translate(150, 0);
            canvas.drawBitmap(bitmap, 0, 0, null);
        }
        canvas.restore();
    }
}

對於這段程式碼,沒有使用clipRect方法前,過度繪製檢測效果圖:
這裡寫圖片描述

修改onDraw方法:

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.save();
    canvas.translate(50, 200);
    for (int i = 0; i < mCards.length; i++) {
        canvas.translate(150, 0);
        canvas.save();
        if (i < mCards.length - 1) {
            canvas.clipRect(0, 0, 150, mCards[i].getHeight());
        }
        canvas.drawBitmap(mCards[i], 0, 0, null);
        canvas.restore();
    }
    canvas.restore();
}

去除windowbackground :

getWindow().setBackgroundDrawable(null);
//getWindow().setBackgroundDrawableResource(android.R.color.transparent);

再看下過度繪製檢測效果圖:
這裡寫圖片描述

平時的開發過程中,需要權衡效果和效能,根據需求進行選擇. ,養成良好的思考習慣。