Android UI性能優化詳解

設計師,開發人員,需求研究和測試都會影響到一個app最後的UI展現,全部人都很樂於去建議app應該怎麼去展現UI。UI也是app和用戶打交道的部分,直接對用戶造成品牌意識,須要仔細的設計。不管你的app UI是簡單仍是複雜,重要的是性能必定要好。html

UI性能測試android

性能優化都須要有一個目標,UI的性能優化也是同樣。你可能會以爲「個人app加載很快」很重要,但咱們還須要瞭解終端用戶的指望,是否能夠去量化這些指望呢?咱們能夠從人機交互心理學的角度來考慮這個問題。研究代表,0-100毫秒之內的延遲對人來講是瞬時的,100-300毫秒則會感受明顯卡頓,300-1000毫秒會讓用戶以爲「手機卡死了」,超過1000ms就會讓用戶想去幹別等事情了。ios

這是人類心理學最基礎的理論,咱們能夠從這個角度去優化頁面/view/app的加載時間。 Ilya Grigorik 有一個很棒的演講,是關於搭建1000毫秒內加載完成移動網站的。若是你的網頁能在1秒內加載好,就超過了人類感知的預期,你的用戶必定會感受很滿意。還有研究代表,若是網頁在3-4秒內還沒加載出任何內容,用戶就會放棄了。把這些數據應用到app的加載,不難明白加載時間是越短越好。這篇文章主要關注UI的加載時間。固然UI性能優化還會涉及到其餘方面,好比必需在後臺運行到任務,要從服務器下載一個文件等等,這些咱們在後面的文章再聊。git

卡頓(Jank)程序員

內容的快速加載很重要,渲染的流暢性也很重要。android團隊把滯緩,不流暢的動畫定義爲jank,通常是因爲丟幀引發的。安卓設備的屏幕刷新率通常是60幀每秒(1/60fps=16.6ms每幀),因此你想要渲染的內容能在16ms內完成十分關鍵。每丟一幀,用戶就會感受的動畫在跳動,會出現違和感。爲了保證動畫的流暢性,咱們接下來看下從哪些方面優化可讓內容在16ms內渲染完成,同時分析一些常見的致使UI卡頓的問題。github

android設備的UI渲染性能算法

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

  • 在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 

那究竟是什麼觸發了deliverInputEvents呢?這實際上是用戶在點擊屏幕,強制ListView重繪全部的view。這部分影響是CPU,咱們接下來簡單看下這時候CPU都在幹啥。

Systrace和CPU對渲染的影響

若是你頻繁的感受到卡頓,可是在繪製或者surfaceflinger部分看不到什麼明顯的異常,這時候能夠嘗試看下CPU在處理什麼事情,在Systrace的頂部能夠看到這部分的數據。若是你能大概猜到是哪部分的邏輯影響了繪製,能夠先把這部分代碼註釋掉試試。山羊app裏有個選項能夠開啓Fibonacci延遲。打開以後,app在每一行數據渲染的時候都會計算一個很大的Fibonacci值。用膝蓋想都知道這時CPU會變得很忙。因爲計算是在主線程作的,會妨礙的view的渲染,理所固然就致使裏丟幀,滑動也會變的很卡。圖4-21裏顯示的log就能看到這種狀況下的丟幀。咱們再深挖一點看能不能經過Systrace定位到計算Fibonacci數的代碼。

咱們再重頭看下trace數據,圖4-31裏是沒有優化過的山羊app在Nexus 6上跑的數據。

圖4-31 

展現作了一些修改,CPU和surfaceflinger之間的一些線被去掉了。這個trace裏看不到卡頓,surfaceflingers每16ms的間隔很均勻。RenderThread和每一行view填滿buffer的表現也很正常。和CPU那一行數據對比一下,能夠發現一個新規律。當RenderThread在繪製layout的時候,CPU1正在運行一個藍色的任務(注意咱們看的是窄一點的CPU1,不是CPU1:C-State)。當山羊app的view正在被measure的時候,CPU0有一個相應的紫色的行爲。view的layout和繪製是由兩個CPU完成的。注意X軸上的點擊是每隔10ms發生的,這裏每一個行爲都沒有超過2-4ms。

當咱們加入費時的Fibonacci計算以後,Systrace的結果看起來就很不同了。(圖4-32)

圖4-32 

從Systrace裏能看到不少卡頓,在相同的100ms時間範圍內,surfaceflinger就畫了三幀(上面不卡頓的狀況畫了7幀)。能夠看到RenderThread繪製view仍是很快的(從圖中能夠看出,藍色的RenderThread是在CPU0上運行的)。可是,measure view的時候,Fibonacci的遞歸計算就致使了問題。山羊app進程那一行花了大部分的時間在obtainView的狀態,而不是measure。同時能夠看到CPU1上紫色對應的山羊進程再也不是2-4ms寬了,變成了2-17ms寬。Fibonacci計算每次大概用了13-17ms,對app的繪製性能產生了很大的影響。

Systrace更新-I/O 2015

在2015年Google I/O大會上,google發佈了新版本的systrace,上面提到的分析數據變的更簡單了。在圖4-27裏,我把每一幀的更新都高亮出來了。在新版本的systrace(圖4-33)裏,每一幀都是由一個帶F的小圓圈標示的。正常渲染的幀會有綠點,慢幀則是黃色或者紅色。選擇一個點,而後按下m就能夠高亮某一幀,分析起來更方便。

圖4-33 

新版本的systrace對於正在發生的行爲也有更清晰的描述了。在圖4-33中,幀的渲染時間是18.181ms,是用黃色標示的,若是有不少幀超過了16ms就會致使卡頓了。在trace文件下方的描述信息面板上(圖4-34),能夠看到警告信息,說個人app在重用ListView的item,而不是建立新的item,這樣拖慢了view inflation。

圖4-34 

在systrace裏能夠看到其它相似的警告,形狀像泡泡或是點,屏幕右邊的警告面板也列出了這些信息(圖4-35)。

圖4-35 

這些新功能讓Systrace診斷UI問題更加簡單了。

第三方工具

每一個大的芯片廠商都有本身的GPU評測工具,能夠幫助發現更多渲染時遇到瓶頸的信息。這些工具對一些特定的芯片更有針對性,信息也更多。能夠幫你針對不一樣的GPU作更深度的優化。Qualcomm,NVIDIA和Intel都提供了這些開發者工具,有興趣的能夠本身試下。

感知優化

上面的內容都是在討論怎麼經過測試,調試,優化佈局來讓UI的體驗更快。其實還有另一個辦法讓你的app UI更快:讓用戶感受更快。固然做爲開發者要儘量優化本身的代碼,view,overdraw和其它全部可能會影響渲染性能的地方,上面這些都作了以後,再考慮下面這些能讓用戶以爲你的app更快的方法。

人類大腦工做的方式頗有意思,經過改變大腦對等待的感知,可讓你的用戶感受延遲變短了。雜貨店的老闆都會在走廊上放一些沒用的雜誌,就是爲了讓客戶有東西能夠看,感受等待的時間就會短一些。若是在向用戶展現內容的時候增長一些過渡效果,見效明顯。這就像一個小魔術同樣讓用戶感受體驗變的更快了,歸根結底重要的是用戶以爲你的app有多快。這個技巧實現起來也有點取巧,有時候這種感知的優化甚至會獲得相反的效果,作A/B test來確保你的優化對用戶來講真的有效。

loading菊花:優缺點

loading菊花,進度條,沙漏圖標,和其它全部表示等待的方式都存在好久了。這些均可以讓app的內容過渡變得更快。好比在app里加一個進度條,加載的時候播放一個進度的動畫來讓用戶等待。研究代表使用一個帶有動畫的滑動條的時候用戶會感受更舒服。快速旋轉的loading菊花也讓用戶感受等待的時間更短。

可是,有延遲的時候,加個菊花並不老是有效的。iOS app Polar的開發者發現他們的app渲染一個view的時候有一點延遲。他們第一反應是在頁面里加了一個菊花告訴用戶頁面正在渲染內容,但效果不如預期。用戶開始反饋app變慢了,等待頁面加載的時間變長了(其實app沒有變慢,不過是加了一個菊花)。加了個等待的標識以後讓用戶明顯的感受到他們在等。取消菊花以後,用戶感受app又變快了(開發者僅僅是改變了菊花)。經過改變用戶對等待的感知,可讓用戶以爲app變快了。Facebook也遇到過相似的問題:使用本身定製的菊花讓用戶感受更慢,用默認菊花感受更快。

增長菊花最好讓用戶測試下他們的真實感覺。通常來講,當等待的時間稍微有點長的時候,增長菊花是能夠接受的:好比打開一個新頁面或者從網上下載一張圖片。若是延遲很短(通常來講小於一秒),就應該考慮去掉菊花了。這種狀況下應該讓用戶以爲他們並無在等。

用動畫來抵消等待的時間

點擊後看到一個空白的屏幕會讓用戶感受在等待。就是這個緣由讓瀏覽器在點擊連接,新頁面刷出來以前都是展現舊的頁面。在手機app裏,通常來講咱們不但願讓用戶停留在老的頁面上,一個快速的切換動畫能夠爭取到足夠的時間讓下一個頁面準備就緒。能夠觀察下你最經常使用的android app,當頁面切換的時候有多少從邊上或者底部出現的動畫。

瞬時更新的小謊話

若是你的用戶在頁面上作了更新數據的操做,即便數據還沒抵達服務器,能夠立刻把用戶看到的數據更新掉(固然開發者要保證這些數據能100%抵達服務器)。好比說,你在Instagram上點了贊,頁面上立刻就更新了讚的狀態,其實讚的狀態甚至可能尚未更新到服務器。Instangram的開發者管這叫「行爲最優化」,狀態的更新要幾秒後才能到服務器並對網站的用戶可見(網速很差的時候可能要幾分鐘),可是更新最後都會成功,等待服務器返回成功實際上是不必的。移動端用戶通常都不但願在等待,只要最後能成功就好。

瞬時更新的另外一個好處是,用戶會感受你的app在網速或者信號很差(火車通過隧道)的時候也能正常工做。FlipBoard就作過一個離線發送網絡請求的框架,能夠很方便的應用到更新UI。

另外一個優化的小技巧是提早上傳。對於像Instagram這種app來講,上傳大量的圖片會增長主線程的延遲,提早開始上傳這些圖片會是個好辦法。Instagram發現發一個新post是慢在上傳圖片這一步,因此Instagram就在用戶在圖片上添加文字的間隙開始上傳圖片了,圖片被真正發佈到服務器以前就已經傳好了。用戶只要一點擊Post按鈕,就只須要上傳文本和建立post的命令了,這樣就會讓用戶感知很是快。Instagram在遇到「是否要添加菊花」這個問題時,他們的答案是經過改變架構的方式永遠的杜絕菊花。

提高感知體驗的小提示

當app的速度經過優化代碼或者view的優化提高以後,你能夠用秒錶來測試下結果。有些感知是能夠用秒錶測量的(Instagram的例子),有些則不能(菊花的例子)。當常規的分析或者測量工具不可靠的時候,須要讓用戶來真正的體驗這些優化效果。能夠作一些可用性測試,增長測試的範圍,A/B測試,這些才能真正的讓你確認你的優化是讓用戶更開心仍是更沮喪。

總結

Android app的用戶體驗直接跟屏幕上展示的內容相關。若是app的內容加載很慢或者滑動不夠流暢,用戶的感知就是負面的。在這篇文章,咱們講了如何優化view樹形結構,看是否扁平或者簡化view等等。咱們還講了怎麼檢測解決overdraw的問題。還有一些須要深度分析的優化(像CPU致使的問題),systrace很適合發現和解決這種卡頓問題。最後是一些讓你的app感受更快的小技巧,好比把CPU或者網絡相關的任務延後處理,不要影響繪製渲染。

相關文章
相關標籤/搜索