性能優化06_渲染優化詳解

Android性能優化彙總
卡頓現象
渲染功能是應用程序最普遍的功能,開發任何應用程序都是這樣,一方面,設計師要求爲用戶展現可用性最高的超然體驗,另一方面,那些華麗的圖片和動畫,並不是在所有的設備上都能劉暢地運行。我們來了解一下什麼是渲染性能。
首先,我們要知道Android系統每隔16ms就重新繪製一次Activity,也就是說,我們的應用必須在16ms內完成屏幕刷新的全部邏輯操作,這樣才能達到每秒60幀,然而這個每秒幀數的參數由手機硬件所決定,現在大多數手機屏幕刷新率是60赫茲(赫茲是國際單位制中頻率的單位,它是每秒中的週期性變動重複次數的計量),也就是說我們有16ms(1000ms/60次=16.66ms)的時間去完成每幀的繪製邏輯操作,如果錯過了,比如說我們花費34ms才完成計算,那麼就會出現我們稱之爲丟幀的情況。
在這裏插入圖片描述
安卓系統嘗試在屏幕上繪製新的一幀,但是這一幀還沒準備好,所以畫面就不會刷新。如果用戶盯着同一張圖看了32ms而不是16ms,用戶會很容易察覺出卡頓感,哪怕僅僅出現一次掉幀,用戶都會發現動畫不是很順暢,如果出現多次掉幀,用戶就會開始抱怨卡頓,如果此時用戶正在和系統進行交互操作,例如滑動列表或者輸入數據,那麼卡頓感就會更加明顯,用戶會毫不留情地對我們的應用進行吐槽,現在我們對繪製每幀花費的時間有了更清晰的瞭解,再來看看是什麼原因導致了卡頓,如何去解決應用中的這些問題
渲染管線
Android系統的渲染管線分爲兩個關鍵組件:CPU和GPU,它們共同工作,在屏幕上繪製圖片,每個組件都有自身定義的特定流程。我們必須遵守這些特定的操作規則才能達到效果。
在這裏插入圖片描述
在CPU方面,最常見的性能問題是不必要的佈局和失效,這些內容必須在視圖層次結構中進行測量、清除並重新創建,引發這種問題通常有兩個原因:一是重建顯示列表的次數太多,二是花費太多時間作廢視圖層次並進行不必要的重繪,這兩個原因在更新顯示列表或者其他緩存GPU資源時導致CPU工作過度。
在GPU方面,最常見的問題是我們所說的過度繪製(overdraw),通常是在像素着色過程中,通過其他工具進行後期着色時浪費了GPU處理時間。
接下來我們將講解更多關於失效佈局和重繪的內容,以及如何使用SDK中的工具找出拖累應用性能的原因
在這裏插入圖片描述
CPU和 GPU
想要開發一款性能優越的應用,我們必須瞭解底層是如何運行的。有一個主要問題就是,Activity是如何繪製到屏幕上的?那些複雜的XML佈局文件和標記語言,是如何轉化成用戶能看懂的圖像的?
實際上,這是由格柵化操作來完成的,格柵化就是將例如字符串、按鈕、路徑或者形狀的一些高級對象,拆分到不同的像素上在屏幕上進行顯示,格柵化是一個非常費時的操作。我們所有人的手機裏面都有一塊特殊硬件,它就是圖像處理器(GPU顯卡的處理器),目的就是加快格柵化的操作,GPU在上個世紀90年代被引入用來幫助加快格柵化操作。
在這裏插入圖片描述

GPU使用一些指定的基礎指令集,主要是多邊形和紋理,也就是圖片,CPU在屏幕上繪製圖像前會向GPU輸入這些指令,這一過程通常使用的API就是Android的OpenGL ES,這就是說,在屏幕上繪製UI對象時無論是按鈕、路徑或者複選框,都需要在CPU中首先轉換爲多邊形或者紋理,然後再傳遞給GPU進行格柵化。
在這裏插入圖片描述
我們要知道,一個UI對象轉換爲一系列多邊形和紋理的過程肯定相當耗時,從CPU上傳處理數據到GPU同樣也很耗時。所以很明顯,我們需要儘量減少對象轉換的次數,以及上傳數據的次數,幸虧,OpenGL ES API允許數據上傳到GPU後可以對數據進行保存,當我們下次繪製一個按鈕時,只需要在GPU存儲器裏引用它,然後告訴OpenGL如何繪製就可以了,一條經驗之談:渲染性能的優化就是儘可能地上傳數據到GPU,然後儘可能長地在不修改的情況下保存數據,因爲每次上傳資源到GPU時,我們都會浪費寶貴的處理時間,Android系統的Honeycomb版本發佈之後,整個UI渲染系統就在GPU中運行,之後各個版本都在渲染系統性能方面有更多改進。
Android系統在降低、重新利用GPU資源方面做了很多工作,這方面完全不用擔心,舉例說,任何我們的主題所提供的資源,例如Bitmaps、Drawables等都是一起打包到統一的紋理當中,然後使用網格工具上傳到GPU,例如Nine Patches等,這樣每次我需要繪製這些資源時,我們就不用做任何轉換,他們已經存儲在GPU中了,大大加快了這些視圖類型的顯示。然而隨着UI對象的不斷升級,渲染流程也變得越來越複雜,例如說繪製圖像,就是把圖片上傳到CPU存儲器,然後傳遞到GPU中進行渲染。路徑使用時完全另外一碼事,我們需要在CPU中創建一系列的多邊形,甚至在GPU中創建掩蔽紋理來定義路徑。繪製字符就更加複雜一些,首先我們需要在CPU中把字符繪製製成圖像,然後把圖像上傳到GPU進行渲染再返回到CPU,在屏幕上爲字符串的每個字符繪製一個正方形。
在這裏插入圖片描述
現在Android系統已經解決了大多數性能問題,除非我們還有更高要求,我們基本不會發現與GPU相關的問題,然後還有一個GPU性能問題瓶頸,這個問題困擾着每個程序員,這就是過度繪製。
GPU的主要問題 -過度繪製(overdraw)
如果我們曾經粉刷過房子,我們應該知道,給牆壁粉刷工作量非常大,如果我們需要重新粉刷,第一次的粉刷就白乾了。同樣的道理,我們的應用程序會因爲過度繪製,從而導致性能問題,如果我們想兼顧高性能和完美的設計,往往會碰到一種性能問題,即過度繪製。過度繪製是一個術語,指的是屏幕上的某個像素點在同一幀的時間內被繪製了多次。假如我們有一堆重疊的UI卡片,最接近用戶的卡片在最上面,其餘卡片都藏在下面,也就是說我們花大力氣繪製的那些下面的卡片基本都是不可見的。
在這裏插入圖片描述
問題就在於此,因爲每次像素經過渲染後,並不是用戶最後看到的部分,這就是在浪費GPU的時間。目前流行的一些佈局是一把雙刃劍,帶給我們漂亮視覺感受的同時,也造成過度繪製的問題,爲了最大限度地提高應用程序的性能,我們必須儘量減少過度繪製。幸運的是,Android手機提供了查看過度繪製情況的工具,在開發者選項中打開“Show GPU overdraw”選項,手機屏幕顯示會出現一些異常不用過於驚慌,Android在屏幕上使用不同顏色,標記過度繪製的區域,如果某個像素點只渲染了一次,我們看到的是它原來的顏色,隨着過度繪製的增多,標記顏色也會逐漸加深,例如1倍過度繪製會被標記爲藍色,2倍、3倍、4倍過度繪製遵循同樣的模式。所以當我們調試應用程序的用戶界面時,目標就是儘可能的減少過度繪製,將紅色區塊轉變成藍色區塊,爲了完成目標有兩種清楚過度繪製的方法,首先要從視圖中清楚那些,不必要的背景和圖片,他們不會在最終渲染圖像中顯示,記住,這些都會影響性能。其次,對視圖中重疊的屏幕區域進行定義,從而降低CPU和GPU的消耗,接下來我們深入瞭解過度繪製
在這裏插入圖片描述
可視化方式解決過度繪製
現在我們看到了示例代碼的應用程序,現在就想象我們自己開發了一款聊天應用,我們想了解應用程序在過度繪製性能上的表現如何,首先要做的就是蒐集信息,在這一步我們需要打開手機上的GPU過度繪製調試,看看這些過度繪製的地方,我們需要減少這些過度繪製,尤其是紅色區域,這裏說明一下各個顏色代表的意思。
現在深入瞭解一下UI是如何創建的,看看能否做一些清理,減少過度繪製,辦法一就是清除不必要的背景和圖片。例如我們想把Chatum背景中的這塊區域變成綠色或者2倍過度繪製區域,爲什麼能實現這個效果呢?這是由於Chatum的BaseActivity採用了不透明白色背景的佈局填充整個屏幕,我們喜歡這樣,但是卻與Android的材料主題默認設置相沖突,特別是窗口背景圖片,這些都導致了不必要的過度繪製,作爲一個開發者我們必須做一個決定,我們希望保留白色背景,材料主題其實沒有任何意義,我們能做的一個優化就是把Activity的背景圖片設置爲null,我向我們們展示一下在代碼中如何實現,打開Chatum的BaseActivity我們看一下onCreate方法,使用下列聲明取消原來的背景,就是這樣,通過取消背景我們將過度繪製區域的顏色,由綠色變成了藍色,變成了1倍過度繪製
在這裏插入圖片描述
我們看一下XML標記文件看看能否再做一些調整,我們可能已經注意到有三個XML文件,指定了Chatum的用戶界面,有Chatum Latinum的BaseActivity,XML聊天片段還有聊天記錄的單個XML,前面已經提過,我們想在這裏保留白色背景,在這裏我們什麼都不做,但是其他兩個XML文件能否做一些調整,現在我需要我們的幫助,請我們幫我梳理一下其他XML文件,看看能否清除一些不必要的背景,完成之後,把清除的背景數目填到這個方框裏,如果碰到什麼麻煩也用不着擔心。
在剩下的文件中應該可以找到4個不必要的背景,我們來查看一下,在BaseActivity中記住我們希望保留白色背景,現在我們來看聊天片段XML文件我們在這裏聲明瞭一個不必要的白色背景,我們並不需要這個聲明因爲可以使用MainActivity的白色背景(fragment_chats.xml)。
在這裏插入圖片描述
現在來看聊天記錄單個XML文件(chat_item.xml),這裏有三個不必要的背景,我們來刪除它們。
好了,現在我們來看看,過度繪製的情況有沒有改善,我們的屏幕就應該現在這樣,恰當的刪除這些背景,乾淨多了,對不對?很快就快大功告成了,但是我們還可以再做一個優化,注意頭像區域仍存在過度繪製,因爲我們繪製了一個方框然後再繪製頭像圖片,在沒有獲取到頭像時我們才設置一個背景,我們可以使用一些條件碼來實現,我們打開ChatAdapter.java,這部分代碼負責在個人聊天記錄上傳後進行填寫,我們找到了getview方法,在底部這裏我們找到一個邏輯操作,用來顯示頭像的同時設置背景顏色,我們來看看能否變得更智能一些,我們來寫一段代碼,在未獲取到頭像時僅用來設置背景顏色,然後我們將把背景顏色設置爲透明,然後上傳頭像,好了,這就是我們更新後的代碼。
修改前
在這裏插入圖片描述
修改後
在這裏插入圖片描述
注意,當爲獲取到頭像時我們要做的是,在頭像通常的位置加載透明色,然後,爲頭像設置真的背景色,剩下的就是獲取到頭像時的操作,我們恰當的加載頭像,然後,我們將背景色設置爲透明,這樣我們就能將過度繪製最小化。好了,我們來看看情況有沒有改善。很好,我們可以看到頭像區域,在更新代碼後過度繪製減少了,好了,這就是我們最後的優化。
我們總結下,優化前過度繪製非常嚴重,首先要做的,就是將背景圖片設置爲null,其次,就是清除XML文件中,不必要的背景聲明,最後,我們只在爲獲取到頭像時,顯示背景顏色。經過這些優化,我們再來看看,過度繪製的情況相比開始有了很大改善。
注意:有些過度繪製對於運行性能,可能是必要的也是可以接受的,比如說Android的ActionBar,但是,如果我們希望應用體驗更進一步,我們可以考慮儘可能地減少過度繪製。
在這裏插入圖片描述
clipRect和quickReject
值得指出的是,Android系統知道過度繪製是個麻煩,Android會設法避免繪製,那些在最終圖片中不顯示的UI組件,這種優化類型,稱作剪輯,它對UI性能非常重要。如果我們能確定某個對象會被完全阻擋,那就完全沒有必要繪製它,事實上,這是最重要的性能優化方法之一,而且是有Android系統執行的,但是不幸的是,這一技術無法應對複雜的自定義的View,系統無法檢測onDraw具體會執行什麼操作。這些情況下,底層系統無法識別如何去繪製對象,系統很難將覆蓋的View,從渲染管道中清除。例如,這疊牌只有最上面的牌是完全可見的,其他牌都被擋住了,這就意味着繪製那些重疊的像素就是浪費時間。
在這裏插入圖片描述
爲了解決這個問題,我們可以使用Canvas類的一些特別方法去幫助Android系統識別被遮擋的不需要繪製的部分,最有用的辦法是Canvas.clipRect,它可以幫助我們識別給定View的圖片邊界,邊界之外區域的任何繪製操作會被忽視,如果碰到此類重疊的View,這個方法特別好用,就像例子中的紙牌。如果我們知道自定義View可見部分的範圍,或者知道遮擋部分的範圍,我們就可以定義ClipRect邊界,可以避免遮擋區域的任何繪製操作,ClipRect API幫助系統識別出無需繪製的區域,對自定義View進行剪輯時,這個方法也很有用處。比如說,如果我們知道繪製對象在剪輯矩形之外,這個方法就非常好用,幸運的是,我們不必親自搞清楚重疊邏輯,我們可以使用Canvas.quickReject方法,判定給定區域是否完全在剪輯矩形之外,這種情況下可以忽略全部繪製工作。現在我們來看一個相關案例,我們對它做一些改進。
佈局優化
是時候來了解一下渲染管道中的CPU部分,爲了在屏幕上繪製某個東西,Android通常將高級XML文件轉換爲GPU能夠識別的對象,然後顯示在屏幕上,這個操作是在DisplayList的幫助下完成的,DisplayList持有所有要交給GPU繪製到屏幕上的數據信息,包含GPU要繪製的全部對象的信息列表,還有執行繪製操作的OpenGL命令列表,在某個View第一次需要被渲染時,DisplayList會因此被創建,當這個View要顯示到屏幕上時,我們將繪製指令提交給GPU來執行DisplayList,我們下次渲染這個View時,比如說位置發生了變化,我們僅僅需要執行DisplayList就夠了,但是如果我們修改了View的某些可見組件的內容,那麼之前的DisplayList就無法繼續使用了,這時我們要重新創建一個DisplayList,重新執行渲染指令並更新到屏幕上,請注意,任何時候View的繪製內容發生變化,都需要重新創建DisplayList並重新執行指令更新到屏幕,這個流程的表現性能,取決於我們的View的複雜程度,取決於視覺變化的類型,同時對渲染管道也會產生一些影響。舉例說,假如某個文本框尺寸突然變成當前的兩倍,在改變尺寸前,需要通過父View重新計算,並擺放其他子View的位置,在這種情況下我們改變了某個View,後面就會有很多工作要做,這些類型的視覺變化需要渲染管道的額外工作,當我們的View的尺寸變化時,觸發了測量操作,會經過整個View Hierarchy,詢問各個View的新尺寸,我們一旦改變了View的大小就會觸發上述過程,無論是填充或者圖片尺寸、設置文本大小、寬度、高度等等,如果我們是改變對象位置或者詢問佈局,或者某個View重新擺放子View都會觸發佈局操作,會觸發整個Hierarchy重新計算對象在屏幕上的新位置,現在Android運行系統已經非常善於處理記錄並執行渲染管道,除非我們要處理自定義View或者同時需要繪製太多View,其他情況下一般不會耗費太多時間,測量和佈局操作性能也很好,但是當我們的View Hierarchy失控時也更容易出現問題,執行這些功能的時間是和我們的View Hierarchy中需要處理的節點數成正比的,系統需要處理的View越多處理時間就越長。某些View可能比其他View要耗費更多時間,造成這種浪費的首要原因是,View Hierarchy中包含太多的無用View,這些View根本不會顯示在屏幕上,一旦觸發測量操作和佈局操作只會拖累應用程序的性能表現,幸好有一款叫做Hierarchy Viewer工具,它可以幫助我們查找並修復這些流氓View,我們來看看。

Hierarchy Viewer工具
讓我們來看第一個工具,Hierarchy Viewer將幫助我們快速可視化整個UI結構,另外,它還提供一個更好的方法,讓我們理解這個結構內的獨特視圖的相對渲染性能。它看起來是這樣的,讓我們來進行設置。我們需要設置一些代碼來使用這個工具,我們將使用Android基礎類中的Sunshine,在下面的教師筆記中,我們可以找到詳細的信息介紹如何抓取這些代碼,完成之後,請點擊Continue繼續。對了,還有一件事情,如果我們的手機已經root,而且運行Android Jellybean或更新版本的系統,將不會有問題。我需要在計算機上設置一個環境變量,使Hierarchy Viewer能夠與我們的設備通信。我們也可以在教師筆記中找到這方面的詳細信息,好了,讓我們繼續。我們啓動Hierarchy Viewer,我們首先要做的事情是進入Android Studio,然後,爲了使用Hierarchy Viewer,我們需要啓動Android Device Monitor。
在這個例子中,這是sunshine應用程序的主活動,現在如果我們把鼠標懸停在這裏,這是樹狀概覽,將概覽顯示整個UI結構。現在,我們還可以看到這個視圖端口,允許我們移動和更改左側詳情視圖中顯示的內容。這是詳情視圖,我們可以看到,當我移動視圖端口時,詳情視圖中的詳細信息也發生變化,這可以放大層級結構中的部分內容。現在如果我們跳轉到樹狀視圖,還會看到另外一個縮放控制功能,允許放大和縮小我們的結構,像這樣。我還可以拖動佈局以移動位置,放大和縮小以檢視更多的內容。當我們點擊一個視圖時,可以在屬性窗格中,看到它的所有屬性。這個屬性窗格在左側的這個位置打開,現在我要展開它。這裏有一些可縮回式菜單,顯示視圖的各種詳細信息。例如,我可以查看事件,和屏幕繪圖相關的其他詳細信息,比如透明度、縮放、旋轉等。現在,雙擊一個視圖,可以預覽我們所選擇的這個視圖在屏幕的實際顯示效果,然後,我們移到右下角的這個位置,這個Layout視圖,當我點擊層級上的一個視圖時,Layout視圖突出顯示Layout視圖中的相同視圖。換言之,我們可以將這個Layout視圖看做是一個框架,用來標記我們的設備屏幕,因此,當我們可以選擇詳情視圖中的項目時,並且看到他們將會佈局我們的設備屏幕。我們還可以進入反方向,點擊這裏的項目。可以看到,在樹狀視圖的頂部,這些項目變亮,它對應於這裏的這個視圖。如果我移動視圖頁面,並且放大一點,我可以看到這個視圖的詳情,不幸的是,我們不能從Hierarchy Viewer轉到我們的源代碼,因爲它鏈接到正在運行的程序,而不是源代碼本身。但是,每個節點顯示類型和ID,這樣我們可以在以後參考它們,或者以後在我們的源代碼中找到他們。我們應該知道,Hierarchy Viewer很有用,首先,它幫助我們從Android的角度,理解我們的用戶界面的結構,另外,這可以幫助我們確定哪些視圖是多餘的,讓我們可以簡化我們的視圖層級以節省內存,並提高渲染性能。但是真實的性能信息來自於,每個節點的概況顯示功能和Hierarchy Viewer。這個工具建模顯示整個用戶界面的渲染過程,並提供一個特定節點,相對特定樹狀圖中的其他節點的渲染數據,讓我們回到詳情樹狀視圖,我點擊一個子數級中的一個根節點,我們想要顯示它的結構情況,從測試的角度出發,我將點擊這個操作工具條容器。現在爲了調用Hierarchy Viewer的概要顯示功能,我們需要點擊這裏的這個Profile Node圖標,我們可能注意到每個視圖有三個獨特的圓點,它們可能具有不同的顏色,綠色、黃色或紅色。但是,這些圓點的順序也具有特定的含義。最左邊的圓點表示渲染管道的測量階段,中間的圓點表示佈局階段,最右邊的圓點表示渲染管道的繪製階段。現在,我來介紹這些顏色的含義,這些圓點的顏色表示這個節點相對於所有其他已經概要顯示的節點的性能。那麼,相對性能是什麼意思呢?讓我們來看這個綠色表示這個管道階段,這個視圖的渲染速度快於至少一半以上的其他視圖。這個黃色表示它的渲染速度屬於比較慢的50%,如果我們看到紅色,意味着這是視圖層級中最慢的節點,我們還應該知道紅色節點可能是存在問題。我們可能不希望它的渲染速度如此之慢。例如,在一個葉片節點或僅有少數幾個子元素的視圖中,應該是一個紅旗。另外還應該記住,當處理大型層級時,還有些節點應該是速度最慢的節點,那麼我們應該問,這個節點是我們所期望的節點嗎?另外請記住,由於它的性能相對較慢,但是它的絕對性能可能並不慢,因此使用實際數字將會起到幫助作用。現在,我們已經熟悉Hierarchy Viewer,讓我們繼續使用本課的一些示例代碼進行練習。

嵌套結構的性能評測
我們可能已經知道,在我們創建Android用戶界面時,應該讓我們的佈局儘可能簡單和扁平化,我有一些很好的建議,請記住龐大的佈局十分浪費資源,每個附加嵌套佈局和內置視圖都會直接影響我們的應用程序的性能和響應靈敏性,因此請記住,我們應該瞭解我們的應用程序的行爲模式,現在我們要返回到Android Device Monitor,我已經打開Hierarchy Viewer視窗,和以前一樣我們跳轉到這裏的窗格,選擇我們的設備然後選擇想要查看的活動,在這個例子中我們要看一下這個根節點,這是我們的線性佈局,這是跟視圖羣組將會顯示這兩行,我們在這裏可以看到它們,請注意來自於這個父級線性佈局的兩個不同子元素,其中一個表示我們聊天界面第一行,但是它使用嵌套線性佈局實現,第二個對應於佈局中的第二行,這次不使用嵌套設計,我們使用扁平化設計,使用相對佈局視圖羣組,這對應於XML中的代碼,進入Android Studio查看我們的源代碼,打開activity_compare_layout.xml文件,現在重新在屏幕上顯示以便於進行比較,我們看到一個父級容器它是一個由垂直方向組成的線性佈局,因此這些控件將會從上到下排列,現在我想讓我們注意這裏,這是我們的第一個聊天行,我們的實現方法不是使用結構化或嵌套佈局,這種方法更加直觀,邏輯性很強,例如,我們從一個水平性質的父級線性佈局開始,在左側我們將設置一個ImageView,在右側創建另外一個嵌套線性佈局以容納我們的文本,但是在這個例子中,方向是垂直的而不是水平的,它代表第一個條目,接下來我們可以看到聊天模板的第二行,與使用嵌套結構不同我們決定採用扁平化佈局,使用相對位置來描述它們。這樣做對於性能有什麼影響?讓我們返回Hierarchy Viewer
在這裏插入圖片描述
從渲染過程的角度來說線性佈局設計比相對佈局更慢一些,與相對佈局比較,線性佈局需要更多的資源開銷,這裏全部是綠色。如果我們有機會採取扁平化佈局,我們應該想辦法儘可能使用它。

優化Chatum Latinum
優化前:在這裏插入圖片描述
優化後:
在這裏插入圖片描述

發佈了231 篇原創文章 · 獲贊 75 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章