我們知道俺祠,當在view繪制時進行耗時操作或者復雜的動畫潘懊,會出現(xiàn)丟幀或卡頓現(xiàn)象线婚,用戶體驗極為不好。Android系統(tǒng)每隔16ms就會發(fā)出一次VSYNC信號觸發(fā)對UI進行渲染,如果這16ms內(nèi)我們沒有完成對視圖的繪制弦蹂,那么就會出現(xiàn)丟幀的情況肩碟。之所以這樣是因為,人眼與大腦之間的協(xié)作無法感知超過60fps的畫面更新凸椿。60幀每秒就意味著:16ms=1000/60Hz削祈。Android提供了SurfaceView來解決這種情況。SurfaceView可以實現(xiàn)復雜的2D動畫脑漫、播放視頻髓抑、攝像頭預覽等。
初識SurfaceView
五年前优幸,就已經(jīng)了解SurfaceView吨拍,知道它可以配合MediaPlayer播放一個視頻流媒體,其中SurfaceHolder是SurfaceView裝載需要顯示的一幀幀圖像的容器网杆。
大部分軟件是如何解析一段視頻流呢羹饰?
首先它需要先確定視頻的格式,這個和解碼相關跛璧,不同的格式視頻編碼不同严里。知道了視頻的編碼格式后,再通過編碼格式進行解碼追城,最后得到一幀一幀的圖像,并把這些圖像快速的顯示在界面上燥撞,即為播放一段視頻座柱。SurfaceView在Android中可以完成這個功能。
SurfaceView雙緩沖機制
SurfaceView跟大部分視頻應用一樣物舒,把視頻流解析成一幀幀的圖像然后顯示色洞。如果把視頻解析過程放到一個線程中完成,可能在上一幀圖像已經(jīng)顯示過后冠胯,下一幀圖像還沒有來得及解析火诸,這樣會導致畫面的不流暢或者聲音和視頻不同步的問題。所以SurfaceView和大部分視頻應用一樣荠察,通過雙緩沖的機制來顯示幀圖像置蜀。那么什么是雙緩沖呢?雙緩沖可以理解為有兩個線程輪番去解析視頻流的幀圖像悉盆,當一個線程解析完幀圖像后盯荤,把圖像渲染到界面中,同時另一線程開始解析下一幀圖像焕盟,使得兩個線程輪番配合去解析視頻流秋秤,以達到流暢播放的效果。
下圖演示了雙緩沖的過程,線程1和線程2配合解析渲染視頻流的幀圖像:
SurfaceHolder
SurfaceView內(nèi)部實現(xiàn)了雙緩沖的機制灼卢,但是實現(xiàn)這個功能是非常消耗系統(tǒng)內(nèi)存的绍哎。因為移動設備的局限性,Android在設計的時候規(guī)定鞋真,SurfaceView如果為用戶可見的時候蛇摸,創(chuàng)建SurfaceView的SurfaceHolder用于顯示視頻流解析的幀圖片,如果發(fā)現(xiàn)SurfaceView變?yōu)橛脩舨豢梢姷臅r候灿巧,則立即銷毀SurfaceView的SurfaceHolder赶袄,以達到節(jié)約系統(tǒng)資源的目的。
如果開發(fā)人員不對SurfaceHolder進行維護抠藕,會出現(xiàn)最小化程序后饿肺,再打開應用的時候,視頻的聲音在繼續(xù)播放盾似,但是不顯示畫面了的情況敬辣,這就是因為當SurfaceView不被用戶可見的時候,之前的SurfaceHolder已經(jīng)被銷毀了零院,再次進入的時候溉跃,界面上的SurfaceHolder已經(jīng)是新的SurfaceHolder了。所以SurfaceHolder需要我們開發(fā)人員去編碼維護告抄,維護SurfaceHolder需要用到它的一個回調(diào)撰茎,SurfaceHolder.Callback(),它需要實現(xiàn)如下三個方法:
- void surfaceDestroyed(SurfaceHolder holder)
當SurfaceHolder被銷毀的時候回調(diào)
2.void surfaceCreated(SurfaceHolder holder)
當SurfaceHolder被創(chuàng)建的時候回調(diào)
3.void surfaceChange(SurfaceHolder holder)
當SurfaceHolder的尺寸發(fā)生變化的時候被回調(diào)
SurfaceView與普通View的區(qū)別
SurfaceView打洼,它擁有獨立的繪圖表面龄糊,即它不與其宿主窗口共享同一個繪圖表面。由于擁有獨立的繪圖表面募疮,因此SurfaceView的UI就可以在一個獨立的線程中進行繪制炫惩。又由于不會占用主線程資源,SurfaceView一方面可以實現(xiàn)復雜而高效的UI阿浓,另一方面又不會導致用戶輸入得不到及時響應他嚷。
普通的Android控件,例如TextView芭毙、Button等筋蓖,都是將自己的UI繪制在宿主窗口的繪圖表面之上,這意味著它們的UI是在應用程序的主線程中進行繪制的稿蹲。由于應用程序的主線程除了要繪制UI之外扭勉,還需要及時地響應用戶輸入,否則的話苛聘,系統(tǒng)就會認為應用程序沒有響應了涂炎,因此就會彈出一個ANR對話框出來忠聚。對于一些游戲畫面,或者攝像頭預覽唱捣、視頻播放來說两蟀,它們的UI都比較復雜,而且要求能夠進行高效的繪制震缭,因此赂毯,它們的UI就不適合在應用程序的主線程中進行繪制。這時候就必須要給那些需要復雜而高效UI的視圖生成一個獨立的繪圖表面拣宰,以及使用一個獨立的線程來繪制這些視圖的UI党涕。
一般來說,每一個窗口在SurfaceFlinger服務中都對應有一個Layer巡社,用來描述它的繪圖表面膛堤。對于那些具有SurfaceView的窗口來說,每一個SurfaceView在SurfaceFlinger服務中還對應有一個獨立的Layer或者LayerBuffer晌该,用來單獨描述它的繪圖表面肥荔,以區(qū)別于它的宿主窗口的繪圖表面。SurfaceFlinger服務負責繪制Android應用程序的UI朝群。SurfaceFlinger服務運行在Android系統(tǒng)的System進程中燕耿,它負責管理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提供一個嵌入視圖層級的專用的繪圖表面姜胖。繪圖表面是在Z軸上有序的誉帅,SurfaceView在宿主窗體的后面。SurfaceView在宿主窗體上“挖”了一個洞谭期,以此來顯示自己的表面堵第。實際上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不過是在其宿主Activity窗口上設置了一塊透明區(qū)域隧出,以顯示自己內(nèi)容
SurfaceView的繪圖表面的創(chuàng)建
我們知道,Activity阀捅、Window胀瞪、View三者緊密聯(lián)系在一起。我們在Activity中設置setContentView()饲鄙,最終會調(diào)用PhoneWindow的setContentView()凄诞。經(jīng)過WindowManagerImpl#addView,WindowManagerGlobal#addView忍级,ViewRootImpl#setView方法帆谍,最頂層視圖DecorView被添加到Window上。最后通過WMS調(diào)用ViewRootImpl#performTraverals方法開始View的測量轴咱、布局汛蝙、繪制流程烈涮。
ViewRootImpl類的成員函數(shù)performTraversals在執(zhí)行的過程中,如果發(fā)現(xiàn)當前窗口的繪圖表面還沒有創(chuàng)建窖剑,或者發(fā)現(xiàn)當前窗口的繪圖表面已經(jīng)失效了坚洽,那么就會請求WindowManagerService服務創(chuàng)建一個新的繪圖表面,同時西土,它還會通過一系列的回調(diào)函數(shù)來讓嵌入在窗口里面的SurfaceView有機會創(chuàng)建自己的繪圖表面讶舰。
雖然SurfaceView不與它的宿主窗口共享同一個繪圖表面,但是它仍然是屬于宿主窗口的視圖結構的一個結點的需了,也就是說跳昼,SurfaceView仍然是會參與到宿主窗口的某些執(zhí)行流程中去。
1 SurfaceView.onAttachedToWindow
說明:SurfaceView在Z軸上位置是小于其宿主窗口的Z軸位置的肋乍。為了保證SurfaceView的UI是可見的鹅颊,SurfaceView就需要在其宿主窗口的上面打一個孔出來,實際上就是在其宿主窗口的繪圖表面上設置一塊透明區(qū)域住拭,以便可以將自己顯示出來挪略。SurfaceView類的成員函數(shù)onAttachedToWindow調(diào)用mParent.requestTransparentRegion(SurfaceView.this)去通知父View,當前正在處理的SurfaceView需要在宿主窗口的繪圖表面上打一個孔滔岳,即需要在宿主窗口的繪圖表面上設置一塊透明區(qū)域杠娱。
2. SurfaceView.onWindowVisibilityChanged
說明:類SurfaceView調(diào)用updateSurface來更新當前正在處理的SurfaceView。在更新的過程中谱煤,如果發(fā)現(xiàn)當前正在處理的SurfaceView還沒有創(chuàng)建繪圖表面摊求,那么就會請求WindowManagerService服務為它創(chuàng)建一個。
3.SurfaceView.updateRequestedVisibility
說明:mWindowVisibility表示SurfaceView的宿主窗口的可見性刘离,mViewVisibility表示SurfaceView自身的可見性室叉。只有當mWindowVisibility和mViewVisibility的值均等于true,且宿主窗口沒有停止硫惕,mRequestedVisible的值才為true茧痕,表示SurfaceView是可見的。
4.SurfaceView.updateSurface
mSurface:這個Surface對象描述的是SurfaceView專有的繪圖表面恼除,在SurfaceView對象創(chuàng)建時就會被實例化踪旷。updateSurface方法根據(jù)實際條件判斷創(chuàng)建或更新mSurface。
SurfaceView的繪制
如何在一個繪圖表面上進行UI繪制豁辉?
1.在繪圖表面的基礎上建立一塊畫布令野,即獲得一個Canvas對象
2.利用Canvas類提供的繪圖方法在前面獲得的畫布上繪制任意的UI
3.最后通過SurfaceFlinger服務將它合成到屏幕上去
SurfaceView如何繪制?
1.通過SurfaceView的getHolder方法獲得SurfaceHolder
2.通過SurfaceHolder的lockCanvas方法獲得Canvas
3.上面會走到Surface的lockCanvas方法獲得Canvas
4.在Canvas上繪制UI
5.通過SurfaceHolder的unlockCanvasAndPost將繪制好的canvas投遞到surface上
6.上面會走到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/