Android UI刷新機(jī)制與SurfaceView

問題:

舉例一個Activity的布局文件和邏輯如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools"
 android:layout_width="match_parent"
 android:layout_height="match_parent"
 android:orientation="vertical"
 tools:ignore="MissingDefaultResource"
 android:background="@android:color/holo_red_dark">

 <FrameLayout
     android:id="@+id/surfaceView_container"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center"
     android:padding="10dp"
     android:background="@android:color/holo_blue_bright">

 <SurfaceView
     android:id="@+id/surfaceView"
     android:layout_gravity="center"
     android:layout_width="200dp"
     android:layout_height="200dp"></SurfaceView>

 <LinearLayout
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="bottom"
     android:orientation="horizontal">

     <Button
         android:id="@+id/gone_btn"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="set container gone"></Button>


     <Button
         android:id="@+id/remove_btn"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="remove surfaceView"></Button>

 </LinearLayout>
 </FrameLayout>


</FrameLayout>
   container.findViewById(R.id.remove_btn).setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             surfaceViewContainer.removeView(surface); //這個會回調(diào)到surfaceDestroyed奢米, surfaceView立即就會消失垮媒,會出現(xiàn)黑塊
             try {
                 Thread.sleep(10000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     });

當(dāng)我們點(diǎn)擊remove_btn時原杂,會出現(xiàn)SurfaceView所在的區(qū)域會出現(xiàn)10s黑塊的現(xiàn)象盯拱,這個現(xiàn)象在我們平時開發(fā)中用到SurafceView時常常遇到祥得,往往在主線程同時存在耗時操作和SurfaceView detach操作的時候出現(xiàn)允青,那么為什么Surfaceview從parent view上面detach的時候容易出現(xiàn)黑塊現(xiàn)象呢橄碾?開發(fā)中遇到SUrfaceView黑塊問題又該如何解決呢?下面對這兩個問題進(jìn)行講解。

回答問題之前堪嫂,我們先了解下Android 普通View的刷新流程和SurfaceView的刷新有什么區(qū)別偎箫。

VSync信號的產(chǎn)生:

關(guān)于頁面渲染,我們經(jīng)常關(guān)注的性能指標(biāo)就是幀率皆串,一般認(rèn)為達(dá)到60 幀/秒 就可以騙過人眼淹办,給人比較順滑的視覺體驗,在Android中有一個很重要的概念就是VSync信號恶复,一般認(rèn)為是16ms發(fā)送一次怜森,Vsync機(jī)制的引入,主要有以下兩個作用:

  • 提升UI刷新的優(yōu)先級谤牡,使得UI刷新操作能夠及時執(zhí)行副硅;

  • 在CPU、GPU和Display之間保持同步翅萤,減少Jank幀和屏幕渲染延遲恐疲。

image.jpeg

VSync信號由硬件產(chǎn)生,決定于顯示器的掃描頻率套么,硬件產(chǎn)生原始的VSync信號后培己,會被轉(zhuǎn)化為兩個VSync信號,一個用于通知APP層去刷新UI胚泌,一個用于通知SurfaceFlingger取graphic buffer組合處理后給顯示屏顯示省咨。VSync信號分發(fā)流程如下:

image.jpeg

SurfaceFlinger:

SurfaceFlinger是系統(tǒng)進(jìn)程,用于整合不同APP不同Window的圖像玷室,合成之后給硬件顯示零蓉。

每一個Layer對應(yīng)java層的Surface,即一個窗口穷缤,一個Activity對應(yīng)一個Surface敌蜂,一個WindowManager創(chuàng)建出來的小窗對應(yīng)一個獨(dú)立的Surface,SurfaceView比較特殊津肛,盡管可以嵌入在Activity的布局中紊册,但實際上它獨(dú)占一個Surface;這個特性與本文最開始提出的問題息息相關(guān)快耿,后文會繼續(xù)分析囊陡。

image.jpeg
image.jpeg

基本流程如下:

image.jpeg

步驟1,2:CPU和GPU處理完之后將buffer放到BufferQueue掀亥,并調(diào)用onFrameAvailable通知SurfaceFlinger有可用buffer了撞反。

步驟3:SurfaceFlinger再通過內(nèi)部MessageQueue調(diào)用requestNextVsync請求接收下一個VSYNC用于合成。

步驟4搪花,5:下一個VSYNC到了之后回調(diào)MessageQueue的handleMessage函數(shù)遏片,實際調(diào)到SurfaceFlinger的onMessageReceived函數(shù)處理如下兩種類型消息:

image.jpeg

步驟6嘹害,7:在處理REFRESH消息時最終會調(diào)用acquireBuffer函數(shù)從BufferQueue中將之前APP繪制完成的buffer取出來合成。

從上文可以看出吮便,SurfaceFlinger的組合圖層給硬件顯示之前笔呀,需要先去取graphic buffer,那么graphic buffer又是誰去更新的呢髓需?對于普通View和SurfaceView來說许师,這個機(jī)制會有所差別。

普通View刷新機(jī)制:

舉例Activity中的一個TextView的更新如下:

如果應(yīng)用層通過調(diào)用TextView的setText方法修改顯示的文案僚匆,總體的執(zhí)行流程如下:

image.jpeg

步驟描述:

  1. TextView調(diào)用setText方法微渠,會執(zhí)行到TextView的invalidate方法,這就會遞歸調(diào)用parent的invalidate咧擂,一直到ViewRootImpl類的invalidate方法逞盆,這個方法會調(diào)用到scheduleTraversals

  2. ViewRootImpl通過scheduleTraversals方法會調(diào)用到Choreographer的postCallback方法,postCallback會記錄ViewRootImpl中的mTraversalRunnable松申,并向底層注冊監(jiān)聽下一個vSync信號

  3. 底層的vSync信號過來之后云芦,才會通過給主線程發(fā)送Runnable任務(wù),執(zhí)行Choreographer的doFrame方法,這里面真正調(diào)用執(zhí)行ViewRootImpl中的doTraversal(包括performMeasure、performLayout、performDraw)流程

  4. draw的具體實現(xiàn)通過ThreadedRenderer類,調(diào)用到c++層的RenderThread泉孩,實現(xiàn)在render thread執(zhí)行GPU計算,更新SurfaceFlinger中buffer 隊列

  5. 下一次SurfaceFlinger收到Vsync信號的時候倔既,就可以真正將這次setText的內(nèi)容交給硬件缕碎,顯示給用戶了

因此,Android系統(tǒng)中普通View的渲染灼捂,并不是代碼執(zhí)行完立即顯示到屏幕上的离例,而是需要在設(shè)置變化之后,等待消費(fèi)下一次給APP的vSync信號悉稠,才能把新的圖像更新給SurfaceFlinger宫蛆,而后才能真正顯示出來。

UI刷新通用流程總結(jié)如下:

image.jpeg

步驟1:View調(diào)用invalidate方法進(jìn)行重繪時最終會遞歸調(diào)用到ViewRootImpl中的猛。

步驟2: ViewRootImpl并不會立即會View進(jìn)行繪制耀盗,而是調(diào)用scheduleTraversals將繪制請求給到Choreographer,并開始同步屏障卦尊,保證UI處理的高優(yōu)先級叛拷。

步驟3,4: 通過postCallback將繪制請求給到Choreographer之后岂却,Choreographer最終會將監(jiān)聽下一個VSYNC的請求發(fā)送到SurfaceFlinger進(jìn)程的DispSync這個類忿薇,這是VSYNC分發(fā)的核心裙椭。

步驟5,6:當(dāng)下一個VSYNC到來之后會回調(diào)Choreographer的onVsync方法署浩,onVsync中調(diào)用doFrame揉燃,doCallbacks處理View的繪制請求。

步驟7:View繪制請求的入口即ViewRootImpl的performTraversals筋栋,這個方法會依次執(zhí)行View的onMeasure炊汤,onLayout,onDraw開始View的繪制流程二汛。

步驟8:硬件加速引入之后UI的具體繪制會在一個單獨(dú)的渲染線程RenderThread婿崭,CPU為View構(gòu)建DisplayList(包含繪制指令和數(shù)據(jù))之后將數(shù)據(jù)共享給GPU,剩下的繪制操作由GPU在RenderThread線程完成肴颊。

步驟9氓栈,10,11:向BufferQueue中dequeue一塊可用GraphicBuffer之后由GPU對這個塊buffer進(jìn)行操作婿着,完成之后交換buffer(dequeue的是back buffer授瘦,front buffer用于顯示,back buffer繪制完成之后和front buffer交換)竟宋。

步驟12:此時CPU和GPU對buffer的繪制已經(jīng)完成(概念上已經(jīng)完成提完,實際上GPU可能還在操作,依賴Fence進(jìn)行同步)丘侠,接著通過queueBuffer函數(shù)將buffer轉(zhuǎn)移到BufferQueue徒欣,然后通知SurfaceFlinger有可用buffer了。

CPU蜗字、GPU打肝、SurfaceFlinger如何協(xié)作:

image.jpeg

SurfaceView的刷新與銷毀:

挖洞與繪制:

前面提到過,SurfaceView與普通的View有很大的區(qū)別挪捕,它可以嵌入到Activity的布局中粗梭,但是它是一個獨(dú)立的Surface(Layer),內(nèi)容的刷新流程也跟普通的View完全不一樣级零。SurfaceView在Activity中的布局断医,只決定它的顯示位置。如果沒有設(shè)置setZOrderOnTop為true奏纪,SurfaceView的窗口在Activity窗口的下面鉴嗤,SurfaceView這個Layer的顯示,依賴于ViewRootImpl中挖洞的邏輯(gatherTransparentRegion)序调,在ViewRootImpl類中performLayout邏輯執(zhí)行完之后躬窜,會收集SurfaceView需要透出的區(qū)域,并把這個信息傳遞給底層炕置,將這個區(qū)域設(shè)置為透明荣挨,這樣Actvity這一層的Layer就不會遮擋下面SurfaceView的Layer男韧。

image.jpeg

挖洞流程如下:

image.jpeg

SurfaceView支持在后臺線程直接繪制內(nèi)容,基本繪制流程如下默垄,調(diào)用了unlockCanvasAndPost之后此虑,便會將在Canvas上繪制的內(nèi)容通過獨(dú)立的RenderProxy處理后提交給SurfaceFlinger合成,后面就可顯示出來了口锭。也可以通過holder.getSurface()獲取到Surface之后朦前,直接通過OpenGl渲染。

image.jpeg

銷毀:

這里講SurfaceView的銷毀主要指的是將SurfaceView對應(yīng)的Layer從SurfaceFlinger中移除鹃操。一般可以通過直接設(shè)置這個SurfaceView本身不可見(注意設(shè)置這個SurfaceView的父View不可見不會觸發(fā)Layer的移除)或者將這個SurfaceView從ViewTree上remove掉實現(xiàn)韭寸。如VC中使用的是從父View remove這個SurfaceView的方法實現(xiàn)SurfaceView資源的釋放和視圖的刷新。

當(dāng)調(diào)用parent.removeView將SurfaceView移除時荆隘,流程如下:

image.jpeg

可以看出恩伺,當(dāng)SurfaceView被從父View上remove掉時,是直接調(diào)用代碼椰拒,將自己對應(yīng)的Layer從的SurfaceFlinger中移除掉了晶渠。并不像普通的View更新一樣,需要等待下一個vSync信號燃观,在主線程插入Runnable任務(wù)觸發(fā)doTraversal的流程褒脯,然后再將這個變化反應(yīng)給SurfaceFlinger。

回到文初的問題:

結(jié)合前面的調(diào)用流程缆毁,可以知道番川,在refreshAllUnit的過程中,由于這個方法總體耗時較長脊框,并且在主線程執(zhí)行颁督,這期間Choreographer沒辦法插入任務(wù)去執(zhí)行doTraversal的流程,因此Activity對應(yīng)的代碼執(zhí)行了缚陷,但實際上并沒有更新顯示适篙。而SurfaceView被remove掉之后往核,會直接更新顯示箫爷,這中間就有一個時間差,導(dǎo)致SurfaceView原來顯示的區(qū)域出現(xiàn)了黑塊(挖出來的洞)聂儒。

image.jpeg

那么如何解決SurfaceView黑塊的問題呢虎锚?我們可以在調(diào)用SurfaceView的detach方法之前,插入16ms的延時衩婚,先讓SurfaceView的parent視圖區(qū)域變得不可見窜护,切換為新的視圖成功之后,再調(diào)用SurfaceView的detach方法非春。

參考:

https://www.mtyun.com/library/hardware-accelerate

https://developer.android.com/guide/topics/graphics/hardware-accel?hl=zh-cn

https://sharrychoo.github.io/blog/android-source/surfaceflinger-vsync-dispatch

https://source.android.google.cn/devices/graphics/implement-vsync

https://www.youtube.com/watch?v=zdQRIYOST64

https://blog.csdn.net/qq_31339141/article/details/108503315

https://mp.weixin.qq.com/s/IIh2g1i6Y4rZeCTY-t6_8w

https://www.codenong.com/cs107053967/

https://source.android.google.cn/devices/graphics/implement-vsync

https://blog.csdn.net/qq_34211365/article/details/107996767

https://juejin.cn/post/7004420015038414885

https://blog.csdn.net/u010164190/article/details/80185469

https://huanle19891345.github.io/en/android/system/%E7%B3%BB%E7%BB%9F%E7%BB%98%E5%88%B6/%E7%A1%AC%E4%BB%B6%E5%8A%A0%E9%80%9F%E7%BB%98%E5%88%B6/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末柱徙,一起剝皮案震驚了整個濱河市缓屠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌护侮,老刑警劉巖敌完,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異羊初,居然都是意外死亡滨溉,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門长赞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晦攒,“玉大人,你說我怎么就攤上這事得哆「眨” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵柳恐,是天一觀的道長伐脖。 經(jīng)常有香客問我,道長乐设,這世上最難降的妖魔是什么讼庇? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮近尚,結(jié)果婚禮上蠕啄,老公的妹妹穿的比我還像新娘。我一直安慰自己戈锻,他們只是感情好歼跟,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著格遭,像睡著了一般哈街。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拒迅,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天骚秦,我揣著相機(jī)與錄音,去河邊找鬼璧微。 笑死作箍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的前硫。 我是一名探鬼主播胞得,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼屹电!你這毒婦竟也來了阶剑?” 一聲冷哼從身側(cè)響起跃巡,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎牧愁,沒想到半個月后瓷炮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡递宅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年娘香,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片办龄。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡烘绽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俐填,到底是詐尸還是另有隱情安接,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布英融,位于F島的核電站盏檐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏驶悟。R本人自食惡果不足惜胡野,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痕鳍。 院中可真熱鬧硫豆,春花似錦、人聲如沸笼呆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诗赌。三九已至汗茄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間铭若,已是汗流浹背洪碳。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奥喻,地道東北人偶宫。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓非迹,卻偏偏與公主長得像环鲤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子憎兽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容