? ? ? ?如果你覺得你的應(yīng)用界面出現(xiàn)卡頓不流暢的情況咆畏,不用懷疑旧找,這很大原因是你沒有在16ms完成你的工作钮蛛。沒錯(cuò)愿卒,16ms要完成你的工作琼开,再慢點(diǎn)柜候,用戶一定會吐槽渣刷,然后狠心把你辛辛苦苦開發(fā)出來的應(yīng)用給卸載掉辅柴,你也不想想碌嘀,人生有幾個(gè)16ms可以浪費(fèi)肮扇摺!
你應(yīng)該知道的16ms問題
? ? ? ?上面說的是APP渲染性能問題烹棉,現(xiàn)在的APP為了提高用戶的體驗(yàn)浆洗,都喜歡加入很多酷炫的動效伏社。實(shí)現(xiàn)這些動效意味著要消耗系統(tǒng)更多的性能洛口。如果處理不好,Android系統(tǒng)可能會無法及時(shí)完成這些復(fù)雜的動畫和界面的渲染买优,從而導(dǎo)致卡頓問題的出現(xiàn)。
??那么上面說的16ms是什么意思湘纵,為什么要在16ms內(nèi)完成我們的工作呢梧喷,這是因?yàn)榇蠖鄶?shù)的Android顯示屏幕是以每秒60幀來刷新的(也就是60Hz)铺敌。一幀可以看做是一張的獨(dú)立圖片偿凭,60幀每秒就意味著:16ms=1000/60Hz弯囊,相當(dāng)于60fps匾嘱。這就是上面說的16ms奄毡,這也是為什么Android系統(tǒng)每隔16ms就會發(fā)出一次VSYNC信號觸發(fā)對UI進(jìn)行渲染吼过,如果這16ms內(nèi)我們沒有完成對視圖的繪制盗忱,那么就會出現(xiàn)丟幀的情況趟佃。也許有人會問Android手機(jī)的屏幕為什么是每秒刷新60幀(60fps)昧捷,專家是這么解釋的:
這是因?yàn)槿搜叟c大腦之間的協(xié)作無法感知超過60fps的畫面更新靡挥。12fps大概類似手動快速翻動書籍的幀率跋破,這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續(xù)線性的運(yùn)動舷手,這其實(shí)是歸功于運(yùn)動模糊的 效果男窟。24fps是電影膠圈通常使用的幀率蝎宇,因?yàn)檫@個(gè)幀率已經(jīng)足夠支撐大部分電影畫面需要表達(dá)的內(nèi)容姥芥,同時(shí)能夠最大的減少費(fèi)用支出凉唐。但是低于30fps是 無法順暢表現(xiàn)絢麗的畫面內(nèi)容的台囱,此時(shí)就需要用到60fps來達(dá)到想要的效果簿训,當(dāng)然超過60fps是沒有必要的(據(jù)說Dart能夠帶來120fps的體驗(yàn))
好强品,聽完專家解釋后的榛,那么卡頓問題到底是怎樣產(chǎn)生呢夫晌,想要知道這個(gè)晓淀,我們需要簡單了解一下Android的渲染機(jī)制凶掰。
從xml到display
??我們平時(shí)寫的那些xml布局到底是怎樣繪制到屏幕上的呢锄俄?我們一般不太喜歡關(guān)注這些問題奶赠,因?yàn)檫@些android系統(tǒng)通通都會幫我們搞掂毅戈。是吧苇经,程序猿一般比較懶扇单,能多省事就多省事蜘澜,但是今天我們還是有必要了解一下鄙信,先看看下面的圖:
Activity的界面之所以可以被繪制到屏幕上其中有一個(gè)很重要的過程就是柵格化(Resterization)装诡,柵格化簡單來說就是將向量圖轉(zhuǎn)化為機(jī)器可以識別的位圖的一個(gè)過程鸦采。其中很復(fù)雜也比較很耗時(shí)赖淤,GPU就是用來加快柵格化的速度咱旱。從上面的圖可以看出吐限,CPU會先把UI組件計(jì)算成polygons(多邊形)和textures(紋理)诸典,然后再交給GPU進(jìn)行柵格化渲染狐粱,最后GPU再將數(shù)據(jù)傳送給屏幕肌蜻,由屏幕進(jìn)行繪制顯示蒋搜。當(dāng)然豆挽,從CPU到GPU還需要經(jīng)過OpenGL ES的處理帮哈,這也是一個(gè)很復(fù)雜的過程娘侍。對OpenGL有興趣的童鞋,想更加深入了解的話僵缺,可以自行度娘磕潮,查看官方文檔或者源碼什么的自脯,這里就不細(xì)說了膏潮,因?yàn)槲乙膊皇呛芏啦危瑖濉?br>
關(guān)于VSYNC
??接下來要聊聊VSYNC叠纷,VSYNC這個(gè)概念出來很久了涩嚣,Vertical Synchronization顷歌,就是所謂的“垂直同步”眯漩。在Android中這也沿用了這個(gè)概念,我們也可以把它理解為“幀同步”冯勉。這個(gè)用來干嘛的呢灼狰,就是為了保證CPU交胚、GPU生成幀的速度和display刷新的速度保持一致蝴簇,Android系統(tǒng)每16ms就會發(fā)出一次VSYNC信號觸發(fā)UI渲染更新熬词。上面提到屏幕一秒刷新60次互拾,這就要求CPU和GPU每秒要有處理60幀的能力颜矿,一幀花費(fèi)的時(shí)間在16ms內(nèi)骑疆。那么在Android系統(tǒng)中箍铭,是如何利用VSYNC工作的呢坡疼,如下圖:
從上圖我們可以知道當(dāng)上一幀顯示結(jié)束后闸氮,在VSYNC信號剛開始發(fā)出時(shí)蒲跨,Android系統(tǒng)就立刻開始了下一幀數(shù)據(jù)的處理了或悲,這樣就不會浪費(fèi)時(shí)間了巡语。圖中先顯示第0幀男公,在這16ms顯示時(shí)間里,CPU和GPU已經(jīng)開始準(zhǔn)備下一幀的數(shù)據(jù)了澄阳,趕在下個(gè)VSYNC信號到來時(shí)碎赢,GPU渲染完成揩抡,及時(shí)交換數(shù)據(jù)峦嗤,display繪制顯示完成烁设,不出什么意外的話装黑,每一幀都這么井然有序進(jìn)行著恋谭,那么用戶就會體驗(yàn)到那如絲順滑般的感覺的了疚颊,這是多美妙的事情啊均抽!
雙緩沖機(jī)制
??其實(shí)上面說的就是Android的雙緩沖機(jī)制油挥,而雙緩沖技術(shù)一直貫穿這個(gè)Android系統(tǒng)深寥。因?yàn)閷?shí)際上幀的數(shù)據(jù)就是保存在兩個(gè)緩沖區(qū)中惋鹅,A緩沖用來顯示當(dāng)前幀负饲,那么B緩沖就用來緩存下一幀的數(shù)據(jù)喂链,這樣就可以做到一邊顯示一邊處理下一幀的數(shù)據(jù)椭微。
前面的幀用序號表示,但實(shí)際上幀數(shù)據(jù)只保存在A本慕、B兩個(gè)緩沖區(qū)中锅尘。當(dāng)前幀顯示緩沖A藤违,Android系統(tǒng)一旦發(fā)出VSYN信號時(shí)顿乒,就會在緩沖B中構(gòu)建新的幀璧榄。當(dāng)完成后(這里的完成指的是屏幕已經(jīng)在緩沖B中拿到新一幀的數(shù)據(jù),完成繪制)拾稳,緩沖A的數(shù)據(jù)就會被清空访得,繼續(xù)進(jìn)行下一幀的繪制悍抑,注意搜骡,此時(shí)緩沖B的數(shù)據(jù)是不會被清空的记靡,因?yàn)楫?dāng)前顯示的是緩沖B中幀畫面摸吠,清空的只是緩沖A的數(shù)據(jù)寸痢。
??這樣看起來貌似沒什么問題啼止,一切都是我們的掌控中献烦。但是巩那,由于某些原因拢操,比如我們應(yīng)用代碼上處理不夠好舶替,又或者用戶手機(jī)后臺打開了很多應(yīng)用顾瞪,又在聽歌又在下載視頻什么的,CPU一時(shí)間被占用了瞧甩,導(dǎo)致下一幀繪制的時(shí)間超過了16ms肚逸,那么問題就來了朦促,這時(shí)候用戶就不爽了务冕,因?yàn)橛脩艉苊黠@感知到了卡頓的出現(xiàn)禀忆,也就是所謂的丟幀情況箩退。如下圖所示:
很好魂毁,下面我們來認(rèn)真分析一下為什么會出現(xiàn)丟幀的情況:
Step1. 當(dāng)Display顯示第0幀數(shù)據(jù)胧瓜,此時(shí)CPU和GPU已經(jīng)開始渲染第1幀畫面府喳,并將數(shù)據(jù)緩存在緩沖B中钝满;
Step2. 但是由于某些原因弯蚜,就好像上面說的碎捺,CPU資源一時(shí)間被占用,導(dǎo)致系統(tǒng)處理該幀數(shù)據(jù)耗時(shí)過長或者未能及時(shí)處理該幀數(shù)據(jù)晋柱;
Step3. 當(dāng)VSYNC信號來時(shí)雁竞,display向B緩沖要數(shù)據(jù)碑诉,這下悲催了联贩,因?yàn)榫彌_B的數(shù)據(jù)還沒準(zhǔn)備好泪幌,B緩沖區(qū)這時(shí)候是被鎖定的祸泪,display無可奈何没隘,只能繼續(xù)顯示之前緩沖A的那一幀右蒲,此時(shí)緩沖A的數(shù)據(jù)也不能被清空和交換數(shù)據(jù)瑰妄。這種情況被Android開發(fā)組命名為“Jank”间坐,就是所謂的“丟幀”,也被稱作“廢幀”劳澄;
Step4. 當(dāng)?shù)?幀數(shù)據(jù)(即緩沖B數(shù)據(jù))準(zhǔn)備完成后秒拔,它并不會馬上被顯示溯警,而是要等待下一個(gè)VSYNC梯轻,Display刷新后喳挑,這時(shí)用戶才看到畫面的更新伊诵,中間這段時(shí)間的時(shí)間就白白被浪費(fèi)掉了曹宴。
??從上面的分析可以知道笛坦,因?yàn)榫彌_B的超時(shí)版扩,掉了鏈子礁芦,導(dǎo)致出現(xiàn)了丟幀的情況。因?yàn)橐徊降难舆t肖方,也很有可能導(dǎo)致后面的處理延遲窥妇,很可能造成一步慢步步慢啊,像你這樣“延誤工期”在古代可是大罪啊烹骨,分分鐘要?dú)㈩^的哦~~~
三倍緩沖機(jī)制
??出現(xiàn)上面這種情況怎么辦吨岭,在Android系統(tǒng)里給出了這樣的解決辦法就是:再加入一個(gè)緩沖峦树。這樣就出現(xiàn)了三個(gè)緩沖,顧名思義姐浮,這里說的就是三倍緩沖卖鲤。好蛋逾,看下圖:
??當(dāng)出現(xiàn)B緩沖超時(shí),屏幕顯示的還是緩沖A中的那一幀区匣,因?yàn)榇藭r(shí)緩沖A的數(shù)據(jù)還在使用沉颂,不能及時(shí)被交換铸屉,所以在下一次VSYNC信號來之前這段時(shí)間無任何作為彻坛,時(shí)間就會白白被浪費(fèi)昌屉。為了避免這種時(shí)間浪費(fèi)间驮,在三倍緩沖機(jī)制中竞帽,系統(tǒng)這個(gè)時(shí)候會創(chuàng)建一個(gè)緩沖C屹篓,用來緩沖下一幀的數(shù)據(jù)堆巧。如上圖所示谍肤,顯示完緩沖B中那一幀后荒揣,下一幀就是顯示緩沖C中的了乳附。這樣雖然還是不能避免會出現(xiàn)卡頓的情況赋除,但是Android系統(tǒng)還是盡力去彌補(bǔ)這種缺陷举农,最終盡可能給用平滑的動效體驗(yàn)颁糟。
??關(guān)于Android的渲染機(jī)制其實(shí)是一個(gè)很復(fù)雜的過程棱貌,上面也只是就整體流程來論述婚脱,其中很多過程和細(xì)節(jié)都忽略了障贸,有興趣的童鞋自行深入研究哈篮洁。袁波。锋叨。下面給出一些簡單的優(yōu)化建議娃磺。
如何優(yōu)化16ms問題
1.盡可能減少Overdraw偷卧,就是減少過渡繪制听诸,減少布局嵌套的層次,去掉重復(fù)設(shè)置的背景桥嗤;
2.減少listview中g(shù)etView中的耗時(shí)操作泛领,一些自定義的view盡可能減少invalidate的調(diào)用渊鞋;
3.盡可能不要在UI線程做過多耗時(shí)的操作锡宋;
??16ms時(shí)間很短执俩,身為一名應(yīng)用開發(fā)者奠滑,為了讓用戶有更好的體驗(yàn)宋税,應(yīng)該要充分利用這16ms杰赛,確保刷新一幀的時(shí)候在16ms內(nèi)乏屯。上面只是幾條簡單的優(yōu)化建議辰晕,如果大家想要深入了解Android的性能優(yōu)化含友,可以參考我們Open軟件開發(fā)小組之前發(fā)布的幾篇關(guān)于Android性能優(yōu)化的文章辆童,這里就不細(xì)說了把鉴。感謝大家的支持庭砍,歡迎大家關(guān)注我們Open軟件開發(fā)小組的公眾號怠缸,更多優(yōu)質(zhì)文章與你分享凯旭。
參考文章