1. 程式人生 > >Android效能優化之UI渲染優化

Android效能優化之UI渲染優化

原文轉自:http://www.cnblogs.com/yezhennan/p/5442031.html

UI效能測試

效能優化都需要有一個目標,UI的效能優化也是一樣。你可能會覺得“我的app載入很快”很重要,但我們還需要了解終端使用者的期望,是否可以去量化這些期望呢?我們可以從人機互動心理學的角度來考慮這個問題。研究表明,0-100毫秒以內的延遲對人來說是瞬時的,100-300毫秒則會感覺明顯示卡頓,300-1000毫秒會讓使用者覺得“手機卡死了”,超過1000ms就會讓使用者想去幹別等事情了。

這是人類心理學最基礎的理論,我們可以從這個角度去優化頁面/view/app的載入時間。 Ilya Grigorik 有一個很棒的演講,是關於搭建1000毫秒內載入完成移動網站的。如果你的網頁能在1秒內載入好,就超過了人類感知的預期,你的使用者一定會感覺很滿意。還有研究表明,如果網頁在3-4秒內還沒加載出任何內容,使用者就會放棄了。把這些資料應用到app的載入,不難明白載入時間是越短越好。這篇文章主要關注UI的載入時間。當然UI效能優化還會涉及到其他方面,比如必需在後臺執行到任務,要從伺服器下載一個檔案等等,這些我們在後面的文章再聊。

卡頓(Jank)

內容的快速載入很重要,渲染的流暢性也很重要。android團隊把滯緩,不流暢的動畫定義為jank,一般是由於丟幀引起的。安卓裝置的螢幕重新整理率一般是60幀每秒(1/60fps=16.6ms每幀),所以你想要渲染的內容能在16ms內完成十分關鍵。每丟一幀,使用者就會感覺的動畫在跳動,會出現違和感。為了保證動畫的流暢性,我們接下來看下從哪些方面優化可以讓內容在16ms內渲染完成,同時分析一些常見的導致UI卡頓的問題。

android裝置的UI渲染效能

早期android使用者抱怨最多的就是UI,尤其是觸碰反饋和動畫流暢度,感覺都很卡。後來隨著android系統逐漸成熟,開發人員也投入了大量的時間和精力讓互動變的流暢起來。下面列舉一些不同系統版本所帶來的提升:

  • 在Gingerbread或者更早的裝置上,螢幕完全是由軟體繪製(CPU繪製)的(不需要GPU的參與)。後來隨著螢幕尺寸變大和畫素的提升,純粹靠軟體繪製遇到了瓶頸。

  • Honeycomb加入了平板裝置,進一步增加了螢幕尺寸。同時出於效能考慮,加入了GPU晶片,app在渲染內容的時候多了一個GPU硬體加速的選項。

  • 對於針對Ice Cream Sandwich或者更高系統的裝置,GPU硬體加速是預設開啟的。將軟體繪製(CPU)的壓力大部分轉移到了GPU上。

  • Jelly Bean 4.1 (and 4.2) “Project Butter” 做了近一步的提升來避免卡頓,通過引入VSYNC機制和增加額外的frame buffer(vsync和frame buffer的解釋可以參考

    這篇文章),執行 Jelly Bean的裝置丟幀的概率變的更小。引入這些機制的同時,android開發團隊還加入了一些優秀的工具來測量螢幕的繪製,開發者可以使用這些工具來檢測VSYNC buffering和卡頓。

我們從普通開發者的角度,來逐一看下這些提升和相關的測量工具。我們的目標很明顯:

  • 螢幕繪製低延遲
  • 保證流程穩定的幀率來避免卡頓

當android開發團隊引入這些UI流暢性的提升時,他們需要能量化這些提升的工具。經由他們的努力,這些工具都打包進了SDK以方便開發者們來檢測UI相關的效能問題。接下來我們就使用這些工具來優化幾個demo程式。

搭建Views

大家應該都對android studio裡xml佈局編輯器很熟悉了,知道怎麼在android studio(Eclipse)中搭建和檢測View結構。下圖是一個簡單的app view,包含一些套嵌的子view。搭建這些view的時候,一定要留意螢幕右上角的元件樹(Component Tree)。套嵌的子view越深,元件樹就越複雜,渲染起來也就越費時間。

圖4-1 

對於app裡的每一個view,android系統都會經過三部曲來渲染:measure,layout,draw。可以在腦中回想下你搭建的view的xml佈局檔案結構,measure從最頂部的節點開始,順著layout樹形結構依次往下:測量每個view需要在螢幕當中展示的尺寸大小(上圖當中:LinearLayout;RelativeLayout,LinearLayout;然後是textView0和LinearLayout Row1點分支,該分支又有另外3個子節點)。每個子節點都需要向自己的父節點提供自己的尺寸來決定展示的位置,遇到衝突的時候,父節點可以強制子節點重新measure(由此可能導致measure的時間消耗為原來的2-3倍)。這就是為什麼扁平的view結構會效能更好。節點所處位置越深,套嵌帶來的measure越多,計算就會越費時。我們來看一些具體的例子,看measure是怎麼影響渲染效能的。

Remeasureing Views(重新測量views)

並不是只有發生錯誤的時候才會觸發remeasure。RelativeLayouts經常需要measure所有子節點兩次才能把子節點合理的佈局。如果子節點設定了weights屬性,LinearLayouts也需要measure這些節點兩次,才能獲得精確的展示尺寸。如果LinearLayouts或者RelativeLayouts被套嵌使用,measure所費時間可能會呈指數級增長(兩個套嵌的views會有四次measure,三個套嵌的views會有8次的measure)。可以看下面圖4-9裡面一個誇張點的例子。

一旦view開始被measure,該view所有的子view都會被重新layout,再把該view傳遞給它的父view,如此重複一直到最頂部的根view。layout完成之後,所有的view都被渲染到螢幕上。需要特別注意到是,並不是只有使用者看得見的view才會被渲染,所有的view都會。後面我們會看下“螢幕重複繪製”的問題。app擁有的views越多,measure,layout,draw所花費的時間就越久。要縮短這個時間,關鍵是保持view的樹形結構儘量扁平,而且要移除所有不需要渲染的view。移除這些view會對加速螢幕渲染產生明顯的效果。理想情況下,總共的measure,layout,draw時間應該被很好的控制在16ms以內,以保證滑動螢幕時UI的流暢。

雖然可以通過xml檔案檢視所有的view,但不一定能輕易的查出哪些view是多餘的。要找到那些多餘的view(增加渲染延遲的view),可以用android studio monitor裡的Hierarachy Viewer工具,視覺化的檢視所有的view。(monitor是個獨立的app,下載android studio的時候會同時下載)

Hierarchy Viewer

Hierarchy Viewer可以很方便視覺化的檢視螢幕上套嵌view結構,是檢視你的view結構的實用工具。這個工具包含在android studio monitor當中,需要執行在帶有開發者版本的android系統的裝置上。後續所有的view和螢幕截圖都來自一款三星的Note II裝置,系統版本是Jelly Bean。在老的裝置(處理器慢)上測試渲染效能,更容易發現問題。

如圖4-2所示,開啟Hierarchy Viewer之後,會看到幾個視窗:左邊的視窗列出了連上你電腦的android裝置和裝置上所有執行的程序。活躍的程序是粗體展示的。第二個tab某一個編譯版本的詳情(後面細說)。中間的部分是可縮放的view的樹形圖。點選某一個view能看到在裝置上展示的樣子和一些額外的資料。右邊有兩個view:樹形結構總覽和佈局view。樹形結構總覽顯示了整個view的樹形結構,裡面有一個方塊顯示了中間視窗在整個樹形結構當中所處的位置。佈局view當中深紅色高亮的區域表示所選中的view被繪製的部分(淺紅色展示的是父view)。

圖4-2 

在中間的這個視窗,你可以點選任何一個view來檢視該view在android裝置螢幕上的展示。點選樹形圖工具欄裡紅綠紫三色的維恩圖圖示,還能展示子view的數量,和measure,layout,draw三部曲所花費的時間。這個時間是被選擇的view及其所有子節點所花費時間的總和。(圖4-3中,我選擇了最頂部的view來獲取整個view結構的時間)

圖4-3 

最頂部的view總共包含181個view,measure的總時間為3.6ms,layout是7ms,draw花了14.5ms(總共大約

25ms)。要縮短渲染這些view的總時間,我們先看下app的樹形結構圖預覽,看看所有的view是怎麼拼湊到一起的。從樹形結構圖上可以看出螢幕裡有非常多的view,樹的結構比較扁平。前面說過,扁平的結構效能好,樹的深度對渲染的效能會產生很大的影響。我們的結構雖然是扁平的,卻依然花費了26ms的時間來渲染,說明扁平的結構也有可能會卡頓,也需要去考慮怎麼優化。

圖4-4 

排查一個新聞類app的樹形結構,大致可以看三個區域:頭部(底部藍色的方框),文章列表(兩個橙色的方框表示兩個不同的tab),單篇文章的view是用紅色方框來標註的。內部標題view的結構重複出現了九次,5個在上面橙色的方框內,4個在下面的方框內。最後,我們可以看到從邊上拉出來的導航欄是用底部綠色的方框標出來的。頭部用了22個view,兩個文章列表個用了67和44個view(每個標題部分使用了13個view),導航抽屜使用了20個。這樣我們還剩下18個view沒有計算在內。剩下的這些view其實是在滑動手勢動畫過程當中生成的。很顯然,view的數量很多,要做到不卡頓要讓view的繪製非常高效才行。

圖4-5 

仔細看下標題部分,一個標題是由13個view組成的。每個標題的結構有5層之深,一共花費0.456ms來measure,0.077ms來layout,2.737ms來draw。第五層是通過第四層的兩個RelativeLayouts來連線的(藍色高亮),這些又是通過第三層的另一個RelativeLayout來連線的(綠色高亮)。如果我們把第四第五層的view都移到第三層來,我們可以少渲染一整層。而且我之前解釋過,RelativeLayout裡的measure都會發生兩次,套嵌的view會導致measure時間的增加。

現在,你可能已經注意到了每個view裡紅色,黃色和綠色的圓圈。它們表示該view在那一層樹形結構裡measure,layout和draw所花費的相對時間(從左到右)。綠色表示最快的前50%,黃色表示最慢的前50%,紅色表示那一層裡面最慢的view。顯然,紅色的部分是我們優先優化的物件。

再看下文章標題的樹形結構,繪製最慢的view是右上角的ImageView。順著ImageView一直找到文章父view,父view是通過兩個RelativeLayouts來連線的(這裡增加了measure的時間),然後是3個沒有子節點的view(在最底部)。這3個view可以優化合併成一個view,這樣能減少兩個layer的渲染。

我們再看另一個新聞類app是怎麼來減少標題view裡面的子view數量的。從圖4-6裡能看到一個和圖4-5類似的樹形結構圖。

圖4-6 

圖4-6裡的標題view也有RelativeLayouts(綠色的部分)的問題,一共消耗了1.275ms的measure時間,layout用了0.066ms,draw 3.24ms(總共是4.6ms)。在這些資料基礎上,我們再做一些調整,加入一個更大的圖片展示和分享按鈕,但是整個樹形結構變得扁平一點(如圖4-7所示)。

圖4-7 

再看下標題view的渲染時間(三層的結構),只用了4.2ms!雖然展示了更大的內容,但節省了400ms!

為了更好的瞭解這部分的優化,我們再看另一個例子app。這個例子會展示一個山羊圖片等列表。介面使用了幾種不同的layout方式,效能差的和效能好的都有。仔細的檢視這些佈局,然後一步步優化它們,我們就能清楚的理解怎麼去優化一個app的渲染效能了。我們分幾步來進行優化,每一步改變都可以通過Hierarchy View視覺化的檢視。每換一種layout方式,xml渲染的效能要麼變好,要麼變差。我們先從效能差的佈局方式開始。先快速的掃一眼圖4-8裡的Hierarchy View。

圖4-8 

這個簡單的app裡有59個view。但是和圖4-4裡的app不同,這個app的樹形結構更扁平,水平方向的view更多一些。疊加的view越多,渲染就會越費時,減少view樹形結構的深度,app每一幀的渲染就會變快。

藍色方框裡面的view是action bar。橘色方框裡的是螢幕頂部的text box,紫色方框裡展示的是山羊的詳細資訊(有6個這種view)。紅色方框標示了7個view,每個都增加了樹形結構的深度。我們仔細看些這7個view其中三個的remeasure資料(圖4-9)。

圖4-9 4-9

當裝置開始measure views的時候,先從右邊的子views開始,然後到左邊的父views。右邊ListView包含6行資料,一共37個view,花了0.012ms來measure。把這個ListView加到中間的LinearLayout之後,變成38個views。有意思的是,measure的時間由於remeasure被觸發,瞬間跳到了18.109ms,是原來的三個數量級。LinearLayout左邊的RelativeLayout使得measure的時間再次翻倍到33.739ms。再依次往左繼續觀察(圖4-8裡紅色方框部分),measure的時間疊加到了68ms。但是隻要移除上面的一個LinearLayout,measure的時間瞬間降到了1ms。我們可以移除更多的層讓樹形結構更扁平一些,這樣我們可以得到圖4-10裡的結果,層數減少到了3層。

圖4-10 

我們可以繼續看下山羊資訊到row展示部分,來繼續減少view結構的深度。每一行山羊資訊有6個view,一個有6行資料在螢幕中展示(圖4-8中有一行資料是用紫色方框高亮的)。我們用Hierarchy View看下一行view的結構是怎麼樣的(圖4-11),先看下左邊兩個view(一個LinearLayout,一個RelativeLayout),這兩個view唯一的作用就是加深了樹機構的深度。LinearLayout連線了RelativeLayout,但並沒有展示其他什麼內容。

圖4-11 

因為RelativeLayout會measure兩次(我們現在關注優化measure的時間),我們先移除RelativeLayout(圖4-12)。這樣樹形結構的深度從4減到了3,渲染立馬快了一些。

圖4-12 

但效果還並不理想。我們繼續移除LinearLayout,同時調整下RelativeLayout來展示整個row的資訊(圖4-13),這樣深度近一步減少到了2。渲染又快了0.1ms。這樣看來優化的途徑有很多種,多嘗試總是有好處的(看下錶格4-1裡的結果)。

圖4-13 

每一行減少大約1ms的時間,我們一共可以節省6ms的渲染時間。如果你的app有卡頓,或者你通過工具檢測到每次渲染接近16ms了,減少6ms的時間當然會讓你的app更快一點。

View的重用

如果一個程式設計師面向物件程式設計經驗豐富,他就會盡可能重用建立的view(而不是每次都建立)。拿上面山羊app作為例子,其實每一行展示的layout都是重用的。如果xml檔案裡最外層的view只是用來承載子view的,那這個view只不過是增加了view結構的深度,這種情況下,我們可以移除這個view,用一個merge標籤來代替。這種方式可以移除樹形結構裡多餘的層。

大家可以從github上下載這個山羊app練習下,改變裡面xml檔案的佈局方式,再用Hierarchy View工具看下渲染時間的變化。

Hierarchy Viewer(不止是樹形結構圖)

Hierarchy Viewer還有一個功能,可以幫助開發者發現overdraw(重複的繪製)。從左到右看下樹形結構視窗的選項,可以發現這些功能:

  • 把view的樹形結構圖儲存為png圖片。
  • 匯出為photoshop的格式。
  • 重新載入一個view(第二個紫色樹形按鈕)。
  • 在另一個窗口裡開啟較大的view結構圖,還可以設定背景色來發現重複繪製。
  • 讓一個view的繪製失效(有條紅線的按鈕)。
  • 讓view重新layout。
  • 讓view生出draw命令到logcat(紫色樹形按鈕到第三個用處)。這樣可以檢視繪製到底觸發了哪些opengl行為。這個功能對opengl的專家做深度優化比較有用。

Hierarchy Viewer對於優化app view的樹形結構重要性不言而喻了,很可能會幫你節省幾十毫秒的繪製時間。

資源縮減

在我們把app的view結構變扁平,view的總數量減少之後,我們還可以嘗試減少每個view裡面使用的資源數量。2014年的時候,Instagram把標題欄裡的資源數量從29減少到了8個。他們測量後發現app的啟動時間增加了10%-20%(因設裝置而異)。主要是通過資源上色的方式來進行縮減。比如只加載一個資源,然後在執行的時候通過ColorFilter進行上色。我們看下下面的例子是怎麼個一個drawable上色的。

這樣一個資原始檔就可以表示幾種不同的狀態了(加星或者不加星,線上或者離線等等)。

螢幕的重複繪製

每過幾年,就會有傳聞說某個博物館在用x光掃描一副無價的名畫之後,發現畫作的作者其實重用了老的畫布,在名畫的底下還藏著另一副沒有被發現的畫作。有時候,博物館還能用高階的影象技術來還原畫布上的原作。android裡面的view的繪製就是類似的情況。當android系統繪製螢幕的時候,先畫父view,然後子view,再是更深的子view等等。這會導致所有的view都被繪製到了螢幕上,就像畫家的畫布一樣,這些view都被他們的子view覆蓋住了。

文藝復興時期,有很多偉大的畫家要等畫幹了以後才能重用畫布。但在我們的高科技觸控式螢幕上,螢幕重畫的速度要快幾個數量級,但是多次的重新繪製螢幕會使得繪製延遲變大,最終導致佈局的卡頓。重新繪製螢幕的行為叫做overdraw,下面我們會看下怎麼檢測overdraw。

overdraw還帶來的另一個問題,當view內容有更新的時候,之前繪製的view就失效了,view的每一個畫素都需要重繪。android裝置沒法判斷哪個view是可見的,所以只能繪製每個view的相關畫素。類比上面畫家的例子,畫家只能把老畫一幅幅還原出來,再一層層畫到畫布上,最後再畫上最新的畫。你的app如果有很多層,每一層的相關畫素都需要繪製一遍。如果不小心,這些繪製就會帶來效能問題。

檢測overdraw

android提供了一些很好的工具來檢測overdraw。Jelly Bean 4.2裡,開發者選項選單裡增加了Debug GPU Overdraw的選項。如果你用的是Jelly Bean 4.3 或者 KitKat 裝置,在螢幕的左下角會有一個計數展示螢幕overdraw的程度。我親身試過這個工具對檢測overdraw十分有效。雖然有時候這個會多提示6-7次overdraw(發生的概率還不小)。

圖4-14中的截圖還是來自上面的山羊app。左下方可以看到overdraw的計數。螢幕中可以看到3個overdraw的計數,其中開發者能控制的是主視窗的計數。overdraw的計數是在左下方。沒優化過的app overdraw的次數是8.43,我們優化過後可以降到1.38。導航欄overdraw的次數是1.2(選單按鈕是2.4),也就是說文字和圖示的overdraw貢獻了額外的20%。overdraw計數可以在不影響使用者體驗的前提下,快速便捷的比較不同app的overdraw,但沒辦法定位overdraw是哪裡產生的。

圖4-14 

另一種檢視overdraw的方式是在Debug GPU overdraw選單裡選擇“Show Overdraw areas”選項。選擇之後,會在app的不同區域覆蓋不同的顏色來表示overdraw的次數。比較螢幕上這些不同的顏色,可以快速方便的定位overdraw問題:

白色:沒有overdraw 藍色:1x overdraw(螢幕繪製了2次) 綠色:2x overdraw 淺紅色:3x overdraw 深紅色:4x或者更多overdraw

在圖4-15中,可以看到山羊app優化前後overdraw區域的變化。app的選單欄優化前後都沒有顏色(沒有overdraw),但android圖示和選單按鈕圖示都是綠色的(2x overdraw)。山羊圖片等列表在優化之前是深紅色的(4x以上的overdraw)。優化app 之後,只有checkbox和圖片區域是藍色(1x)的了,說明至少3層overdraw被消滅掉了!text和空白區域都沒有overdraw了。

圖4-15 

通過減少view的數量(或者去移除重複繪製的view),app的渲染會更快。通過比較父view在優化前後的繪製時間,可以發現優化後帶來50%效能的提升,由13.5ms降到6.8ms。

Hierarchy Viewer當中的overdraw

另一種檢視app當中overdraw的方式是把Hierarchy Viewer中的view的樹形結構儲存成photoshop識別的文件(樹形view裡的第二個選項)。如果你沒有安裝photoshop,有幾個其他的免費軟體也可以開啟這個文件。開啟文件檢視view,可以清楚看到不同layer裡的overdraw。對於大部分的線上app,在一個白色背景上放上另一個白色背景很常見。聽起來還好,但這裡其實有一次繪製是多餘的,完全可以避免的。我們再看下山羊app,所有overdraw圖片區域都放在了一張驢子的背景圖片上(替換了之前的白色背景)。之前的驢子看不到,是因為被白色背景圖擋住了。移除掉之後就可以看到下面的驢子了,這樣我們就可以快速的定位哪裡出現了overdraw。用GIMP開啟文件之後,app裡所有可見的view的左邊都有一個小眼睛圖示。在圖4-16中,可以看到我從最上面開始把view一個個隱藏起來了。在右邊的layout檢視中,可以看到一些其他的全屏layout(都顯示了驢子的圖片)。

圖4-16 

在圖4-17中可以看到另一個逐步隱藏view的辦法。從最左邊的全屏圖片開始,到中間的圖片,可以看到我們隱藏了兩行山羊的圖片展示,每一行下面的出現了一張拉伸的驢子的圖片。在這些驢子圖片的下面是一張白色的背景圖(從最右邊的圖片可以看出)。再移除這張白色背景可以看到一張大的驢子的圖片,在左下角。再往下是另一張白色的全屏背景圖。

圖4-17 

KitKat裡的overdraw

在KitKat或者更新的裝置裡,overdraw被大幅度的削減了。這項技術叫overdraw avoidance,系統可以檢測發現簡單的overdraw場景(比如一個view完全蓋住了另一個view),然後自動移除額外的繪製,應用到上面的例子,也就是說驢子那張大背景圖就不會去繪製了。這很明顯會極大的提高裝置的繪製效能。但開發者還是要儘可能的避免額外的overdraw(為了更好的效能,也為了能相容Jelly Bean及更老的裝置)。

Overdraw Avoidance和相關開發者工具

當用上面提到的overdraw檢測工具時,KitKat的overdraw avoidance功能會被禁止,這只是為了方便你檢視view的佈局,和在裝置上真正執行的情況並不一樣。

分析卡頓(測量GPU的渲染效能)

在我們優化過view的樹形結構和overdraw之後,你可能還是感覺自己的app有卡頓和丟幀,或者滑動慢:卡頓還是存在。可能高階機器上感覺不到卡頓,但低端機上還是可能會出現卡頓。為了能獲取更全面的卡頓檢測資訊,android在Jelly Bean及更新的系統里加入了一個GPU繪製開發者選項。能夠測出每一幀的繪製用了多少時間。你可以把測量出來的資料儲存到一個logfile(adb shell dumpsys gfxinfo),或者在裝置的螢幕上實時檢視這些資訊(只支援android 4.2+)。

我們快速來看下怎麼分析,我比較喜歡在螢幕上直接展示GPU的渲染資料,這樣感覺更直觀全面(logfile裡面的資料很適合離線的詳細分析)。我們最好在不同的裝置上都試一下。圖4-18展示的是Nexus 6執行Lollipop(左邊)和Moto G執行 KitKat(右邊)同時跑山羊app的GPU渲染資料。重點看下GPU測量圖表底部的水平綠條。它是裝置16ms繪製一幀的分割線,如果你有很多幀都超過了這條綠線,那就表示有卡頓了。在下圖裡可以看到Nexus6上有偶爾的卡頓。出現在滑動到頁面底部的時候,播放裡一個反彈的動畫。使用者體驗不算太糟。每一次螢幕繪製(豎線)被分成四種顏色來表示額外的測量資料:draw(藍色),prepare(紫色),process(紅色),執行(黃色)。在KitKat和更早的版本里,prepare的資料沒有獨立出來,包含在其他項裡面(因此只有看到3種顏色)。

圖4-18 

對比下Nexus 6和Moto G的GPU資料可以看出真機測試的重要性。圖4-18中,沒有優化過的山羊app精確的表示Moto G繪製的時間是Nexus 6的兩倍(比較兩圖中綠線的高度)。這一點可以通過資料採集(adb shell dumpsys gfxinfo)進一步說明。下一個例子當中,優化過的view繪製在Moto G上用了兩倍多時間。對於兩臺裝置來說,draw,prepare,process這幾步都花了差不多的時間(少於4ms)。差別出現在execute階段(紫色),Moto G比Nexus 6多用了差不多4ms。說明GPU渲染測試最好是在低端機器上來做,比較容易發現卡頓問題。

圖4-19 

一般來說,GPU Profiler可以幫你發現問題。在山羊app裡,如果我開啟Fibonacci延遲(在建立view多時候進行耗時的遞迴計算),GPU profiler看不出任何卡頓,因為計算都發生在主執行緒而且完全阻止了渲染(在低端機上,可能會出現ANR訊息)。

Fibonacci演算法

Fibonacci序列是這樣一組數的集合:每個數字都是它前面兩個數字的和。比如0,1,1,2,3,5,8等等。程式裡一般用來表示遞迴,這裡我用了最低效的方式來生成Fibonacci序列。

生成這些數字的計算次數呈指數級增長。這樣做的目的是在渲染的時候增加CPU的壓力,這樣渲染事件就無法得到及時處理,出現延遲。計算n=40就把app變得很慢了(低端機上會crash)。這個例子雖然有點牽強,但我們定位卡頓是由Fibonacci產生的過程會很有意義。

Android Marshmallow裡的GPU渲染

在android marshmallow裡,執行adb shell dumpsys gfxinfo . 可以發現一些檢測卡頓的新功能。首先,資料報告開頭部分能看到每一幀渲染的資訊了。

從app的啟動開始,我們可以看到一共渲染了多少幀,其中多少幀的渲染時間是控制在理想值的90%以內,還能看到渲染比較慢的幀(90%,95%,99%)。最後五行列出的是沒有在16ms內渲染完成的原因。注意,這裡不止有卡頓的問題,幀率還收到了其他因素的影響。

android marshmallow在gfxinfo庫裡增加了另一個好用的測試工具,adb shell dumpsys gfxinfo framestats。它能夠輸出每一幀裡發生的某些事件耗時,格式是逗號分隔的一張大表。列名沒有給出,但在Android Developer網站裡有解釋。為了算出渲染裡每一步的費時,我們要計算出報告裡不同framestats的差異。下面是一些繪製事件:

  • VSYNC-Intended_VSYC(告訴你是否丟幀裡,也就是卡頓)
  • 處理輸入事件的時間(一般要小於2ms)
  • 動畫計算(一般小於2ms)
  • layout和measure
  • view.draw()耗時
  • Sync耗時(如果大於0.4ms,表示很多bitmap正在傳送到GPU)
  • GPU耗時(overdraw的時間會在這裡面)
  • 繪製一幀的總時間

有時候即使出現了超過16ms的繪製,但由於有vsync buffer的存在,也不會出現丟幀。對於沒有額外buffer的低端裝置,就可能會出現卡頓了。

不只是卡頓(丟幀)

有時候GPU Profile裡看不到超過16ms的資料,但你從螢幕上看到明顯的卡頓或跳動。出現這種情況可能是由於CPU在做別的事情被堵住了,從而導致裡丟幀。在Monitor或者Android Studio中,可以檢視DDMS裡的logfiles。通過過濾log更容易檢視app的執行情況。可以重點看下類似下圖中的log。

我們在後面的文章裡會講訴CPU導致的丟幀是怎麼產生的。

Systrace

在上面的這些優化之後,如果你的介面還有卡頓,我們還有辦法。Systrace工具也可以測量你app的效能。甚至可以幫助你定位問題產生的位置。這個工具是作為“Project Butter”一部分同Jelly Bean一同釋出的,它能夠從核心級檢測你裝置的執行狀態。Systrace可配置的引數很多。我們這裡重點關注UI是怎麼渲染的,用systrace檢測卡頓問題。

Systrace和之前的工具不同的是,它記錄的是整個android系統的狀態,並不是針對某一個app 的。所以最好是用執行app比較少的裝置來做檢測,這樣就不會受到其他app的干擾了。Systrace圖示是綠色和粉紅色組成的(下圖紅色的橢圓裡)。點選下,會彈出一個帶幾個選項的視窗。

圖4-22 

trace資料記錄在一個html檔案裡,可以用瀏覽器開啟。這裡主要研究螢幕的互動資料,主要收集CPU,graphics和view資料(如圖4-22所示)。duration留空(預設是5秒)。點選OK之後,Systrace會馬上開始採集裝置上的資料(最好馬上開始操作)。因為採集的資料非常之多,所以最好一次只針對一個問題。

traces裡面的資料看著有點嚇人(我們只是勾選裡4個選項!)。滑鼠可以控制滑動,WASD可以用來zoom in/out(W,S)和左右滑動(A,D)。在剛跑的trace資料最上面,能看到CPU的詳細資料,CPU資料的下面是幾個可摺疊的區域,分別表示不同的活躍程序。每一個色條表示系統的一個行為,色條的長度表示該行為的耗時(放大可以看到更多細節)。選中螢幕底部的一個色條,第一眼看到的總覽有點嚇人,我們一條條分析看下這些資料。

圖4-23 

Systrace進化史

就像android生態圈一樣,Systrace在不同的系統版本里有不同的介面,展示,和輸出結果。

  • 在Jelly Bean裝置,在設定的開發者選項裡可以開啟tracing。必須要同時開啟電腦和手機上的該功能。
  • 隨著android系統版本的升級,trace生成的資料也更加詳細,佈局也有一些改變。
  • 我建議通過Jelly Bean檢視Systraaces,然後喝Lollipop上的資料對比,收集到的資料會不一樣。

在2015年的google io大會上,google釋出了新版本的Systrace,新版本增加了一些新特性,下面會有更詳細的介紹。

我們繼續滑動Systrace的輸出結果,執行期間每個程序的資料都可以看到。我們主要研究卡頓相關資訊,檢視螢幕重新整理時可能有問題的繪製。只要重新整理率和繪製都正常,螢幕的渲染應該就是流暢的。但只要一個出問題,就有可能會導致頁面渲染的卡頓。

Systrace Screen Painting

我們通過圖4-24來看下螢幕繪製的步驟。最頂部一行的trace(藍色高亮)時VSYNC,由一些均勻分佈的藍綠色寬條組成。VSYNC是作業系統發來的訊號,表示此時該重新整理螢幕了。每個寬條表示16ms(寬條之間的空白也是16ms)。當VSYNC事件發生的時候(在藍綠色寬條的任意一側),surface flinger(紅色高亮方框包含幾種顏色的長條)會從view buffer(沒展示出來)裡選一個view,然後繪製到螢幕上。理想情況下,surfaceflinger事件之間相距16ms(沒有卡頓),因此如果出現長條空缺則表示surfaceflinger丟掉了一次VSYNC更新事件,螢幕就沒有及時的重新整理(此時就會有卡頓)。在trace檔案2/3的位置可以看到這樣的空缺(綠色高亮方框)。

圖4-24 

圖4-24底部展示的是app的詳情。第二行資料(綠色和紫色的線條)表示的app正在建立view,然後是底部的資料(綠色,藍色,和一些紫色的條狀),表示的是RenderThread,view的渲染和傳送到buffer(圖中沒有畫出來)都是在這個執行緒裡做的。注意看可以發現大概1/3的位置,這些條狀在該區域集中變粗了,表示app此時由於某種原因發生了卡頓。不同app情況不一樣,發生卡頓的原因也不同,但是我們可以根據一些共同的現象推測卡頓的發生。

這種總覽很適合查詢卡頓,但要調查清楚原因需要放大仔細看下。要明白Systrace都記錄了什麼資料,最好搞明白Systrace到底是怎麼進行測量的,app沒有卡頓的時候Systrace輸出又是什麼樣的。一旦弄明白了Systrace是怎麼工作的,查詢問題就方便多了。在圖4-25中,我把app正常執行時Systrace紀錄的相關線條放到了一起。我們從螢幕左邊的droid.yahoo.com看起。我描述的時候在trace檔案裡會來回跳動到不同的位置。當繪製發生的時候:

  • 紅色方框:droid.yahoo.com完成了所有view的measure,然後把結果傳送給RenderThread。
  • 橘色方框:RenderThread,這裡app會:
    • 繪製frame(淺綠色)
    • 顯示buffer裡的內容(灰色)
    • 清空buffer(紫色)
    • 傳送給快取的view列表。
  • 黃色方框:com.yahoo.mobile.client.andr…

buffer裡面有一些view,線條的高度表示了buffer當中view的數量。剛開始,只有一個,當新的view加入到buffer中之後,高度就變成了2倍。

  • 綠色方框:VSYNC-sf 提示surface flinger有16ms的時間來渲染螢幕。裡面棕色的條狀表示16ms的長度。
  • 藍色方框:surfaceflinger從佇列裡抓取一個view(注意黃色方框裡的buffer中view數量從2變為1)。完成之後,view被髮送給GPU,螢幕就繪製被繪製了。
  • 紫色方框:VSYNC-app告訴app去渲染新的view(這裡有個16ms的timer)。
  • 當VSYNC一開始,droid.yahoo.att就不停的重複這個過程,measure view,傳送給RenderThread等等,不停的迴圈。

圖4-25 

再回過頭想一下裝置能這麼短的時間內流暢的渲染螢幕,確實是件很神奇的事情。瞭解了渲染的過程,我們來找下卡頓的原因。

圖4-26中,我們看下OS層的行為。我增加了一些箭頭來表示16ms的間隔,紅色的方框表示surfaceflinger的丟幀。

圖4-26 

為什麼會出現這種情況?箭頭上方的一行是view buffer,行的高度表示有多少幀快取在了buffer裡面。trace開始的時候,buffer裡快取的數量是1到2交替出現。surfaceflinger每抓取一個view(buffer裡的數量減一),又會馬上從app裡生成一個新的view來填充。但是當surfaceflinger完成第三個動作之後,buffer被清空了,但是沒有從app裡及時填充新的view。所以,我們從app層面來檢查下這期間發生了什麼。

在圖4-27中,我們可以看到開始的時候RenderThread傳送了一個view到buffer(紅色方框)。橘色方框表示app新建了另一個view,渲染,然後交給buffer(droid.yahoo.att measure,layout所有的view,RenderThread負責繪製)。不幸的是,app沒來得及建立新view就被掛起了(黃色方框內)。為了建立下一個view,droid.yahoo.att app在執行暗綠色的“performTraversals”(3ms)之前,要先執行“obtainView” 7ms,“setupListItem” 8.7ms。app然後把資料交給RenderThread,這一步也比較慢(12ms)。建立這一幀總共用了近31ms(上一個只用了6ms)。當建立這一幀開始的時候,buffer裡只有一幀的資料,但是裝置需要兩幀。buffer沒有被填滿,所以螢幕繪製出現了卡頓。

圖4-27 

有意思的是app後面馬上就速度追了上來。黃色方框內延遲遞交的view建立並交給buffer之後,後續的兩幀緊接著建立好了(綠色和藍色的方框)。通過快速的填充新的幀,app就只丟了一幀。這個trace結果是在Nexus 6上執行的(處理器比較快,能快速的跟上)。在三星S4 Mini,Jelly Bean 4.2.2上運行同樣的結果得到圖4-28.

圖4-28 

從總覽圖上可以清晰的看到有很多幀都丟掉了(trace開始的時候surfacelinger部分有很多的空缺)。而且頂部那一行(view buffer)裡的buffer經常是空的(導致裡卡頓),buffer裡同時有兩個view的情況非常少。對於一個GPU效能比較差的裝置來說,app能夠像Nexus 6一樣趕上填滿buffer的概率比較小。

小貼示: 其實你可以偶爾渲染一幀超過16ms,因為buffer裡面一般都有1到2幀準備好的view備用。但是如果超過2-3幀渲染很慢,使用者就會感覺到卡頓了。

上面的trace是在執行Jelly Bean的手機上跑的,RenderThread的資料歸到了droid.yahoo.att那一行(Lollipop之前measure,draw,layout都是和在一起的)。把每一行資料合在一起之後豎條變寬。每一次呼叫之間的細條空白說明手機在每幀的繪製之後,只剩下很少的時間處理其它任務。手機上的app只能稍稍領先surfacelinger填滿buffer的速度。如果app能夠減小所繪製view的複雜度,也就是加快view的渲染,細條的空白就會變的寬一點,buffer填滿的概率就更大,也就給低端裝置在繪製之外更多的空間去處理其它任務。

把這塊區域加高亮之後,Systrace會把所有條狀所佔的時間計算出一個總和,用滑鼠在上面依次移動就能看到基本的資料了。圖4-29中,可以看到performtraversals(父view的draw命令)平均用了13.8ms,大概有5ms的波動。16ms的卡頓閾值在波動的範圍之內,所以很有可能裝置上會有卡頓。

圖4-29 

把這塊放大能看到更多的細節(圖4-30)。每個垂直的紅線表示16ms。從圖中可以看出,大概有5,6次SurfaceFlinger錯過了紅線標記。綠色的“performtraversals”線條都幾乎有16ms長(這一步是必須做的,有卡頓)。還有兩個藍綠色的 deliverInputEvents(每個都超過了16ms)也阻礙了app的螢幕繪製。

圖4-30 

相關推薦

Android效能優化UI渲染優化

原文轉自:http://www.cnblogs.com/yezhennan/p/5442031.html UI效能測試 效能優化都需要有一個目標,UI的效能優化也是一樣。你可能會覺得“我的app載入很快”很重要,但我們還需要了解終端使用者的期望,是否可以去量化這些期望

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

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

Android效能優化記錄介面渲染優化

前言:網上已經有很多對於效能優化的文章,但是為啥自己還要造輪子呢?俗話說的好讀萬卷書不如行萬里路,好記性不如爛筆頭。只有自己動手去做了印象才會更深嘛!寫這篇是為了記錄效能優化這一系列的學習以及使用,順便鍛鍊下語言組織能力。 在之前Google釋

Android進階——效能優化佈局渲染原理和底層機制詳解(四)

引言 UI 全稱User Interaction,我第一次聽到這個名詞是在大學的時候,當時候上人機互動課,我們教授說他認為iPhone的i 就是代表Interaction的意思,暫且不必爭辯是非。回到我們軟體開發中來,UI是使用者感知與互動的第一且唯一的途徑,

Android效能優化圖片壓縮優化

1 分類Android圖片壓縮結合多種壓縮方式,常用的有尺寸壓縮、質量壓縮、取樣率壓縮以及通過JNI呼叫libjpeg庫來進行壓縮。 參考此方法:Android-BitherCompress 備註:對於資源圖片直接使用:tiny壓縮 2 質量壓縮(1)原理:保持畫素的前提下改變圖片的位深及透明度,(即:通

Android開發UI佈局優化全面總結

Android開發最常見的問題之一是螢幕碎片化太嚴重,所以我們在寫佈局的時候儘量不能適應硬編碼去佈局。 佈局優化在開發過程中起到至關重要的作用。 1.合用weightSum屬性和layout_weig

Android效能優化冷啟動優化

1.什麼是冷啟動[啟動時間比較長]:在應用啟動前,系統沒有該應用的任何程序資訊。2.什麼是熱啟動[啟動時間比較短] :使用者按了返回鍵,又馬上重新啟動了此應用。3.冷啟動會走application這個類,熱啟動就不會走application這個類4.冷啟動流程5.冷啟動優化 

Android應用優化冷啟動優化

前言 事件發生在發包上線的前兩天,在某某雲進行移動測試時,提示冷啟動速度低於平均值的問題,之前自己也曾嘗試過優化,但是發現效果並不是很明顯,作為一個有追求的開發者,趁著有點空閒時間,要好好研究一下冷啟動優化問題。 App的啟動流程 我們可以瞭解一下官方文件《App startup time》對App啟動

KVM總結-KVM效能優化磁碟IO優化

前面講了KVM CPU(http://blog.csdn.net/dylloveyou/article/details/71169463)、記憶體(http://blog.csdn.net/dylloveyou/article/details/71338378)的優化,下面接著第三塊的內容,KVM磁

MySQL(二) —— MySQL效能優化 SQL語句優化

          SQL語句優化   MySQL優化的目的   1、避免出現頁面訪問錯誤:或由於資料庫連線超時 timeout 產生頁面5xx錯誤;或由於慢查詢造成頁面無法載入;或由於阻        塞造成資料無法提交;

【MySQL資料庫】效能優化索引及優化(一)

一、Mysql效能優化之影響效能的因素 1.商業需求的影響 不合理的需求造成的資源投入產出,這裡就用一個看上去很簡單的功能分析。需求:一個論壇帖子的總量統計,附加要求:實時更新。從功能上看來是非常容易實現的,執行一條select count(*)from表名就可以得到結果,但是如果我們採

Mysql效能優化資料型別優化

一、選擇正確的資料型別對於獲得高效能至關重要 1.1更小的通常更好 佔用更少的磁碟、記憶體和CPU快取 1.2儘量避免null 如果查詢中包含可為null的列,對Mysql來說更難優化,因為可為null的列使得索引、索引統計和值都更復雜。會使用更多的儲存空間. 2、整數和實數

MySQL 資料庫效能優化表結構優化

很多人都將 資料庫設計正規化 作為資料庫表結構設計“聖經”,認為只要按照這個正規化需求設計,就能讓設計出來的表結構足夠優化,既能保證效能優異同時還能滿足擴充套件性要求。殊不知,在N年前被奉為“聖經”的資料庫設計3正規化早就已經不完全適用了。這裡我整理了一些比較常見的資料庫表結構設計方面的優化技巧,希

Android性能優化圖片壓縮優化

bit nat nsa lose 3.2 透明度 之間 修復 基準 1 分類Android圖片壓縮結合多種壓縮方式,常用的有尺寸壓縮、質量壓縮、采樣率壓縮以及通過JNI調用libjpeg庫來進行壓縮。 參考此方法:Android-BitherCompress 備註:對於資源

Android應用優化程式碼檢測優化

前言 最近換了新的公司,面對新的程式碼大家都有不同的熟悉過程和方法。在我的角度來說,利用程式碼檢測工具,可以更直接地去熟悉程式碼邏輯和業務邏輯,表現得自己去程式碼質量很有追求,最重要當然是在公司的任務管理工時上面顯得自己積極向上啦。不過在修改程式碼之前,你要根據專案的分工、明確在公司

Java效能優化作業系統層面優化

目前常用的作業系統分為:windows,Unix(Linux),我們會分別介紹在不同系統上的調優。 一,概念 效能監控:一種以非侵入方式收集或檢視應用執行效能資料的活動,通常是指在生產,質量評估, 開發環境中實施的帶有預防或主動性的活動。 效能分析:一種以侵入方式收集執行效能資料的活

MySQL(二) —— MySQL效能優化 SQL語句優化

          SQL語句優化 MySQL優化的目的   1、避免出現頁面訪問錯誤:或由於資料庫連線超時 timeout 產生頁面5xx錯誤;或由於慢查詢造成頁面無法載入;或由於阻        塞造成資料無法提交;   2、增加資料庫的穩定性:避免由於低效查詢

資料庫效能優化SQL語句優化

避免使用HAVING子句, HAVING 只會在檢索出所有記錄之後才對結果集進行過濾. 這個處理需要排序,總計等操作. 如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷. (非oracle中)on、where、having這三個都可以加條件的子句中,on是最先執行,where次之,having最

Mysql效能優化快取引數優化

資料庫屬於 IO 密集型的應用程式,其主要職責就是資料的管理及儲存工作。而我們知道,從記憶體中讀取一個數據庫的時間是微秒級別,而從一塊普通硬碟上讀取一個IO是在毫秒級別,二者相差3個數量級。所以,要優化資料庫,首先第一步需要優化的就是 IO,儘可能將磁碟IO轉化為記憶體IO。本文先從 MySQL 資料庫IO相

MySQL 資料庫效能優化快取引數優化

https://blog.csdn.net/truelove12358/article/details/51956356   部落格 學院 下載 圖文課 論壇 APP 問答 商城 VIP會員 活動 招聘 ITe