1. 程式人生 > >關於Android中16ms的繪製工作問題

關於Android中16ms的繪製工作問題

 如果你覺得你的應用介面出現卡頓不流暢的情況,不用懷疑,這很大原因是你沒有在16ms完成你的工作。沒錯,16ms要完成你的工作,再慢點,使用者一定會吐槽,然後狠心把你辛辛苦苦開發出來的應用給解除安裝掉,你也不想想,人生有幾個16ms可以浪費啊!

你應該知道的16ms問題

       上面說的是APP渲染效能問題,現在的APP為了提高使用者的體驗,都喜歡加入很多酷炫的動效。實現這些動效意味著要消耗系統更多的效能。如果處理不好,Android系統可能會無法及時完成這些複雜的動畫和介面的渲染,從而導致卡頓問題的出現。

  那麼上面說的16ms是什麼意思,為什麼要在16ms內完成我們的工作呢,這是因為大多數的Android顯示螢幕是以每秒60幀來重新整理的(也就是60Hz)。一幀可以看做是一張的獨立圖片,60幀每秒就意味著:16ms=1000/60Hz,相當於60fps。這就是上面說的16ms,這也是為什麼Android系統每隔16ms就會發出一次VSYNC

訊號觸發對UI進行渲染,如果這16ms內我們沒有完成對檢視的繪製,那麼就會出現丟幀的情況。也許有人會問Android手機的螢幕為什麼是每秒重新整理60幀(60fps),專家是這麼解釋的:

這是因為人眼與大腦之間的協作無法感知超過60fps的畫面更新。12fps大概類似手動快速翻動書籍的幀率,這明顯是可以感知到不夠順滑的。24fps使得人眼感知的是連續線性的運動,這其實是歸功於運動模糊的 效果。24fps是電影膠圈通常使用的幀率,因為這個幀率已經足夠支撐大部分電影畫面需要表達的內容,同時能夠最大的減少費用支出。但是低於30fps是 無法順暢表現絢麗的畫面內容的,此時就需要用到60fps來達到想要的效果,當然超過60fps是沒有必要的(據說Dart能夠帶來120fps的體驗)

好,聽完專家解釋後,那麼卡頓問題到底是怎樣產生呢,想要知道這個,我們需要簡單瞭解一下Android的渲染機制。

從xml到display

  我們平時寫的那些xml佈局到底是怎樣繪製到螢幕上的呢?我們一般不太喜歡關注這些問題,因為這些android系統通通都會幫我們搞掂。是吧,程式猿一般比較懶,能多省事就多省事,但是今天我們還是有必要了解一下,先看看下面的圖:

Activity的介面之所以可以被繪製到螢幕上其中有一個很重要的過程就是柵格化(Resterization,柵格化簡單來說就是將向量圖轉化為機器可以識別的點陣圖的一個過程。其中很複雜也比較很耗時,GPU就是用來加快柵格化的速度。從上面的圖可以看出,CPU會先把UI元件計算成polygons

(多邊形)和textures(紋理),然後再交給GPU進行柵格化渲染,最後GPU再將資料傳送給螢幕,由螢幕進行繪製顯示。當然,從CPU到GPU還需要經過OpenGL ES的處理,這也是一個很複雜的過程。對OpenGL有興趣的童鞋,想更加深入瞭解的話,可以自行度娘,檢視官方文件或者原始碼什麼的,這裡就不細說了,因為我也不是很懂,囧。

關於VSYNC

  接下來要聊聊VSYNC,VSYNC這個概念出來很久了,Vertical Synchronization,就是所謂的“垂直同步”。在Android中這也沿用了這個概念,我們也可以把它理解為“幀同步”。這個用來幹嘛的呢,就是為了保證CPU、GPU生成幀的速度和display重新整理的速度保持一致,Android系統每16ms就會發出一次VSYNC訊號觸發UI渲染更新。上面提到螢幕一秒重新整理60次,這就要求CPU和GPU每秒要有處理60幀的能力,一幀花費的時間在16ms內。那麼在Android系統中,是如何利用VSYNC工作的呢,如下圖:

從上圖我們可以知道當上一幀顯示結束後,在VSYNC訊號剛開始發出時,Android系統就立刻開始了下一幀資料的處理了,這樣就不會浪費時間了。圖中先顯示第0幀,在這16ms顯示時間裡,CPU和GPU已經開始準備下一幀的資料了,趕在下個VSYNC訊號到來時,GPU渲染完成,及時交換資料,display繪製顯示完成,不出什麼意外的話,每一幀都這麼井然有序進行著,那麼使用者就會體驗到那如絲順滑般的感覺的了,這是多美妙的事情啊!

雙緩衝機制

  其實上面說的就是Android的雙緩衝機制,而雙緩衝技術一直貫穿這個Android系統。因為實際上幀的資料就是儲存在兩個緩衝區中,A緩衝用來顯示當前幀,那麼B緩衝就用來快取下一幀的資料,這樣就可以做到一邊顯示一邊處理下一幀的資料。

前面的幀用序號表示,但實際上幀資料只儲存在A、B兩個緩衝區中。當前幀顯示緩衝A,Android系統一旦發出VSYN訊號時,就會在緩衝B中構建新的幀。當完成後(這裡的完成指的是螢幕已經在緩衝B中拿到新一幀的資料,完成繪製),緩衝A的資料就會被清空,繼續進行下一幀的繪製,注意,此時緩衝B的資料是不會被清空的,因為當前顯示的是緩衝B中幀畫面,清空的只是緩衝A的資料。

  這樣看起來貌似沒什麼問題,一切都是我們的掌控中。但是,由於某些原因,比如我們應用程式碼上處理不夠好,又或者使用者手機後臺打開了很多應用,又在聽歌又在下載視訊什麼的,CPU一時間被佔用了,導致下一幀繪製的時間超過了16ms,那麼問題就來了,這時候使用者就不爽了,因為使用者很明顯感知到了卡頓的出現,也就是所謂的丟幀情況。如下圖所示:

很好,下面我們來認真分析一下為什麼會出現丟幀的情況:

Step1. 當Display顯示第0幀資料,此時CPU和GPU已經開始渲染第1幀畫面,並將資料快取在緩衝B中;

Step2. 但是由於某些原因,就好像上面說的,CPU資源一時間被佔用,導致系統處理該幀資料耗時過長或者未能及時處理該幀資料;

Step3. 當VSYNC訊號來時,display向B緩衝要資料,這下悲催了,因為緩衝B的資料還沒準備好,B緩衝區這時候是被鎖定的,display無可奈何,只能繼續顯示之前緩衝A的那一幀,此時緩衝A的資料也不能被清空和交換資料。這種情況被Android開發組命名為“Jank”,就是所謂的“丟幀”,也被稱作“廢幀”;

Step4. 當第1幀資料(即緩衝B資料)準備完成後,它並不會馬上被顯示,而是要等待下一個VSYNC,Display重新整理後,這時使用者才看到畫面的更新,中間這段時間的時間就白白被浪費掉了。

  從上面的分析可以知道,因為緩衝B的超時,掉了鏈子,導致出現了丟幀的情況。因為一步的延遲,也很有可能導致後面的處理延遲,很可能造成一步慢步步慢啊,像你這樣“延誤工期”在古代可是大罪啊,分分鐘要殺頭的哦~~~

三倍緩衝機制

  出現上面這種情況怎麼辦,在Android系統裡給出了這樣的解決辦法就是:再加入一個緩衝。這樣就出現了三個緩衝,顧名思義,這裡說的就是三倍緩衝。好,看下圖:

  當出現B緩衝超時,螢幕顯示的還是緩衝A中的那一幀,因為此時緩衝A的資料還在使用,不能及時被交換,所以在下一次VSYNC訊號來之前這段時間無任何作為,時間就會白白被浪費。為了避免這種時間浪費,在三倍緩衝機制中,系統這個時候會建立一個緩衝C,用來緩衝下一幀的資料。如上圖所示,顯示完緩衝B中那一幀後,下一幀就是顯示緩衝C中的了。這樣雖然還是不能避免會出現卡頓的情況,但是Android系統還是盡力去彌補這種缺陷,最終儘可能給用平滑的動效體驗。

  關於Android的渲染機制其實是一個很複雜的過程,上面也只是就整體流程來論述,其中很多過程和細節都忽略了,有興趣的童鞋自行深入研究哈。。。下面給出一些簡單的優化建議。

如何優化16ms問題

1.儘可能減少Overdraw,就是減少過渡繪製,減少佈局巢狀的層次,去掉重複設定的背景;

2.減少listview中getView中的耗時操作,一些自定義的view儘可能減少invalidate的呼叫;

3.儘可能不要在UI執行緒做過多耗時的操作;

  16ms時間很短,身為一名應用開發者,為了讓使用者有更好的體驗,應該要充分利用這16ms,確保重新整理一幀的時候在16ms內。上面只是幾條簡單的優化建議,如果大家想要深入瞭解Android的效能優化



作者:豬叔叔
連結:https://www.jianshu.com/p/02800806356c
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。