問題:
舉例一個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幀和屏幕渲染延遲恐疲。
VSync信號由硬件產(chǎn)生,決定于顯示器的掃描頻率套么,硬件產(chǎn)生原始的VSync信號后培己,會被轉(zhuǎn)化為兩個VSync信號,一個用于通知APP層去刷新UI胚泌,一個用于通知SurfaceFlingger取graphic buffer組合處理后給顯示屏顯示省咨。VSync信號分發(fā)流程如下:
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ù)分析囊陡。
基本流程如下:
步驟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ù)處理如下兩種類型消息:
步驟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í)行流程如下:
步驟描述:
TextView調(diào)用setText方法微渠,會執(zhí)行到TextView的invalidate方法,這就會遞歸調(diào)用parent的invalidate咧擂,一直到ViewRootImpl類的invalidate方法逞盆,這個方法會調(diào)用到scheduleTraversals
ViewRootImpl通過scheduleTraversals方法會調(diào)用到Choreographer的postCallback方法,postCallback會記錄ViewRootImpl中的mTraversalRunnable松申,并向底層注冊監(jiān)聽下一個vSync信號
底層的vSync信號過來之后云芦,才會通過給主線程發(fā)送Runnable任務(wù),執(zhí)行Choreographer的doFrame方法,這里面真正調(diào)用執(zhí)行ViewRootImpl中的doTraversal(包括performMeasure、performLayout、performDraw)流程
draw的具體實現(xiàn)通過ThreadedRenderer類,調(diào)用到c++層的RenderThread泉孩,實現(xiàn)在render thread執(zhí)行GPU計算,更新SurfaceFlinger中buffer 隊列
下一次SurfaceFlinger收到Vsync信號的時候倔既,就可以真正將這次setText的內(nèi)容交給硬件缕碎,顯示給用戶了
因此,Android系統(tǒng)中普通View的渲染灼捂,并不是代碼執(zhí)行完立即顯示到屏幕上的离例,而是需要在設(shè)置變化之后,等待消費(fèi)下一次給APP的vSync信號悉稠,才能把新的圖像更新給SurfaceFlinger宫蛆,而后才能真正顯示出來。
UI刷新通用流程總結(jié)如下:
步驟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é)作:
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男韧。
挖洞流程如下:
SurfaceView支持在后臺線程直接繪制內(nèi)容,基本繪制流程如下默垄,調(diào)用了unlockCanvasAndPost之后此虑,便會將在Canvas上繪制的內(nèi)容通過獨(dú)立的RenderProxy處理后提交給SurfaceFlinger合成,后面就可顯示出來了口锭。也可以通過holder.getSurface()獲取到Surface之后朦前,直接通過OpenGl渲染。
銷毀:
這里講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移除時荆隘,流程如下:
可以看出恩伺,當(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)了黑塊(挖出來的洞)聂儒。
那么如何解決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