天天看點

SurfaceView 與view差別詳解

在Android系統中,有一種特殊的視圖,稱為SurfaceView,它擁有獨立的繪圖表面,即它不與其宿主視窗共享同一個繪圖表面,由于擁有獨立的繪圖表面,是以SurfaceView的UI就可以在一個獨立的線程中進行行繪制,由于不占用主線程資源,SurfaceView一方面可以實作複雜而高效的UI。

SurfaceView的繪制方式效率非常高,因為SurfaceView的視窗重新整理的時候不需要重繪應用程式的視窗(android普通視窗的視圖繪制機制是一層一層的,任何一個子元素或者是局部的重新整理都會導緻整個視圖結構全部重繪一次,是以效率非常低下)。

View通過重新整理來重繪視圖,Android系統通過發出VSYNC信号來進行螢幕的重繪,重新整理的時間間隔一般為16ms,在一些需要頻繁重新整理的界面,如果重新整理執行很多邏輯繪制操作,就會導緻重新整理使用時間超過了16ms,就會導緻丢幀或者卡頓,比如你更新畫面的時間過長,那麼你的主UI線程會被你的繪制函數阻塞,那麼将無法響應按鍵,觸屏等消息,會造成 ANR 問題,SurfaceView雖然繼承自View,但擁有獨立的surface,即它不與其宿主視窗共享同一個surface,可以單獨在一個線程進行繪制,并不會占用主線程的資源,這樣,繪制就會比較高效,遊戲,視訊播放,直播,都可以用SurfaceView來實作,SurfaceView有兩個子類GLSurfaceView和VideoView

SurfaceView 與view差別詳解

SurfaceView裡面鑲嵌的Surface是在包含SurfaceView的宿主Activity視窗(頂層視圖對應的Surface)後面,用來描述SurfaceView的Layer的Z軸位置是小于用來描述其宿主Activity視窗的Layer的Z軸位置的,這樣SurfaceView的Layer就被擋住看不見了,SurfaceView提供了一個可見區域,隻有在這個可見區域内的surface部分内容才可見,就好像SurfaceView會在宿主Activity視窗上面挖一個“洞”出來,以便它的UI可以漏出來對使用者可見,實際上,SurfaceView隻不過是在其宿主Activity視窗上設定了一塊透明區域

SurfaceView 與view差別詳解

如上Activity視窗的頂層視圖DecorView及其兩個TextView控件都是通過視窗的Surface對應的Canvas繪制在SurfaceFlinger服務中的同一個Layer上,而SurfaceView的UI是通過其自己的Surface對應的Canvas繪制在SurfaceFlinger服務中的另外一個Layer上。

要了解 SurfaceView ,還須了解它的另外兩個元件:Surface 和 SurfaceHolder,他們三者之間的關系實質上就是 MVC,Model就是資料模型的意思也就是這裡的Surface;View即視圖也就是這裡的SurfaceView;SurfaceHolder很明顯可以了解為Controller(控制器)。

在SurfaceView中你可以通過SurfaceHolder接口通路它内部的surface,而我們執行繪制的方法就是操作這個 Surface内部的Canvas,處理Canvas畫的效果和動畫,大小,像素等,getHolder()方法可以得到這個SurfaceHolder,通過SurfaceHolder來控制surface的尺寸和格式,或者修改監視surface的變化等等。

SurfaceHolder有三個回調方法可以監聽SurfaceView中的surface的生命周期,SurfaceView一開始建立出來後,它擁有的Surface不一定會一起建立出來,SurfaceView變得可見時,surface被建立,SurfaceView隐藏前,surface被銷毀,被建立了表示可以開始準備繪制了,而被銷毀後我們要釋放其他資源,Surfaceview一般會繼承SurfaceHolder的Callback接口,SurfaceHolder.Callback具有如下的方法:

   surfaceCreated(SurfaceHolder holder):當Surface第一次建立後會立即調用該函數,可以在該函數中做些和繪制界面相關的初始化工作,一般情況下都是在新線程來繪制界面,是以不要在這個函數中繪制Surface。 

   surfaceChanged(SurfaceHolder holder, int format, int width,int height):當Surface的狀态(大小和格式)發生變化的時候會調用該函數,在surfaceCreated調用後該函數至少會被調用一次。 

   surfaceDestroyed(SurfaceHolder holder):當Surface被摧毀前會調用該函數,該函數被調用後就不能繼續使用Surface了,一般在該函數中來清理使用的資源。 

特别需要注意的是SurfaceView和SurfaceHolder.Callback的所有回調方法都是在主線程中回調的,在繪制前必須先合法的擷取 Surface 才能開始繪制内容, SurfaceHolder.Callback.surfaceCreated() 和 SurfaceHolder.Callback.surfaceDestroyed() 之間的狀态為合法的,在這之外使用Surface都會出錯。

在使用SurfaceView過程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或則Canvas lockCanvas(Rect dirty)函數來鎖定并且擷取Surface中的Canvas畫布對象,通過在Canvas上繪制内容來修改Surface中的資料,如果Surface被别的線程占有不可編輯或則尚未建立或者已經被銷毀,調用該函數會傳回null。

在unlockCanvas() 和 lockCanvas()之間Surface的内容是不緩存的,是以需要完全重繪Surface的内容,如果為了提高效率隻重繪變化的部分則可以調用lockCanvas(Rect dirty)函數來指定一個dirty區域,這樣該區域外的内容會緩存起來,隻更新需要重繪的區域,相對部分記憶體要求比較高的遊戲來說,不重畫dirty外的其他區域的像素,可以提高速度。

在調用lockCanvas函數擷取Surface的Canvas後,SurfaceView會利用Surface的一個同步鎖鎖住畫布Canvas,直到調用unlockCanvasAndPost(Canvas canvas)函數,才解鎖畫布并送出改變,将圖形顯示,這裡的同步機制保證Surface的Canvas在繪制過程中不會被改變(被摧毀、修改),避免多個不同的線程同時操作同一個Canvas對象。

雙緩沖:SurfaceView在更新視圖時用了兩個Canvas,一張frontCanvas和一張backCanvas,每次實際顯示的是frontCanvas,backCanvas存儲的是上一次更改前的視圖,當使用lockCanvas()擷取畫布時,得到的實際上是backCanvas而不是正在顯示的frontCanvas,當你在擷取到的backCanvas上繪制完成後,再使用unlockCanvasAndPost(canvas)送出backCanvas視圖,那麼這張backCanvas将替換正在顯示的frontCanvas被顯示出來,原來的frontCanvas将切換到背景作為backCanvas,這樣做的好處是在繪制期間不會出現黑屏。

SurfaceView類的成員變量mRequestedType描述的是SurfaceView的繪圖表面Surface的類型,一般來說,它的值可能等于SURFACE_TYPE_NORMAL或者SURFACE_TYPE_PUSH_BUFFERS,

當一個SurfaceView的繪圖表面的類型等于SURFACE_TYPE_NORMAL的時候,就表示該SurfaceView的繪圖表面所使用的記憶體是一塊普通的記憶體,一般來說,這塊記憶體是由SurfaceFlinger服務來配置設定的,我們可以在應用程式内部自由地通路它,即可以在它上面填充任意的UI資料,然後交給SurfaceFlinger服務來合成,并且顯示在螢幕上,在這種情況下,在SurfaceFlinger服務一端使用一個Layer對象來描述該SurfaceView的繪圖表面。

當一個SurfaceView的繪圖表面的類型等于SURFACE_TYPE_PUSH_BUFFERS的時候,就表示該SurfaceView的繪圖表面所使用的記憶體不是由SurfaceFlinger服務配置設定的,我們不能夠在應用程式内部對它進行操作,是以不能調用lockCanvas來擷取Canvas對象進行繪制了,例如當一個SurfaceView是用來顯示攝像頭預覽或者視訊播放的時候,我們就會将它的繪圖表面的類型設定為SURFACE_TYPE_PUSH_BUFFERS,這樣攝像頭服務或者視訊播放服務就會為該SurfaceView繪圖表面建立一塊記憶體,并且将采集的預覽圖像資料或者視訊幀資料源源不斷地填充到該記憶體中去,在這種情況下,在SurfaceFlinger服務一端使用一個LayerBuffer對象來描述該SurfaceView的繪圖表面。

是以:決定surfaceView的記憶體是普通記憶體(由開發者自己決定用來繪制什麼)還是專用的記憶體(顯示攝像頭或者視訊等,開發者無法使用這塊記憶體)由mRequestType決定,我們在建立了一個SurfaceView之後,可以調用它的SurfaceHolder對象的成員函數setType來修改該SurfaceView的繪圖表面的類型,繪圖表面類型為SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由應用程式來控制的,而是由專門的服務來控制的,例如,攝像頭服務或者視訊播放服務。

SurfaceView類的成員變量mRequestedType目前接收如下的參數:

SURFACE_TYPE_NORMAL:用RAM緩存原生資料的普通Surface 

SURFACE_TYPE_HARDWARE:适用于DMA(Direct memory access )引擎和硬體加速的Surface 

SURFACE_TYPE_GPU:适用于GPU加速的Surface 

SURFACE_TYPE_PUSH_BUFFERS:表明該Surface不包含原生資料,Surface用到的資料由其他對象提供。 

繼續閱讀