我們知道,當(dāng)在view繪制時(shí)進(jìn)行耗時(shí)操作或者復(fù)雜的動(dòng)畫(huà),會(huì)出現(xiàn)丟幀或卡頓現(xiàn)象,用戶體驗(yàn)極為不好。Android系統(tǒng)每隔16ms就會(huì)發(fā)出一次VSYNC信號(hào)觸發(fā)對(duì)UI進(jìn)行渲染,如果這16ms內(nèi)我們沒(méi)有完成對(duì)視圖的繪制,那么就會(huì)出現(xiàn)丟幀的情況。之所以這樣是因?yàn)?,人眼與大腦之間的協(xié)作無(wú)法感知超過(guò)60fps的畫(huà)面更新。60幀每秒就意味著:16ms=1000/60Hz。Android提供了SurfaceView來(lái)解決這種情況。SurfaceView可以實(shí)現(xiàn)復(fù)雜的2D動(dòng)畫(huà)、播放視頻、攝像頭預(yù)覽等。
初識(shí)SurfaceView
五年前,就已經(jīng)了解SurfaceView,知道它可以配合MediaPlayer播放一個(gè)視頻流媒體,其中SurfaceHolder是SurfaceView裝載需要顯示的一幀幀圖像的容器。
大部分軟件是如何解析一段視頻流呢?
首先它需要先確定視頻的格式,這個(gè)和解碼相關(guān),不同的格式視頻編碼不同。知道了視頻的編碼格式后,再通過(guò)編碼格式進(jìn)行解碼,最后得到一幀一幀的圖像,并把這些圖像快速的顯示在界面上,即為播放一段視頻。SurfaceView在Android中可以完成這個(gè)功能。
SurfaceView雙緩沖機(jī)制
SurfaceView跟大部分視頻應(yīng)用一樣,把視頻流解析成一幀幀的圖像然后顯示。如果把視頻解析過(guò)程放到一個(gè)線程中完成,可能在上一幀圖像已經(jīng)顯示過(guò)后,下一幀圖像還沒(méi)有來(lái)得及解析,這樣會(huì)導(dǎo)致畫(huà)面的不流暢或者聲音和視頻不同步的問(wèn)題。所以SurfaceView和大部分視頻應(yīng)用一樣,通過(guò)雙緩沖的機(jī)制來(lái)顯示幀圖像。那么什么是雙緩沖呢?雙緩沖可以理解為有兩個(gè)線程輪番去解析視頻流的幀圖像,當(dāng)一個(gè)線程解析完幀圖像后,把圖像渲染到界面中,同時(shí)另一線程開(kāi)始解析下一幀圖像,使得兩個(gè)線程輪番配合去解析視頻流,以達(dá)到流暢播放的效果。
下圖演示了雙緩沖的過(guò)程,線程1和線程2配合解析渲染視頻流的幀圖像:
SurfaceHolder
SurfaceView內(nèi)部實(shí)現(xiàn)了雙緩沖的機(jī)制,但是實(shí)現(xiàn)這個(gè)功能是非常消耗系統(tǒng)內(nèi)存的。因?yàn)橐苿?dòng)設(shè)備的局限性,Android在設(shè)計(jì)的時(shí)候規(guī)定,SurfaceView如果為用戶可見(jiàn)的時(shí)候,創(chuàng)建SurfaceView的SurfaceHolder用于顯示視頻流解析的幀圖片,如果發(fā)現(xiàn)SurfaceView變?yōu)橛脩舨豢梢?jiàn)的時(shí)候,則立即銷(xiāo)毀SurfaceView的SurfaceHolder,以達(dá)到節(jié)約系統(tǒng)資源的目的。
如果開(kāi)發(fā)人員不對(duì)SurfaceHolder進(jìn)行維護(hù),會(huì)出現(xiàn)最小化程序后,再打開(kāi)應(yīng)用的時(shí)候,視頻的聲音在繼續(xù)播放,但是不顯示畫(huà)面了的情況,這就是因?yàn)楫?dāng)SurfaceView不被用戶可見(jiàn)的時(shí)候,之前的SurfaceHolder已經(jīng)被銷(xiāo)毀了,再次進(jìn)入的時(shí)候,界面上的SurfaceHolder已經(jīng)是新的SurfaceHolder了。所以SurfaceHolder需要我們開(kāi)發(fā)人員去編碼維護(hù),維護(hù)SurfaceHolder需要用到它的一個(gè)回調(diào),SurfaceHolder.Callback(),它需要實(shí)現(xiàn)如下三個(gè)方法:
- void surfaceDestroyed(SurfaceHolder holder)
當(dāng)SurfaceHolder被銷(xiāo)毀的時(shí)候回調(diào)
2.void surfaceCreated(SurfaceHolder holder)
當(dāng)SurfaceHolder被創(chuàng)建的時(shí)候回調(diào)
3.void surfaceChange(SurfaceHolder holder)
當(dāng)SurfaceHolder的尺寸發(fā)生變化的時(shí)候被回調(diào)
SurfaceView與普通View的區(qū)別
SurfaceView,它擁有獨(dú)立的繪圖表面,即它不與其宿主窗口共享同一個(gè)繪圖表面。由于擁有獨(dú)立的繪圖表面,因此SurfaceView的UI就可以在一個(gè)獨(dú)立的線程中進(jìn)行繪制。又由于不會(huì)占用主線程資源,SurfaceView一方面可以實(shí)現(xiàn)復(fù)雜而高效的UI,另一方面又不會(huì)導(dǎo)致用戶輸入得不到及時(shí)響應(yīng)。
普通的Android控件,例如TextView、Button等,都是將自己的UI繪制在宿主窗口的繪圖表面之上,這意味著它們的UI是在應(yīng)用程序的主線程中進(jìn)行繪制的。由于應(yīng)用程序的主線程除了要繪制UI之外,還需要及時(shí)地響應(yīng)用戶輸入,否則的話,系統(tǒng)就會(huì)認(rèn)為應(yīng)用程序沒(méi)有響應(yīng)了,因此就會(huì)彈出一個(gè)ANR對(duì)話框出來(lái)。對(duì)于一些游戲畫(huà)面,或者攝像頭預(yù)覽、視頻播放來(lái)說(shuō),它們的UI都比較復(fù)雜,而且要求能夠進(jìn)行高效的繪制,因此,它們的UI就不適合在應(yīng)用程序的主線程中進(jìn)行繪制。這時(shí)候就必須要給那些需要復(fù)雜而高效UI的視圖生成一個(gè)獨(dú)立的繪圖表面,以及使用一個(gè)獨(dú)立的線程來(lái)繪制這些視圖的UI。
一般來(lái)說(shuō),每一個(gè)窗口在SurfaceFlinger服務(wù)中都對(duì)應(yīng)有一個(gè)Layer,用來(lái)描述它的繪圖表面。對(duì)于那些具有SurfaceView的窗口來(lái)說(shuō),每一個(gè)SurfaceView在SurfaceFlinger服務(wù)中還對(duì)應(yīng)有一個(gè)獨(dú)立的Layer或者LayerBuffer,用來(lái)單獨(dú)描述它的繪圖表面,以區(qū)別于它的宿主窗口的繪圖表面。SurfaceFlinger服務(wù)負(fù)責(zé)繪制Android應(yīng)用程序的UI。SurfaceFlinger服務(wù)運(yùn)行在Android系統(tǒng)的System進(jìn)程中,它負(fù)責(zé)管理Android系統(tǒng)的幀緩沖區(qū)(Frame Buffer)。
SurfaceView原理
官方文檔:
SurfaceView:Provides a dedicated drawing surface embedded inside of a view hierarchy. The surface is Z ordered so that it is behind the window holding its SurfaceView; the SurfaceView punches a hole in its window to allow its surface to be displayed.
翻譯解釋:
SurfaceView提供一個(gè)嵌入視圖層級(jí)的專用的繪圖表面。繪圖表面是在Z軸上有序的,SurfaceView在宿主窗體的后面。SurfaceView在宿主窗體上“挖”了一個(gè)洞,以此來(lái)顯示自己的表面。實(shí)際上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不過(guò)是在其宿主Activity窗口上設(shè)置了一塊透明區(qū)域,以顯示自己內(nèi)容
SurfaceView的繪圖表面的創(chuàng)建
我們知道,Activity、Window、View三者緊密聯(lián)系在一起。我們?cè)贏ctivity中設(shè)置setContentView(),最終會(huì)調(diào)用PhoneWindow的setContentView()。經(jīng)過(guò)WindowManagerImpl#addView,WindowManagerGlobal#addView,ViewRootImpl#setView方法,最頂層視圖DecorView被添加到Window上。最后通過(guò)WMS調(diào)用ViewRootImpl#performTraverals方法開(kāi)始View的測(cè)量、布局、繪制流程。
ViewRootImpl類(lèi)的成員函數(shù)performTraversals在執(zhí)行的過(guò)程中,如果發(fā)現(xiàn)當(dāng)前窗口的繪圖表面還沒(méi)有創(chuàng)建,或者發(fā)現(xiàn)當(dāng)前窗口的繪圖表面已經(jīng)失效了,那么就會(huì)請(qǐng)求WindowManagerService服務(wù)創(chuàng)建一個(gè)新的繪圖表面,同時(shí),它還會(huì)通過(guò)一系列的回調(diào)函數(shù)來(lái)讓嵌入在窗口里面的SurfaceView有機(jī)會(huì)創(chuàng)建自己的繪圖表面。
雖然SurfaceView不與它的宿主窗口共享同一個(gè)繪圖表面,但是它仍然是屬于宿主窗口的視圖結(jié)構(gòu)的一個(gè)結(jié)點(diǎn)的,也就是說(shuō),SurfaceView仍然是會(huì)參與到宿主窗口的某些執(zhí)行流程中去。
1 SurfaceView.onAttachedToWindow
說(shuō)明:SurfaceView在Z軸上位置是小于其宿主窗口的Z軸位置的。為了保證SurfaceView的UI是可見(jiàn)的,SurfaceView就需要在其宿主窗口的上面打一個(gè)孔出來(lái),實(shí)際上就是在其宿主窗口的繪圖表面上設(shè)置一塊透明區(qū)域,以便可以將自己顯示出來(lái)。SurfaceView類(lèi)的成員函數(shù)onAttachedToWindow調(diào)用mParent.requestTransparentRegion(SurfaceView.this)去通知父View,當(dāng)前正在處理的SurfaceView需要在宿主窗口的繪圖表面上打一個(gè)孔,即需要在宿主窗口的繪圖表面上設(shè)置一塊透明區(qū)域。
2. SurfaceView.onWindowVisibilityChanged
說(shuō)明:類(lèi)SurfaceView調(diào)用updateSurface來(lái)更新當(dāng)前正在處理的SurfaceView。在更新的過(guò)程中,如果發(fā)現(xiàn)當(dāng)前正在處理的SurfaceView還沒(méi)有創(chuàng)建繪圖表面,那么就會(huì)請(qǐng)求WindowManagerService服務(wù)為它創(chuàng)建一個(gè)。
3.SurfaceView.updateRequestedVisibility
說(shuō)明:mWindowVisibility表示SurfaceView的宿主窗口的可見(jiàn)性,mViewVisibility表示SurfaceView自身的可見(jiàn)性。只有當(dāng)mWindowVisibility和mViewVisibility的值均等于true,且宿主窗口沒(méi)有停止,mRequestedVisible的值才為true,表示SurfaceView是可見(jiàn)的。
4.SurfaceView.updateSurface
mSurface:這個(gè)Surface對(duì)象描述的是SurfaceView專有的繪圖表面,在SurfaceView對(duì)象創(chuàng)建時(shí)就會(huì)被實(shí)例化。updateSurface方法根據(jù)實(shí)際條件判斷創(chuàng)建或更新mSurface。
SurfaceView的繪制
如何在一個(gè)繪圖表面上進(jìn)行UI繪制?
1.在繪圖表面的基礎(chǔ)上建立一塊畫(huà)布,即獲得一個(gè)Canvas對(duì)象
2.利用Canvas類(lèi)提供的繪圖方法在前面獲得的畫(huà)布上繪制任意的UI
3.最后通過(guò)SurfaceFlinger服務(wù)將它合成到屏幕上去
SurfaceView如何繪制?
1.通過(guò)SurfaceView的getHolder方法獲得SurfaceHolder
2.通過(guò)SurfaceHolder的lockCanvas方法獲得Canvas
3.上面會(huì)走到Surface的lockCanvas方法獲得Canvas
4.在Canvas上繪制UI
5.通過(guò)SurfaceHolder的unlockCanvasAndPost將繪制好的canvas投遞到surface上
6.上面會(huì)走到Surface的unlockCanvasAndPost方法
參考文檔
https://developer.android.com/reference/android/view/SurfaceView
https://www.cnblogs.com/plokmju/p/android_SurfaceView.html
https://blog.csdn.net/luoshengyang/article/details/8661317/