設(shè)計師沼瘫,開發(fā)人員,需求研究和測試都會影響到一個app最后的UI展示咙俩,所有人都很樂于去建議app應(yīng)該怎么去展示UI耿戚。UI也是app和用戶打交道的部分,直接對用戶形成品牌意識阿趁,需要仔細(xì)的設(shè)計膜蛔。無論你的app UI是簡單還是復(fù)雜,重要的是性能一定要好脖阵。
UI性能測試
性能優(yōu)化都需要有一個目標(biāo)皂股,UI的性能優(yōu)化也是一樣。你可能會覺得“我的app加載很快”很重要命黔,但我們還需要了解終端用戶的期望呜呐,是否可以去量化這些期望呢?我們可以從人機(jī)交互心理學(xué)的角度來考慮這個問題纷铣。研究表明卵史,0-100毫秒以內(nèi)的延遲對人來說是瞬時的,100-300毫秒則會感覺明顯卡頓搜立,300-1000毫秒會讓用戶覺得“手機(jī)卡死了”以躯,超過1000ms就會讓用戶想去干別等事情了。
這是人類心理學(xué)最基礎(chǔ)的理論啄踊,我們可以從這個角度去優(yōu)化頁面/view/app的加載時間忧设。 Ilya Grigorik 有一個很棒的演講,是關(guān)于搭建1000毫秒內(nèi)加載完成移動網(wǎng)站的颠通。如果你的網(wǎng)頁能在1秒內(nèi)加載好址晕,就超過了人類感知的預(yù)期,你的用戶一定會感覺很滿意顿锰。還有研究表明谨垃,如果網(wǎng)頁在3-4秒內(nèi)還沒加載出任何內(nèi)容启搂,用戶就會放棄了。把這些數(shù)據(jù)應(yīng)用到app的加載刘陶,不難明白加載時間是越短越好胳赌。這篇文章主要關(guān)注UI的加載時間褪子。當(dāng)然UI性能優(yōu)化還會涉及到其他方面习瑰,比如必需在后臺運(yùn)行到任務(wù),要從服務(wù)器下載一個文件等等畔柔,這些我們在后面的文章再聊纷责。
卡頓(Jank)
內(nèi)容的快速加載很重要捍掺,渲染的流暢性也很重要。android團(tuán)隊(duì)把滯緩再膳,不流暢的動畫定義為jank挺勿,一般是由于丟幀引起的。安卓設(shè)備的屏幕刷新率一般是60幀每秒(1/60fps=16.6ms每幀)饵史,所以你想要渲染的內(nèi)容能在16ms內(nèi)完成十分關(guān)鍵满钟。每丟一幀,用戶就會感覺的動畫在跳動胳喷,會出現(xiàn)違和感湃番。為了保證動畫的流暢性,我們接下來看下從哪些方面優(yōu)化可以讓內(nèi)容在16ms內(nèi)渲染完成吭露,同時分析一些常見的導(dǎo)致UI卡頓的問題吠撮。
android設(shè)備的UI渲染性能
早期android用戶抱怨最多的就是UI,尤其是觸碰反饋和動畫流暢度讲竿,感覺都很卡泥兰。后來隨著android系統(tǒng)逐漸成熟,開發(fā)人員也投入了大量的時間和精力讓交互變的流暢起來题禀。下面列舉一些不同系統(tǒng)版本所帶來的提升:
在Gingerbread或者更早的設(shè)備上鞋诗,屏幕完全是由軟件繪制(CPU繪制)的(不需要GPU的參與)。后來隨著屏幕尺寸變大和像素的提升迈嘹,純粹靠軟件繪制遇到了瓶頸削彬。
Honeycomb加入了平板設(shè)備,進(jìn)一步增加了屏幕尺寸秀仲。同時出于性能考慮融痛,加入了GPU芯片,app在渲染內(nèi)容的時候多了一個GPU硬件加速的選項(xiàng)神僵。
對于針對Ice Cream Sandwich或者更高系統(tǒng)的設(shè)備雁刷,GPU硬件加速是默認(rèn)打開的。將軟件繪制(CPU)的壓力大部分轉(zhuǎn)移到了GPU上保礼。
Jelly Bean 4.1 (and 4.2) “Project Butter” 做了近一步的提升來避免卡頓沛励,通過引入VSYNC機(jī)制和增加額外的frame buffer(vsync和frame buffer的解釋可以參考這篇文章)责语,運(yùn)行 Jelly Bean的設(shè)備丟幀的概率變的更小。引入這些機(jī)制的同時侯勉,android開發(fā)團(tuán)隊(duì)還加入了一些優(yōu)秀的工具來測量屏幕的繪制鹦筹,開發(fā)者可以使用這些工具來檢測VSYNC buffering和卡頓铝阐。
我們從普通開發(fā)者的角度址貌,來逐一看下這些提升和相關(guān)的測量工具。我們的目標(biāo)很明顯:
屏幕繪制低延遲
保證流程穩(wěn)定的幀率來避免卡頓
當(dāng)android開發(fā)團(tuán)隊(duì)引入這些UI流暢性的提升時徘键,他們需要能量化這些提升的工具练对。經(jīng)由他們的努力,這些工具都打包進(jìn)了SDK以方便開發(fā)者們來檢測UI相關(guān)的性能問題吹害。接下來我們就使用這些工具來優(yōu)化幾個demo程序螟凭。
搭建Views
大家應(yīng)該都對android studio里xml布局編輯器很熟悉了,知道怎么在android studio(Eclipse)中搭建和檢測View結(jié)構(gòu)它呀。下圖是一個簡單的app view螺男,包含一些套嵌的子view。搭建這些view的時候纵穿,一定要留意屏幕右上角的組件樹(Component Tree)下隧。套嵌的子view越深,組件樹就越復(fù)雜谓媒,渲染起來也就越費(fèi)時間淆院。
對于app里的每一個view,android系統(tǒng)都會經(jīng)過三部曲來渲染:measure句惯,layout土辩,draw∏酪埃可以在腦中回想下你搭建的view的xml布局文件結(jié)構(gòu)拷淘,measure從最頂部的節(jié)點(diǎn)開始,順著layout樹形結(jié)構(gòu)依次往下:測量每個view需要在屏幕當(dāng)中展示的尺寸大兄腹隆(上圖當(dāng)中:LinearLayout启涯,RelativeLayout,LinearLayout邓厕;然后是textView0和LinearLayout Row1點(diǎn)分支逝嚎,該分支又有另外3個子節(jié)點(diǎn))。每個子節(jié)點(diǎn)都需要向自己的父節(jié)點(diǎn)提供自己的尺寸來決定展示的位置详恼,遇到?jīng)_突的時候补君,父節(jié)點(diǎn)可以強(qiáng)制子節(jié)點(diǎn)重新measure(由此可能導(dǎo)致measure的時間消耗為原來的2-3倍)。這就是為什么扁平的view結(jié)構(gòu)會性能更好昧互。節(jié)點(diǎn)所處位置越深挽铁,套嵌帶來的measure越多伟桅,計算就會越費(fèi)時。我們來看一些具體的例子叽掘,看measure是怎么影響渲染性能的楣铁。
Remeasureing Views(重新測量views)
并不是只有發(fā)生錯誤的時候才會觸發(fā)remeasure。RelativeLayout經(jīng)常需要measure所有子節(jié)點(diǎn)兩次才能把子節(jié)點(diǎn)合理的布局更扁。如果子節(jié)點(diǎn)設(shè)置了weight屬性盖腕,LinearLayout也需要measure這些節(jié)點(diǎn)兩次,才能獲得精確的展示尺寸浓镜。如果LinearLayout或者RelativeLayout被套嵌使用溃列,measure所費(fèi)時間可能會呈指數(shù)級增長(兩個套嵌的views會有四次measure,三個套嵌的views會有8次的measure)膛薛√可以看下面圖4-9里面一個夸張點(diǎn)的例子。
一旦view開始被measure哄啄,該view所有的子view都會被重新layout雅任,再把該view傳遞給它的父view,如此重復(fù)一直到最頂部的根view咨跌。layout完成之后沪么,所有的view都被渲染到屏幕上。需要特別注意到是虑润,并不是只有用戶看得見的view才會被渲染成玫,所有的view都會。后面我們會看下“屏幕重復(fù)繪制”的問題拳喻。app擁有的views越多哭当,measure,layout冗澈,draw所花費(fèi)的時間就越久钦勘。要縮短這個時間,關(guān)鍵是保持view的樹形結(jié)構(gòu)盡量扁平亚亲,而且要移除所有不需要渲染的view彻采。移除這些view會對加速屏幕渲染產(chǎn)生明顯的效果。理想情況下捌归,總共的measure肛响,layout,draw時間應(yīng)該被很好的控制在16ms以內(nèi)惜索,以保證滑動屏幕時UI的流暢特笋。
雖然可以通過xml文件查看所有的view,但不一定能輕易的查出哪些view是多余的巾兆。要找到那些多余的view(增加渲染延遲的view)猎物,可以用Android studio monitor里的Hierarchy Viewer工具虎囚,可視化的查看所有的view。(monitor是個獨(dú)立的app蔫磨,下載android studio的時候會同時下載)
Hierarchy Viewer
Hierarchy Viewer可以很方便可視化的查看屏幕上套嵌view結(jié)構(gòu)淘讥,是查看你的view結(jié)構(gòu)的實(shí)用工具。這個工具包含在android studio monitor當(dāng)中堤如,需要運(yùn)行在帶有開發(fā)者版本的android系統(tǒng)的設(shè)備上蒲列。后續(xù)所有的view和屏幕截圖都來自一款三星的Note II設(shè)備,系統(tǒng)版本是Jelly Bean煤惩。在老的設(shè)備(處理器慢)上測試渲染性能嫉嘀,更容易發(fā)現(xiàn)問題。
如圖4-2所示魄揉,打開Hierarchy Viewer之后,會看到幾個窗口:左邊的窗口列出了連上你電腦的android設(shè)備和設(shè)備上所有運(yùn)行的進(jìn)程拭宁÷逋耍活躍的進(jìn)程是粗體展示的。第二個tab某一個編譯版本的詳情(后面細(xì)說)杰标。中間的部分是可縮放的view的樹形圖兵怯。點(diǎn)擊某一個view能看到在設(shè)備上展示的樣子和一些額外的數(shù)據(jù)。右邊有兩個view:樹形結(jié)構(gòu)總覽和布局view腔剂。樹形結(jié)構(gòu)總覽顯示了整個view的樹形結(jié)構(gòu)媒区,里面有一個方塊顯示了中間窗口在整個樹形結(jié)構(gòu)當(dāng)中所處的位置。布局view當(dāng)中深紅色高亮的區(qū)域表示所選中的view被繪制的部分(淺紅色展示的是父view)掸犬。
在中間的這個窗口袜漩,你可以點(diǎn)擊任何一個view來查看該view在android設(shè)備屏幕上的展示。點(diǎn)擊樹形圖工具欄里紅綠紫三色的維恩圖圖標(biāo)湾碎,還能展示子view的數(shù)量宙攻,和measure,layout介褥,draw三部曲所花費(fèi)的時間座掘。這個時間是被選擇的view及其所有子節(jié)點(diǎn)所花費(fèi)時間的總和。(圖4-3中柔滔,我選擇了最頂部的view來獲取整個view結(jié)構(gòu)的時間)
最頂部的view總共包含181個view溢陪,measure的總時間為3.6ms,layout是7ms睛廊,draw花了14.5ms(總共大約25ms)形真。要縮短渲染這些view的總時間,我們先看下app的樹形結(jié)構(gòu)圖預(yù)覽喉前,看看所有的view是怎么拼湊到一起的没酣。從樹形結(jié)構(gòu)圖上可以看出屏幕里有非常多的view王财,樹的結(jié)構(gòu)比較扁平。前面說過裕便,扁平的結(jié)構(gòu)性能好绒净,樹的深度對渲染的性能會產(chǎn)生很大的影響。我們的結(jié)構(gòu)雖然是扁平的偿衰,卻依然花費(fèi)了26ms的時間來渲染挂疆,說明扁平的結(jié)構(gòu)也有可能會卡頓,也需要去考慮怎么優(yōu)化下翎。
排查一個新聞類app的樹形結(jié)構(gòu)缤言,大致可以看三個區(qū)域:頭部(底部藍(lán)色的方框),文章列表(兩個橙色的方框表示兩個不同的tab)视事,單篇文章的view是用紅色方框來標(biāo)注的胆萧。內(nèi)部標(biāo)題view的結(jié)構(gòu)重復(fù)出現(xiàn)了九次,5個在上面橙色的方框內(nèi)俐东,4個在下面的方框內(nèi)跌穗。最后,我們可以看到從邊上拉出來的導(dǎo)航欄是用底部綠色的方框標(biāo)出來的虏辫。頭部用了22個view蚌吸,兩個文章列表個用了67和44個view(每個標(biāo)題部分使用了13個view),導(dǎo)航抽屜使用了20個砌庄。這樣我們還剩下18個view沒有計算在內(nèi)羹唠。剩下的這些view其實(shí)是在滑動手勢動畫過程當(dāng)中生成的。很顯然娄昆,view的數(shù)量很多佩微,要做到不卡頓要讓view的繪制非常高效才行。
仔細(xì)看下標(biāo)題部分稿黄,一個標(biāo)題是由13個view組成的喊衫。每個標(biāo)題的結(jié)構(gòu)有5層之深,一共花費(fèi)0.456ms來measure杆怕,0.077ms來layout族购,2.737ms來draw。第五層是通過第四層的兩個RelativeLayouts來連接的(藍(lán)色高亮)陵珍,這些又是通過第三層的另一個RelativeLayout來連接的(綠色高亮)寝杖。如果我們把第四第五層的view都移到第三層來,我們可以少渲染一整層互纯。而且我之前解釋過瑟幕,RelativeLayout里的measure都會發(fā)生兩次,套嵌的view會導(dǎo)致measure時間的增加。
現(xiàn)在只盹,你可能已經(jīng)注意到了每個view里紅色辣往,黃色和綠色的圓圈。它們表示該view在那一層樹形結(jié)構(gòu)里measure殖卑,layout和draw所花費(fèi)的相對時間(從左到右)站削。綠色表示最快的前50%,黃色表示最慢的前50%孵稽,紅色表示那一層里面最慢的view许起。顯然,紅色的部分是我們優(yōu)先優(yōu)化的對象菩鲜。
再看下文章標(biāo)題的樹形結(jié)構(gòu)园细,繪制最慢的view是右上角的ImageView。順著ImageView一直找到文章父view接校,父view是通過兩個RelativeLayouts來連接的(這里增加了measure的時間)猛频,然后是3個沒有子節(jié)點(diǎn)的view(在最底部)。這3個view可以優(yōu)化合并成一個view馅笙,這樣能減少兩個layer的渲染伦乔。
我們再看另一個新聞類app是怎么來減少標(biāo)題view里面的子view數(shù)量的。從圖4-6里能看到一個和圖4-5類似的樹形結(jié)構(gòu)圖董习。
圖4-6里的標(biāo)題view也有RelativeLayouts(綠色的部分)的問題,一共消耗了1.275ms的measure時間爱只,layout用了0.066ms皿淋,draw 3.24ms(總共是4.6ms)。在這些數(shù)據(jù)基礎(chǔ)上恬试,我們再做一些調(diào)整窝趣,加入一個更大的圖片展示和分享按鈕,但是整個樹形結(jié)構(gòu)變得扁平一點(diǎn)(如圖4-7所示)训柴。
再看下標(biāo)題view的渲染時間(三層的結(jié)構(gòu))哑舒,只用了4.2ms!雖然展示了更大的內(nèi)容幻馁,但節(jié)省了400ms洗鸵!
為了更好的了解這部分的優(yōu)化,我們再看另一個例子app仗嗦。這個例子會展示一個山羊圖片等列表膘滨。界面使用了幾種不同的layout方式,性能差的和性能好的都有稀拐。仔細(xì)的查看這些布局火邓,然后一步步優(yōu)化它們,我們就能清楚的理解怎么去優(yōu)化一個app的渲染性能了。我們分幾步來進(jìn)行優(yōu)化铲咨,每一步改變都可以通過Hierarchy View可視化的查看躲胳。每換一種layout方式,xml渲染的性能要么變好纤勒,要么變差坯苹。我們先從性能差的布局方式開始。先快速的掃一眼圖4-8里的Hierarchy View踊东。
這個簡單的app里有59個view北滥。但是和圖4-4里的app不同,這個app的樹形結(jié)構(gòu)更扁平闸翅,水平方向的view更多一些再芋。疊加的view越多,渲染就會越費(fèi)時坚冀,減少view樹形結(jié)構(gòu)的深度济赎,app每一幀的渲染就會變快。
藍(lán)色方框里面的view是action bar记某。橘色方框里的是屏幕頂部的text box司训,紫色方框里展示的是山羊的詳細(xì)信息(有6個這種view)。紅色方框標(biāo)示了7個view液南,每個都增加了樹形結(jié)構(gòu)的深度壳猜。我們仔細(xì)看些這7個view其中三個的remeasure數(shù)據(jù)(圖4-9)。
當(dāng)設(shè)備開始measure views的時候滑凉,先從右邊的子views開始统扳,然后到左邊的父views。右邊ListView包含6行數(shù)據(jù)畅姊,一共37個view咒钟,花了0.012ms來measure。把這個ListView加到中間的LinearLayout之后若未,變成38個views朱嘴。有意思的是,measure的時間由于remeasure被觸發(fā)粗合,瞬間跳到了18.109ms萍嬉,是原來的三個數(shù)量級。LinearLayout左邊的RelativeLayout使得measure的時間再次翻倍到33.739ms舌劳。再依次往左繼續(xù)觀察(圖4-8里紅色方框部分)帚湘,measure的時間疊加到了68ms。但是只要移除上面的一個LinearLayout甚淡,measure的時間瞬間降到了1ms大诸。我們可以移除更多的層讓樹形結(jié)構(gòu)更扁平一些捅厂,這樣我們可以得到圖4-10里的結(jié)果,層數(shù)減少到了3層资柔。
我們可以繼續(xù)看下山羊信息到row展示部分焙贷,來繼續(xù)減少view結(jié)構(gòu)的深度。每一行山羊信息有6個view贿堰,一個有6行數(shù)據(jù)在屏幕中展示(圖4-8中有一行數(shù)據(jù)是用紫色方框高亮的)辙芍。我們用Hierarchy View看下一行view的結(jié)構(gòu)是怎么樣的(圖4-11),先看下左邊兩個view(一個LinearLayout羹与,一個RelativeLayout)故硅,這兩個view唯一的作用就是加深了樹機(jī)構(gòu)的深度。LinearLayout連接了RelativeLayout纵搁,但并沒有展示其他什么內(nèi)容吃衅。
因?yàn)镽elativeLayout會measure兩次(我們現(xiàn)在關(guān)注優(yōu)化measure的時間),我們先移除RelativeLayout(圖4-12)腾誉。這樣樹形結(jié)構(gòu)的深度從4減到了3徘层,渲染立馬快了一些。
但效果還并不理想利职。我們繼續(xù)移除LinearLayout趣效,同時調(diào)整下RelativeLayout來展示整個row的信息(圖4-13),這樣深度近一步減少到了2猪贪。渲染又快了0.1ms跷敬。這樣看來優(yōu)化的途徑有很多種,多嘗試總是有好處的(看下表格4-1里的結(jié)果)热押。
每一行減少大約1ms的時間干花,我們一共可以節(jié)省6ms的渲染時間。如果你的app有卡頓楞黄,或者你通過工具檢測到每次渲染接近16ms了,減少6ms的時間當(dāng)然會讓你的app更快一點(diǎn)抡驼。
View的重用
如果一個程序員面向?qū)ο缶幊探?jīng)驗(yàn)豐富鬼廓,他就會盡可能重用創(chuàng)建的view(而不是每次都創(chuàng)建)。拿上面山羊app作為例子致盟,其實(shí)每一行展示的layout都是重用的碎税。如果xml文件里最外層的view只是用來承載子view的,那這個view只不過是增加了view結(jié)構(gòu)的深度馏锡,這種情況下雷蹂,我們可以移除這個view,用一個merge標(biāo)簽來代替杯道。這種方式可以移除樹形結(jié)構(gòu)里多余的層匪煌。
大家可以從github上下載這個山羊app練習(xí)下,改變里面xml文件的布局方式,再用Hierarchy View工具看下渲染時間的變化萎庭。
Hierarchy Viewer(不止是樹形結(jié)構(gòu)圖)
Hierarchy Viewer還有一個功能霜医,可以幫助開發(fā)者發(fā)現(xiàn)overdraw(重復(fù)的繪制)。從左到右看下樹形結(jié)構(gòu)窗口的選項(xiàng)驳规,可以發(fā)現(xiàn)這些功能:
把view的樹形結(jié)構(gòu)圖保存為png圖片肴敛。
導(dǎo)出為photoshop的格式。
重新加載一個view(第二個紫色樹形按鈕)吗购。
在另一個窗口里打開較大的view結(jié)構(gòu)圖医男,還可以設(shè)置背景色來發(fā)現(xiàn)重復(fù)繪制。
讓一個view的繪制失效(有條紅線的按鈕)捻勉。
讓view重新layout镀梭。
讓view生出draw命令到logcat(紫色樹形按鈕到第三個用處)。這樣可以查看繪制到底觸發(fā)了哪些opengl行為贯底。這個功能對opengl的專家做深度優(yōu)化比較有用丰辣。
Hierarchy Viewer對于優(yōu)化app view的樹形結(jié)構(gòu)重要性不言而喻了,很可能會幫你節(jié)省幾十毫秒的繪制時間禽捆。
資源縮減
在我們把a(bǔ)pp的view結(jié)構(gòu)變扁平笙什,view的總數(shù)量減少之后,我們還可以嘗試減少每個view里面使用的資源數(shù)量胚想。2014年的時候琐凭,Instagram把標(biāo)題欄里的資源數(shù)量從29減少到了8個。他們測量后發(fā)現(xiàn)app的啟動時間增加了10%-20%(因設(shè)設(shè)備而異)浊服。主要是通過資源上色的方式來進(jìn)行縮減统屈。比如只加載一個資源,然后在運(yùn)行的時候通過ColorFilter進(jìn)行上色牙躺。我們看下下面的例子是怎么個一個drawable上色的愁憔。
這樣一個資源文件就可以表示幾種不同的狀態(tài)了(加星或者不加星,在線或者離線等等)孽拷。
屏幕的重復(fù)繪制
每過幾年吨掌,就會有傳聞?wù)f某個博物館在用x光掃描一副無價的名畫之后,發(fā)現(xiàn)畫作的作者其實(shí)重用了老的畫布脓恕,在名畫的底下還藏著另一副沒有被發(fā)現(xiàn)的畫作膜宋。有時候,博物館還能用高級的圖像技術(shù)來還原畫布上的原作炼幔。android里面的view的繪制就是類似的情況秋茫。當(dāng)android系統(tǒng)繪制屏幕的時候,先畫父view乃秀,然后子view肛著,再是更深的子view等等圆兵。這會導(dǎo)致所有的view都被繪制到了屏幕上,就像畫家的畫布一樣策泣,這些view都被他們的子view覆蓋住了衙傀。
文藝復(fù)興時期,有很多偉大的畫家要等畫干了以后才能重用畫布萨咕。但在我們的高科技觸摸屏上统抬,屏幕重畫的速度要快幾個數(shù)量級,但是多次的重新繪制屏幕會使得繪制延遲變大危队,最終導(dǎo)致布局的卡頓聪建。重新繪制屏幕的行為叫做overdraw,下面我們會看下怎么檢測overdraw茫陆。
overdraw還帶來的另一個問題金麸,當(dāng)view內(nèi)容有更新的時候,之前繪制的view就失效了,view的每一個像素都需要重繪。android設(shè)備沒法判斷哪個view是可見的哪亿,所以只能繪制每個view的相關(guān)像素。類比上面畫家的例子棚瘟,畫家只能把老畫一幅幅還原出來,再一層層畫到畫布上喜最,最后再畫上最新的畫偎蘸。你的app如果有很多層,每一層的相關(guān)像素都需要繪制一遍瞬内。如果不小心迷雪,這些繪制就會帶來性能問題。
檢測overdraw
android提供了一些很好的工具來檢測overdraw虫蝶。Jelly Bean 4.2里章咧,開發(fā)者選項(xiàng)菜單里增加了Debug GPU Overdraw的選項(xiàng)。如果你用的是Jelly Bean 4.3 或者 KitKat 設(shè)備能真,在屏幕的左下角會有一個計數(shù)展示屏幕overdraw的程度慧邮。我親身試過這個工具對檢測overdraw十分有效。雖然有時候這個會多提示6-7次overdraw(發(fā)生的概率還不兄勐健)。
圖4-16中的截圖還是來自上面的山羊app耻矮。左下方可以看到overdraw的計數(shù)秦躯。屏幕中可以看到3個overdraw的計數(shù),其中開發(fā)者能控制的是主窗口的計數(shù)裆装。overdraw的計數(shù)是在左下方踱承。沒優(yōu)化過的app overdraw的次數(shù)是8.43倡缠,我們優(yōu)化過后可以降到1.38。導(dǎo)航欄overdraw的次數(shù)是1.2(菜單按鈕是2.4)茎活,也就是說文字和圖標(biāo)的overdraw貢獻(xiàn)了額外的20%昙沦。overdraw計數(shù)可以在不影響用戶體驗(yàn)的前提下,快速便捷的比較不同app的overdraw载荔,但沒辦法定位overdraw是哪里產(chǎn)生的盾饮。
另一種查看overdraw的方式是在Debug GPU overdraw菜單里選擇“Show Overdraw areas”選項(xiàng)。選擇之后懒熙,會在app的不同區(qū)域覆蓋不同的顏色來表示overdraw的次數(shù)丘损。比較屏幕上這些不同的顏色,可以快速方便的定位overdraw問題:
白色:沒有overdraw 藍(lán)色:1x overdraw(屏幕繪制了2次) 綠色:2x overdraw 淺紅色:3x overdraw 深紅色:4x或者更多overdraw
在圖4-17中工扎,可以看到山羊app優(yōu)化前后overdraw區(qū)域的變化徘钥。app的菜單欄優(yōu)化前后都沒有顏色(沒有overdraw),但android圖標(biāo)和菜單按鈕圖標(biāo)都是綠色的(2x overdraw)肢娘。山羊圖片等列表在優(yōu)化之前是深紅色的(4x以上的overdraw)呈础。優(yōu)化app 之后,只有checkbox和圖片區(qū)域是藍(lán)色(1x)的了橱健,說明至少3層overdraw被消滅掉了而钞!text和空白區(qū)域都沒有overdraw了。
通過減少view的數(shù)量(或者去移除重復(fù)繪制的view)畴博,app的渲染會更快笨忌。通過比較父view在優(yōu)化前后的繪制時間,可以發(fā)現(xiàn)優(yōu)化后帶來50%性能的提升俱病,由13.5ms降到6.8ms官疲。
Hierarchy Viewer當(dāng)中的overdraw
另一種查看app當(dāng)中overdraw的方式是把Hierarchy Viewer中的view的樹形結(jié)構(gòu)保存成photoshop識別的文檔(樹形view里的第二個選項(xiàng))。如果你沒有安裝photoshop亮隙,有幾個其他的免費(fèi)軟件也可以打開這個文檔途凫。打開文檔查看view,可以清楚看到不同layer里的overdraw溢吻。對于大部分的線上app维费,在一個白色背景上放上另一個白色背景很常見。聽起來還好促王,但這里其實(shí)有一次繪制是多余的犀盟,完全可以避免的。我們再看下山羊app蝇狼,所有overdraw圖片區(qū)域都放在了一張驢子的背景圖片上(替換了之前的白色背景)阅畴。之前的驢子看不到,是因?yàn)楸话咨尘皥D擋住了迅耘。移除掉之后就可以看到下面的驢子了贱枣,這樣我們就可以快速的定位哪里出現(xiàn)了overdraw监署。用GIMP打開文檔之后,app里所有可見的view的左邊都有一個小眼睛圖標(biāo)纽哥。在圖4-18中钠乏,可以看到我從最上面開始把view一個個隱藏起來了。在右邊的layout視圖中春塌,可以看到一些其他的全屏layout(都顯示了驢子的圖片)晓避。
在圖4-19中可以看到另一個逐步隱藏view的辦法。從最左邊的全屏圖片開始摔笤,到中間的圖片够滑,可以看到我們隱藏了兩行山羊的圖片展示,每一行下面的出現(xiàn)了一張拉伸的驢子的圖片吕世。在這些驢子圖片的下面是一張白色的背景圖(從最右邊的圖片可以看出)彰触。再移除這張白色背景可以看到一張大的驢子的圖片,在左下角命辖。再往下是另一張白色的全屏背景圖况毅。
KitKat里的overdraw
在KitKat或者更新的設(shè)備里,overdraw被大幅度的削減了尔艇。這項(xiàng)技術(shù)叫overdraw avoidance尔许,系統(tǒng)可以檢測發(fā)現(xiàn)簡單的overdraw場景(比如一個view完全蓋住了另一個view),然后自動移除額外的繪制终娃,應(yīng)用到上面的例子味廊,也就是說驢子那張大背景圖就不會去繪制了。這很明顯會極大的提高設(shè)備的繪制性能棠耕。但開發(fā)者還是要盡可能的避免額外的overdraw(為了更好的性能余佛,也為了能兼容Jelly Bean及更老的設(shè)備)。
Overdraw Avoidance和相關(guān)開發(fā)者工具
當(dāng)用上面提到的overdraw檢測工具時窍荧,KitKat的overdraw avoidance功能會被禁止辉巡,這只是為了方便你查看view的布局,和在設(shè)備上真正運(yùn)行的情況并不一樣蕊退。
分析卡頓(測量GPU的渲染性能)
在我們優(yōu)化過view的樹形結(jié)構(gòu)和overdraw之后郊楣,你可能還是感覺自己的app有卡頓和丟幀,或者滑動慢:卡頓還是存在瓤荔【辉椋可能高端機(jī)器上感覺不到卡頓,但低端機(jī)上還是可能會出現(xiàn)卡頓输硝。為了能獲取更全面的卡頓檢測信息塞栅,android在Jelly Bean及更新的系統(tǒng)里加入了一個GPU繪制開發(fā)者選項(xiàng)。能夠測出每一幀的繪制用了多少時間。你可以把測量出來的數(shù)據(jù)保存到一個logfile(adb shell dumpsys gfxinfo)放椰,或者在設(shè)備的屏幕上實(shí)時查看這些信息(只支持android 4.2+)。
我們快速來看下怎么分析愉粤,我比較喜歡在屏幕上直接展示GPU的渲染數(shù)據(jù)砾医,這樣感覺更直觀全面(logfile里面的數(shù)據(jù)很適合離線的詳細(xì)分析)。我們最好在不同的設(shè)備上都試一下衣厘。圖4-20展示的是Nexus 6運(yùn)行Lollipop(左邊)和Moto G運(yùn)行 KitKat(右邊)同時跑山羊app的GPU渲染數(shù)據(jù)如蚜。重點(diǎn)看下GPU測量圖表底部的水平綠條。它是設(shè)備16ms繪制一幀的分割線影暴,如果你有很多幀都超過了這條綠線错邦,那就表示有卡頓了。在下圖里可以看到Nexus6上有偶爾的卡頓型宙。出現(xiàn)在滑動到頁面底部的時候撬呢,播放里一個反彈的動畫。用戶體驗(yàn)不算太糟妆兑。每一次屏幕繪制(豎線)被分成四種顏色來表示額外的測量數(shù)據(jù):draw(藍(lán)色)魂拦,prepare(紫色),process(紅色)搁嗓,執(zhí)行(黃色)芯勘。在KitKat和更早的版本里,prepare的數(shù)據(jù)沒有獨(dú)立出來腺逛,包含在其他項(xiàng)里面(因此只有看到3種顏色)荷愕。
對比下Nexus 6和Moto G的GPU數(shù)據(jù)可以看出真機(jī)測試的重要性。圖4-20中棍矛,沒有優(yōu)化過的山羊app精確的表示Moto G繪制的時間是Nexus 6的兩倍(比較兩圖中綠線的高度)安疗。這一點(diǎn)可以通過數(shù)據(jù)采集(adb shell dumpsys gfxinfo)進(jìn)一步說明。下一個例子當(dāng)中茄靠,優(yōu)化過的view繪制在Moto G上用了兩倍多時間茂契。對于兩臺設(shè)備來說,draw慨绳,prepare掉冶,process這幾步都花了差不多的時間(少于4ms)。差別出現(xiàn)在execute階段(紫色)脐雪,Moto G比Nexus 6多用了差不多4ms厌小。說明GPU渲染測試最好是在低端機(jī)器上來做,比較容易發(fā)現(xiàn)卡頓問題战秋。
一般來說璧亚,GPU Profiler可以幫你發(fā)現(xiàn)問題。在山羊app里脂信,如果我打開Fibonacci延遲(在創(chuàng)建view多時候進(jìn)行耗時的遞歸計算)癣蟋,GPU profiler看不出任何卡頓透硝,因?yàn)橛嬎愣及l(fā)生在主線程而且完全阻止了渲染(在低端機(jī)上,可能會出現(xiàn)ANR消息)疯搅。
Fibonacci算法
Fibonacci序列是這樣一組數(shù)的集合:每個數(shù)字都是它前面兩個數(shù)字的和濒生。比如0,1幔欧,1罪治,2,3礁蔗,5觉义,8等等。程序里一般用來表示遞歸浴井,這里我用了最低效的方式來生成Fibonacci序列晒骇。
生成這些數(shù)字的計算次數(shù)呈指數(shù)級增長。這樣做的目的是在渲染的時候增加CPU的壓力滋饲,這樣渲染事件就無法得到及時處理厉碟,出現(xiàn)延遲。計算n=40就把a(bǔ)pp變得很慢了(低端機(jī)上會crash)屠缭。這個例子雖然有點(diǎn)牽強(qiáng)箍鼓,但我們定位卡頓是由Fibonacci產(chǎn)生的過程會很有意義。
Android Marshmallow里的GPU渲染
在android marshmallow里呵曹,運(yùn)行adb shell dumpsys gfxinfo?. 可以發(fā)現(xiàn)一些檢測卡頓的新功能款咖。首先,數(shù)據(jù)報告開頭部分能看到每一幀渲染的信息了奄喂。
從app的啟動開始铐殃,我們可以看到一共渲染了多少幀,其中多少幀的渲染時間是控制在理想值的90%以內(nèi)跨新,還能看到渲染比較慢的幀(90%富腊,95%,99%)域帐。最后五行列出的是沒有在16ms內(nèi)渲染完成的原因赘被。注意,這里不止有卡頓的問題肖揣,幀率還收到了其他因素的影響民假。
android marshmallow在gfxinfo庫里增加了另一個好用的測試工具,adb shell dumpsys gfxinfo?framestats龙优。它能夠輸出每一幀里發(fā)生的某些事件耗時羊异,格式是逗號分隔的一張大表。列名沒有給出,但在Android Developer網(wǎng)站里有解釋野舶。為了算出渲染里每一步的費(fèi)時易迹,我們要計算出報告里不同framestats的差異。下面是一些繪制事件:
VSYNC-Intended_VSYC(告訴你是否丟幀里平道,也就是卡頓)
處理輸入事件的時間(一般要小于2ms)
動畫計算(一般小于2ms)
layout和measure
view.draw()耗時
Sync耗時(如果大于0.4ms赴蝇,表示很多bitmap正在發(fā)送到GPU)
GPU耗時(overdraw的時間會在這里面)
繪制一幀的總時間
有時候即使出現(xiàn)了超過16ms的繪制,但由于有vsync buffer的存在巢掺,也不會出現(xiàn)丟幀。對于沒有額外buffer的低端設(shè)備劲蜻,就可能會出現(xiàn)卡頓了陆淀。
不只是卡頓(丟幀)
有時候GPU Profile里看不到超過16ms的數(shù)據(jù),但你從屏幕上看到明顯的卡頓或跳動先嬉。出現(xiàn)這種情況可能是由于CPU在做別的事情被堵住了轧苫,從而導(dǎo)致里丟幀。在Monitor或者Android Studio中疫蔓,可以查看DDMS里的logfiles含懊。通過過濾log更容易查看app的運(yùn)行情況⌒普停可以重點(diǎn)看下類似下圖中的log岔乔。
我們在后面的文章里會講訴CPU導(dǎo)致的丟幀是怎么產(chǎn)生的。
Systrace
在上面的這些優(yōu)化之后滚躯,如果你的界面還有卡頓雏门,我們還有辦法。Systrace工具也可以測量你app的性能掸掏。甚至可以幫助你定位問題產(chǎn)生的位置茁影。這個工具是作為“Project Butter”一部分同Jelly Bean一同發(fā)布的,它能夠從內(nèi)核級檢測你設(shè)備的運(yùn)行狀態(tài)丧凤。Systrace可配置的參數(shù)很多募闲。我們這里重點(diǎn)關(guān)注UI是怎么渲染的,用systrace檢測卡頓問題愿待。
Systrace和之前的工具不同的是浩螺,它記錄的是整個android系統(tǒng)的狀態(tài),并不是針對某一個app 的呼盆。所以最好是用運(yùn)行app比較少的設(shè)備來做檢測年扩,這樣就不會受到其他app的干擾了。Systrace圖標(biāo)是綠色和粉紅色組成的(下圖紅色的橢圓里)访圃。點(diǎn)擊下厨幻,會彈出一個帶幾個選項(xiàng)的窗口。
trace數(shù)據(jù)記錄在一個html文件里,可以用瀏覽器打開况脆。這里主要研究屏幕的交互數(shù)據(jù)饭宾,主要收集CPU,graphics和view數(shù)據(jù)(如圖4-25所示)格了。duration留空(默認(rèn)是5秒)看铆。點(diǎn)擊OK之后,Systrace會馬上開始采集設(shè)備上的數(shù)據(jù)(最好馬上開始操作)盛末。因?yàn)椴杉臄?shù)據(jù)非常之多弹惦,所以最好一次只針對一個問題。
traces里面的數(shù)據(jù)看著有點(diǎn)嚇人(我們只是勾選里4個選項(xiàng)G牡)棠隐。鼠標(biāo)可以控制滑動,WASD可以用來zoom in/out(W檐嚣,S)和左右滑動(A助泽,D)。在剛跑的trace數(shù)據(jù)最上面嚎京,能看到CPU的詳細(xì)數(shù)據(jù)嗡贺,CPU數(shù)據(jù)的下面是幾個可折疊的區(qū)域,分別表示不同的活躍進(jìn)程鞍帝。每一個色條表示系統(tǒng)的一個行為诫睬,色條的長度表示該行為的耗時(放大可以看到更多細(xì)節(jié))。選中屏幕底部的一個色條膜眠,第一眼看到的總覽有點(diǎn)嚇人岩臣,我們一條條分析看下這些數(shù)據(jù)。
Systrace進(jìn)化史
就像android生態(tài)圈一樣宵膨,Systrace在不同的系統(tǒng)版本里有不同的界面架谎,展示,和輸出結(jié)果辟躏。
在Jelly Bean設(shè)備谷扣,在設(shè)置的開發(fā)者選項(xiàng)里可以打開tracing。必須要同時打開電腦和手機(jī)上的該功能捎琐。
隨著android系統(tǒng)版本的升級会涎,trace生成的數(shù)據(jù)也更加詳細(xì),布局也有一些改變瑞凑。
我建議通過Jelly Bean查看Systraaces末秃,然后喝Lollipop上的數(shù)據(jù)對比,收集到的數(shù)據(jù)會不一樣籽御。
在2015年的google io大會上练慕,google發(fā)布了新版本的Systrace惰匙,新版本增加了一些新特性,下面會有更詳細(xì)的介紹铃将。
我們繼續(xù)滑動Systrace的輸出結(jié)果项鬼,運(yùn)行期間每個進(jìn)程的數(shù)據(jù)都可以看到。我們主要研究卡頓相關(guān)信息劲阎,查看屏幕刷新時可能有問題的繪制绘盟。只要刷新率和繪制都正常,屏幕的渲染應(yīng)該就是流暢的悯仙。但只要一個出問題龄毡,就有可能會導(dǎo)致頁面渲染的卡頓。
Systrace Screen Painting
我們通過圖4-24來看下屏幕繪制的步驟锡垄。最頂部一行的trace(藍(lán)色高亮)時VSYNC稚虎,由一些均勻分布的藍(lán)綠色寬條組成。VSYNC是操作系統(tǒng)發(fā)來的信號偎捎,表示此時該刷新屏幕了。每個寬條表示16ms(寬條之間的空白也是16ms)序攘。當(dāng)VSYNC事件發(fā)生的時候(在藍(lán)綠色寬條的任意一側(cè))茴她,surface flinger(紅色高亮方框包含幾種顏色的長條)會從view buffer(沒展示出來)里選一個view,然后繪制到屏幕上程奠。理想情況下丈牢,surfaceflinger事件之間相距16ms(沒有卡頓),因此如果出現(xiàn)長條空缺則表示surfaceflinger丟掉了一次VSYNC更新事件瞄沙,屏幕就沒有及時的刷新(此時就會有卡頓)己沛。在trace文件2/3的位置可以看到這樣的空缺(綠色高亮方框)。
圖4-27底部展示的是app的詳情距境。第二行數(shù)據(jù)(綠色和紫色的線條)表示的app正在創(chuàng)建view申尼,然后是底部的數(shù)據(jù)(綠色,藍(lán)色垫桂,和一些紫色的條狀)师幕,表示的是RenderThread,view的渲染和發(fā)送到buffer(圖中沒有畫出來)都是在這個線程里做的诬滩。注意看可以發(fā)現(xiàn)大概1/3的位置霹粥,這些條狀在該區(qū)域集中變粗了,表示app此時由于某種原因發(fā)生了卡頓疼鸟。不同app情況不一樣后控,發(fā)生卡頓的原因也不同,但是我們可以根據(jù)一些共同的現(xiàn)象推測卡頓的發(fā)生空镜。
這種總覽很適合查找卡頓浩淘,但要調(diào)查清楚原因需要放大仔細(xì)看下捌朴。要明白Systrace都記錄了什么數(shù)據(jù),最好搞明白Systrace到底是怎么進(jìn)行測量的馋袜,app沒有卡頓的時候Systrace輸出又是什么樣的男旗。一旦弄明白了Systrace是怎么工作的,查找問題就方便多了欣鳖。在圖4-28中察皇,我把a(bǔ)pp正常運(yùn)行時Systrace紀(jì)錄的相關(guān)線條放到了一起。我們從屏幕左邊的droid.yahoo.com看起泽台。我描述的時候在trace文件里會來回跳動到不同的位置什荣。當(dāng)繪制發(fā)生的時候:
紅色方框:droid.yahoo.com完成了所有view的measure,然后把結(jié)果發(fā)送給RenderThread怀酷。
橘色方框:RenderThread稻爬,這里app會:
????????????????繪制frame(淺綠色)
????????????????顯示buffer里的內(nèi)容(灰色)?
????????????????清空buffer(紫色)
????????????????發(fā)送給緩存的view列表。
黃色方框:com.yahoo.mobile.client.andr…
buffer里面有一些view蜕依,線條的高度表示了buffer當(dāng)中view的數(shù)量桅锄。剛開始,只有一個样眠,當(dāng)新的view加入到buffer中之后友瘤,高度就變成了2倍。
綠色方框:VSYNC-sf 提示surface flinger有16ms的時間來渲染屏幕檐束。里面棕色的條狀表示16ms的長度辫秧。
藍(lán)色方框:surfaceflinger從隊(duì)列里抓取一個view(注意黃色方框里的buffer中view數(shù)量從2變?yōu)?)。完成之后被丧,view被發(fā)送給GPU盟戏,屏幕就繪制被繪制了。
紫色方框:VSYNC-app告訴app去渲染新的view(這里有個16ms的timer)甥桂。
當(dāng)VSYNC一開始柿究,droid.yahoo.att就不停的重復(fù)這個過程,measure view,發(fā)送給RenderThread等等黄选,不停的循環(huán)笛求。
再回過頭想一下設(shè)備能這么短的時間內(nèi)流暢的渲染屏幕,確實(shí)是件很神奇的事情糕簿。了解了渲染的過程探入,我們來找下卡頓的原因。
圖4-29中懂诗,我們看下OS層的行為蜂嗽。我增加了一些箭頭來表示16ms的間隔,紅色的方框表示surfaceflinger的丟幀殃恒。
為什么會出現(xiàn)這種情況植旧?箭頭上方的一行是view buffer辱揭,行的高度表示有多少幀緩存在了buffer里面。trace開始的時候病附,buffer里緩存的數(shù)量是1到2交替出現(xiàn)问窃。surfaceflinger每抓取一個view(buffer里的數(shù)量減一),又會馬上從app里生成一個新的view來填充完沪。但是當(dāng)surfaceflinger完成第三個動作之后域庇,buffer被清空了,但是沒有從app里及時填充新的view覆积。所以听皿,我們從app層面來檢查下這期間發(fā)生了什么。
在圖4-30中宽档,我們可以看到開始的時候RenderThread發(fā)送了一個view到buffer(紅色方框)尉姨。橘色方框表示app新建了另一個view,渲染吗冤,然后交給buffer(droid.yahoo.att measure,layout所有的view又厉,RenderThread負(fù)責(zé)繪制)。不幸的是椎瘟,app沒來得及創(chuàng)建新view就被掛起了(黃色方框內(nèi))馋没。為了創(chuàng)建下一個view,droid.yahoo.att app在運(yùn)行暗綠色的“performTraversals”(3ms)之前降传,要先運(yùn)行“obtainView” 7ms,“setupListItem” 8.7ms勾怒。app然后把數(shù)據(jù)交給RenderThread婆排,這一步也比較慢(12ms)。創(chuàng)建這一幀總共用了近31ms(上一個只用了6ms)笔链。當(dāng)創(chuàng)建這一幀開始的時候段只,buffer里只有一幀的數(shù)據(jù),但是設(shè)備需要兩幀鉴扫。buffer沒有被填滿赞枕,所以屏幕繪制出現(xiàn)了卡頓。
有意思的是app后面馬上就速度追了上來坪创。黃色方框內(nèi)延遲遞交的view創(chuàng)建并交給buffer之后炕婶,后續(xù)的兩幀緊接著創(chuàng)建好了(綠色和藍(lán)色的方框)。通過快速的填充新的幀莱预,app就只丟了一幀柠掂。這個trace結(jié)果是在Nexus 6上運(yùn)行的(處理器比較快,能快速的跟上)依沮。在三星S4 Mini涯贞,Jelly Bean 4.2.2上運(yùn)行同樣的結(jié)果得到圖4-31.
從總覽圖上可以清晰的看到有很多幀都丟掉了(trace開始的時候surfacelinger部分有很多的空缺)枪狂。而且頂部那一行(view buffer)里的buffer經(jīng)常是空的(導(dǎo)致里卡頓),buffer里同時有兩個view的情況非常少宋渔。對于一個GPU性能比較差的設(shè)備來說州疾,app能夠像Nexus 6一樣趕上填滿buffer的概率比較小。
小貼示: 其實(shí)你可以偶爾渲染一幀超過16ms皇拣,因?yàn)閎uffer里面一般都有1到2幀準(zhǔn)備好的view備用严蓖。但是如果超過2-3幀渲染很慢,用戶就會感覺到卡頓了审磁。
上面的trace是在運(yùn)行Jelly Bean的手機(jī)上跑的谈飒,RenderThread的數(shù)據(jù)歸到了droid.yahoo.att那一行(Lollipop之前measure,draw,layout都是和在一起的)。把每一行數(shù)據(jù)合在一起之后豎條變寬态蒂。每一次調(diào)用之間的細(xì)條空白說明手機(jī)在每幀的繪制之后杭措,只剩下很少的時間處理其它任務(wù)。手機(jī)上的app只能稍稍領(lǐng)先surfacelinger填滿buffer的速度钾恢。如果app能夠減小所繪制view的復(fù)雜度手素,也就是加快view的渲染,細(xì)條的空白就會變的寬一點(diǎn)瘩蚪,buffer填滿的概率就更大泉懦,也就給低端設(shè)備在繪制之外更多的空間去處理其它任務(wù)。
把這塊區(qū)域加高亮之后疹瘦,Systrace會把所有條狀所占的時間計算出一個總和崩哩,用鼠標(biāo)在上面依次移動就能看到基本的數(shù)據(jù)了。圖4-32中言沐,可以看到performtraversals(父view的draw命令)平均用了13.8ms邓嘹,大概有5ms的波動。16ms的卡頓閾值在波動的范圍之內(nèi)险胰,所以很有可能設(shè)備上會有卡頓汹押。
把這塊放大能看到更多的細(xì)節(jié)(圖4-33)。每個垂直的紅線表示16ms起便。從圖中可以看出棚贾,大概有5,6次SurfaceFlinger錯過了紅線標(biāo)記榆综。綠色的“performtraversals”線條都幾乎有16ms長(這一步是必須做的妙痹,有卡頓)。還有兩個藍(lán)綠色的 deliverInputEvents(每個都超過了16ms)也阻礙了app的屏幕繪制鼻疮。
那到底是什么觸發(fā)了deliverInputEvents呢细诸?這其實(shí)是用戶在點(diǎn)擊屏幕,強(qiáng)制ListView重繪所有的view陋守。這部分影響是CPU震贵,我們接下來簡單看下這時候CPU都在干啥利赋。
Systrace和CPU對渲染的影響
如果你頻繁的感覺到卡頓,但是在繪制或者surfaceflinger部分看不到什么明顯的異常猩系,這時候可以嘗試看下CPU在處理什么事情媚送,在Systrace的頂部可以看到這部分的數(shù)據(jù)。如果你能大概猜到是哪部分的邏輯影響了繪制寇甸,可以先把這部分代碼注釋掉試試塘偎。山羊app里有個選項(xiàng)可以開啟Fibonacci延遲。打開之后拿霉,app在每一行數(shù)據(jù)渲染的時候都會計算一個很大的Fibonacci值吟秩。用膝蓋想都知道這時CPU會變得很忙。由于計算是在主線程做的绽淘,會妨礙的view的渲染弦赖,理所當(dāng)然就導(dǎo)致里丟幀柒凉,滑動也會變的很卡。圖4-24里顯示的log就能看到這種情況下的丟幀。我們再深挖一點(diǎn)看能不能通過Systrace定位到計算Fibonacci數(shù)的代碼页衙。
我們再重頭看下trace數(shù)據(jù)狮杨,圖4-34里是沒有優(yōu)化過的山羊app在Nexus 6上跑的數(shù)據(jù)排龄。
展示做了一些修改腕唧,CPU和surfaceflinger之間的一些線被去掉了。這個trace里看不到卡頓赔退,surfaceflingers每16ms的間隔很均勻橙依。RenderThread和每一行view填滿buffer的表現(xiàn)也很正常。和CPU那一行數(shù)據(jù)對比一下硕旗,可以發(fā)現(xiàn)一個新規(guī)律窗骑。當(dāng)RenderThread在繪制layout的時候,CPU1正在運(yùn)行一個藍(lán)色的任務(wù)(注意我們看的是窄一點(diǎn)的CPU1卵渴,不是CPU1:C-State)。當(dāng)山羊app的view正在被measure的時候鲤竹,CPU0有一個相應(yīng)的紫色的行為浪读。view的layout和繪制是由兩個CPU完成的。注意X軸上的點(diǎn)擊是每隔10ms發(fā)生的辛藻,這里每個行為都沒有超過2-4ms碘橘。
當(dāng)我們加入費(fèi)時的Fibonacci計算之后,Systrace的結(jié)果看起來就很不一樣了吱肌。(圖4-35)
從Systrace里能看到很多卡頓痘拆,在相同的100ms時間范圍內(nèi),surfaceflinger就畫了三幀(上面不卡頓的情況畫了7幀)氮墨》那可以看到RenderThread繪制view還是很快的(從圖中可以看出吐葵,藍(lán)色的RenderThread是在CPU0上運(yùn)行的)。但是桥氏,measure view的時候温峭,F(xiàn)ibonacci的遞歸計算就導(dǎo)致了問題。山羊app進(jìn)程那一行花了大部分的時間在obtainView的狀態(tài)字支,而不是measure凤藏。同時可以看到CPU1上紫色對應(yīng)的山羊進(jìn)程不再是2-4ms寬了,變成了2-17ms寬堕伪。Fibonacci計算每次大概用了13-17ms揖庄,對app的繪制性能產(chǎn)生了很大的影響。
Systrace更新-I/O 2015
在2015年Google I/O大會上欠雌,google發(fā)布了新版本的systrace蹄梢,上面提到的分析數(shù)據(jù)變的更簡單了。在圖4-27里桨昙,我把每一幀的更新都高亮出來了检号。在新版本的systrace(圖4-36)里,每一幀都是由一個帶F的小圓圈標(biāo)示的蛙酪。正常渲染的幀會有綠點(diǎn)齐苛,慢幀則是黃色或者紅色。選擇一個點(diǎn)桂塞,然后按下m就可以高亮某一幀凹蜂,分析起來更方便。
新版本的systrace對于正在發(fā)生的行為也有更清晰的描述了阁危。在圖4-36中玛痊,幀的渲染時間是18.181ms,是用黃色標(biāo)示的狂打,如果有很多幀超過了16ms就會導(dǎo)致卡頓了擂煞。在trace文件下方的描述信息面板上(圖4-37),可以看到警告信息趴乡,說我的app在重用ListView的item对省,而不是創(chuàng)建新的item,這樣拖慢了view inflation晾捏。
在systrace里可以看到其它類似的警告蒿涎,形狀像泡泡或是點(diǎn),屏幕右邊的警告面板也列出了這些信息(圖4-38)惦辛。
這些新功能讓Systrace診斷UI問題更加簡單了劳秋。
第三方工具
每個大的芯片廠商都有自己的GPU評測工具,可以幫助發(fā)現(xiàn)更多渲染時遇到瓶頸的信息。這些工具對一些特定的芯片更有針對性玻淑,信息也更多嗽冒。可以幫你針對不同的GPU做更深度的優(yōu)化岁忘。Qualcomm辛慰,NVIDIA和Intel都提供了這些開發(fā)者工具,有興趣的可以自己試下干像。
感知優(yōu)化
上面的內(nèi)容都是在討論怎么通過測試帅腌,調(diào)試,優(yōu)化布局來讓UI的體驗(yàn)更快麻汰。其實(shí)還有另外一個辦法讓你的app UI更快:讓用戶感覺更快速客。當(dāng)然作為開發(fā)者要盡可能優(yōu)化自己的代碼,view五鲫,overdraw和其它所有可能會影響渲染性能的地方溺职,上面這些都做了之后,再考慮下面這些能讓用戶覺得你的app更快的方法位喂。
人類大腦工作的方式很有意思浪耘,通過改變大腦對等待的感知,可以讓你的用戶感覺延遲變短了塑崖。雜貨店的老板都會在走廊上放一些沒用的雜志七冲,就是為了讓客戶有東西可以看,感覺等待的時間就會短一些规婆。如果在向用戶展示內(nèi)容的時候增加一些過渡效果澜躺,見效明顯。這就像一個小魔術(shù)一樣讓用戶感覺體驗(yàn)變的更快了抒蚜,歸根結(jié)底重要的是用戶覺得你的app有多快掘鄙。這個技巧實(shí)現(xiàn)起來也有點(diǎn)取巧,有時候這種感知的優(yōu)化甚至?xí)玫较喾吹男Ч怂瑁鯝/B test來確保你的優(yōu)化對用戶來說真的有效操漠。
loading菊花:優(yōu)缺點(diǎn)
loading菊花,進(jìn)度條饿这,沙漏圖標(biāo)浊伙,和其它所有表示等待的方式都存在很久了。這些都可以讓app的內(nèi)容過渡變得更快蛹稍。比如在app里加一個進(jìn)度條吧黄,加載的時候播放一個進(jìn)度的動畫來讓用戶等待部服。研究表明使用一個帶有動畫的滑動條的時候用戶會感覺更舒服唆姐。快速旋轉(zhuǎn)的loading菊花也讓用戶感覺等待的時間更短廓八。
但是奉芦,有延遲的時候赵抢,加個菊花并不總是有效的。iOS app Polar的開發(fā)者發(fā)現(xiàn)他們的app渲染一個view的時候有一點(diǎn)延遲声功。他們第一反應(yīng)是在頁面里加了一個菊花告訴用戶頁面正在渲染內(nèi)容烦却,但效果不如預(yù)期。用戶開始反饋app變慢了先巴,等待頁面加載的時間變長了(其實(shí)app沒有變慢其爵,不過是加了一個菊花)。加了個等待的標(biāo)識之后讓用戶明顯的感覺到他們在等伸蚯。取消菊花之后摩渺,用戶感覺app又變快了(開發(fā)者僅僅是改變了菊花)。通過改變用戶對等待的感知剂邮,可以讓用戶覺得app變快了摇幻。Facebook也遇到過類似的問題:使用自己定制的菊花讓用戶感覺更慢,用默認(rèn)菊花感覺更快挥萌。
增加菊花最好讓用戶測試下他們的真實(shí)感受绰姻。一般來說,當(dāng)?shù)却臅r間稍微有點(diǎn)長的時候引瀑,增加菊花是可以接受的:比如打開一個新頁面或者從網(wǎng)上下載一張圖片狂芋。如果延遲很短(一般來說小于一秒),就應(yīng)該考慮去掉菊花了伤疙。這種情況下應(yīng)該讓用戶覺得他們并沒有在等银酗。
用動畫來抵消等待的時間
點(diǎn)擊后看到一個空白的屏幕會讓用戶感覺在等待。就是這個原因讓瀏覽器在點(diǎn)擊鏈接徒像,新頁面刷出來之前都是展示舊的頁面黍特。在手機(jī)app里,一般來說我們不希望讓用戶停留在老的頁面上锯蛀,一個快速的切換動畫可以爭取到足夠的時間讓下一個頁面準(zhǔn)備就緒灭衷。可以觀察下你最常用的android app旁涤,當(dāng)頁面切換的時候有多少從邊上或者底部出現(xiàn)的動畫翔曲。
瞬時更新的小謊言
如果你的用戶在頁面上做了更新數(shù)據(jù)的操作,即使數(shù)據(jù)還沒抵達(dá)服務(wù)器劈愚,可以馬上把用戶看到的數(shù)據(jù)更新掉(當(dāng)然開發(fā)者要保證這些數(shù)據(jù)能100%抵達(dá)服務(wù)器)瞳遍。比如說,你在Instagram上點(diǎn)了贊菌羽,頁面上馬上就更新了贊的狀態(tài)掠械,其實(shí)贊的狀態(tài)甚至可能還沒有更新到服務(wù)器。Instangram的開發(fā)者管這叫“行為最優(yōu)化”,狀態(tài)的更新要幾秒后才能到服務(wù)器并對網(wǎng)站的用戶可見(網(wǎng)速不好的時候可能要幾分鐘)猾蒂,但是更新最后都會成功均唉,等待服務(wù)器返回成功其實(shí)是沒必要的。移動端用戶一般都不希望在等待肚菠,只要最后能成功就好舔箭。
瞬時更新的另一個好處是,用戶會感覺你的app在網(wǎng)速或者信號不好(火車經(jīng)過隧道)的時候也能正常工作蚊逢。FlipBoard就做過一個離線發(fā)送網(wǎng)絡(luò)請求的框架层扶,可以很方便的應(yīng)用到更新UI。
另一個優(yōu)化的小技巧是提前上傳烙荷。對于像Instagram這種app來說怒医,上傳大量的圖片會增加主線程的延遲,提前開始上傳這些圖片會是個好辦法奢讨。Instagram發(fā)現(xiàn)發(fā)一個新post是慢在上傳圖片這一步稚叹,所以Instagram就在用戶在圖片上添加文字的間隙開始上傳圖片了,圖片被真正發(fā)布到服務(wù)器之前就已經(jīng)傳好了拿诸。用戶只要一點(diǎn)擊Post按鈕扒袖,就只需要上傳文本和創(chuàng)建post的命令了,這樣就會讓用戶感知非衬堵耄快季率。Instagram在遇到“是否要添加菊花”這個問題時,他們的答案是通過改變架構(gòu)的方式永遠(yuǎn)的杜絕菊花描沟。
提升感知體驗(yàn)的小提示
當(dāng)app的速度通過優(yōu)化代碼或者view的優(yōu)化提升之后飒泻,你可以用秒表來測試下結(jié)果。有些感知是可以用秒表測量的(Instagram的例子)吏廉,有些則不能(菊花的例子)泞遗。當(dāng)常規(guī)的分析或者測量工具不可靠的時候,需要讓用戶來真正的體驗(yàn)這些優(yōu)化效果席覆∈氛蓿可以做一些可用性測試,增加測試的范圍佩伤,A/B測試聊倔,這些才能真正的讓你確認(rèn)你的優(yōu)化是讓用戶更開心還是更沮喪。
總結(jié)
Android app的用戶體驗(yàn)直接跟屏幕上展現(xiàn)的內(nèi)容相關(guān)生巡。如果app的內(nèi)容加載很慢或者滑動不夠流暢耙蔑,用戶的感知就是負(fù)面的。在這篇文章孤荣,我們講了如何優(yōu)化view樹形結(jié)構(gòu)甸陌,看是否扁平或者簡化view等等徐鹤。我們還講了怎么檢測解決overdraw的問題。還有一些需要深度分析的優(yōu)化(像CPU導(dǎo)致的問題)邀层,systrace很適合發(fā)現(xiàn)和解決這種卡頓問題。最后是一些讓你的app感覺更快的小技巧遂庄,比如把CPU或者網(wǎng)絡(luò)相關(guān)的任務(wù)延后處理寥院,不要影響繪制渲染。