SurfaceView原始碼分析
基於 android-27原始碼
ofollow,noindex">https://blog.csdn.net/Luoshengyang/article/details/86613171. 什麼是SurfaceView/">SurfaceView,和普通的View的區別?
- View適用於主動更新的情況,而SurfaceView則適用於被動更新的情況,比如頻繁重新整理介面。
- View在主執行緒中對頁面進行重新整理,而SurfaceView則開啟一個子執行緒來對頁面進行重新整理。
- View在繪圖時沒有實現雙緩衝機制,SurfaceView在底層機制中就實現了雙緩衝機制。(當一個動畫爭先顯示時,程式又在改變它,前面還沒有顯示完,程式又請求重新繪製,這樣螢幕就會不停地閃爍。而雙緩衝技術是把要處理的圖片在記憶體中處理好之後,再將其顯示在螢幕上。雙緩衝主要是為了解決 反覆區域性刷屏帶來的閃爍。把要畫的東西先畫到一個記憶體區域裡,然後整體的一次性畫出來。)
2. SurfaceView的繪製原理
Android應用程式視窗是通過SurfaceFlinger服務來繪製自己的UI。一般來說,每一個視窗在SurfaceFlinger服務中都對應有一個Layer,用來描述它的繪圖表面。對於那些具有SurfaceView的視窗來說,每一個SurfaceView在SurfaceFlinger服務中還對應有一個獨立的Layer或者LayerBuffer,用來單獨描述它的繪圖表面,以區別於它的宿主視窗的繪圖表面。

image
3. SurfaceView的繪製過程

image
SurfaceView的繪圖表面的建立過程從ViewRoot類的成員函式performTraversals開始
3.1 ViewRoot.performTraversals
host = ViewRoot.mView指向DecorView物件,描述當前視窗的頂級檢視
attachInfo = ViewRoot.mAttachInfo指向的AttachInfo是描述串列埠資訊物件
這個方法的主要作用是判斷繪圖表面是否建立,通知View(ViewGroup)附加到視窗 3.2 ,判斷並通知當前視窗的可見性是否變化 3.5
3.2 ViewGroup.dispatchAttachedToWindow() (DecorView ViewGroup)
遍歷通知子檢視新增到視窗 3.3
3.3 View.dispatchAttachedToWindow() (View)
儲存視窗資訊mAttachInfo,呼叫子類的onAttachedToWindow 3.4
3.4 SurfaceView.onAttachedToWindow() (SurfaceView)
主要做了兩件事:
1 通知父檢視,當前正在處理的SurfaceView需要在宿主視窗的繪圖表面上挖一個洞,即需要在宿主視窗的繪圖表面上設定一塊透明區域。 待更新
2 呼叫從父類View繼承下來的成員函式getWindowSession()來獲得一個實現了IWindowSession介面的Binder代理物件. mSession指向這個物件.主要是想通過binder請求繪製繪圖表面.
3.5 ViewGroup.dispatchWindowVisibilityChanged() (DecorView ViewGroup)
遍歷設定子View的可見性 3.6
3.6 View.dispatchWindowVisibilityChanged() (View)
呼叫成員函式onWindowVisibilityChanged()來讓子類處理可見性變化
3.7 SurfaceView.onWindowVisibilityChanged() (SurfaceView)
設定當前SurfaceView的可見性,呼叫3.8方法,更新檢視,如果還沒有繪製SurfaceView,就請求繪製
3.8 SurfaceView.updateWindow
重要的成員變數解釋:
mSurface:指向特定的繪圖表面,其他的View的繪圖表面是共享的,SurfaceView的是特有的;
mWindow:指向MyWindow物件,每個SurfaceView都關聯了一個實現了IWindow介面的Binder本地物件
mWindowType:描述SurfaceView的視窗型別,預設是顯示多媒體的型別,可通過視窗設定層級,比如media的在下面,media_overlate的在上面;也可以通過修改setZXXX()的值來提升在Z軸的顯示層級
mRequestedType:繪圖表面型別 Layer or LayerBuffer 對應的在SurfaceFlinger的記憶體分配也不一致.
3.8.1 繪圖表面的建立過程:
-
判斷並準備宿主視窗
-
獲得SurfaceView寬高
- 更新記錄SurfaceView的繪製資訊,可見性、位置、大小、繪圖表面畫素格式和型別等等
image
-
檢查成員變數mWindow的值是否等於null,相當於檢測是否新增到WindowManagerService服務
-
呼叫成員變數mSession所描述的一個Binder代理物件的成員函式relayout來請求WindowManagerService服務對SurfaceView的UI進行佈局
3.8.2 SurfaceView的挖洞過程:

image
- SurfaceView -- SurfaceView.onAttachedToWindow
呼叫mParent.requestTransparentRegion(SurfaceView.this);來請求在宿主視窗挖洞; - ViewGroup -- requestTransparentRegion()
設定標誌位mPrivateFlags為頂層繪製透明視窗,呼叫mParent(ViewRoot)的方法; - ViewRootImp -- requestTransparentRegion()
檢查執行緒(為非主執行緒),檢查viewRoot指向的物件和傳入的引數是否是同個物件;設定標誌位,調requestLayout()開始重新整理視窗; - ViewRootImp -- performTraversals()
在視窗的UI佈局完成之後,並且在視窗的UI繪製之前,收集嵌入在它裡面的SurfaceView所設定的透明區域的,這樣子View的大小和位置才能確定; - ViewGroup -- gatherTransparentRegion() 挖洞過程
5.1 呼叫父類View的成員函式gatherTransparentRegion來檢查當前正在處理的檢視容器是否需要繪製。
5.2 遍歷子類的gatherTransparentRegion來繼續往下收集透明區域。 - SurfaceView -- gatherTransparentRegion()
假設當前正在處理的SurfaceView不是用作視窗面板,並且也是不需要在宿主視窗的繪圖表面上進行繪製的,而引數region的值又不等於null,那麼SurfaceView類的成員函式gatherTransparentRegion就會先計算好當前正在處理的SurfaceView所佔據的區域,然後再將該區域新增到引數region所描述的區域中去,這樣就可以得到視窗的一個新的透明區域。
3.8.3 SurfaceView的繪製過程:

image.png
如果要在一個繪圖表面進行UI繪製,那麼就順序執行以下的操作:
(1). 在繪圖表面的基礎上建立一塊畫布,即獲得一個Canvas物件。
(2). 利用Canvas類提供的繪圖介面在前面獲得的畫布上繪製任意的UI。
(3). 將已經填充好了UI資料的畫布緩衝區提交給SurfaceFlinger服務,以便SurfaceFlinger服務可以將它合成到螢幕上去。
SurfaceView提供了一個SurfaceHolder介面,通過這個SurfaceHolder介面就可以執行第(1)和引(3)個操作;
SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view); SurfaceHolder sh = sv.getHolder(); Cavas canvas = sh.lockCanvas() //Draw something on canvas ...... sh.unlockCanvasAndPost(canvas);
- SurfaceView.getHolder
獲得SurfaceHolder物件 - SurfaceHolder.lockCanvas
因為SurfaceView是在子執行緒執行繪製的,畫布的繪製過程不是執行緒安全的,所以在繪製的時候需要對當前的繪圖表面進行鎖保護--mSurfaceLock; - Surface.lockCanvas
通過JNI方法來在當前正在處理的繪圖表面上獲得一個 圖形緩衝區 ,並且將這個圖形繪衝區封裝在一塊型別為Canvas的畫布中返回給呼叫者使用。
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
這樣就可以開始在mCanvas中繪製
- SurfaceHolder.unlockCanvasAndPost
SurfaceHolder類的成員函式unlockCanvasAndPost再呼叫當前正在處理的SurfaceView的成員變數mSurfaceLock所指向的一個ReentrantLock物件的成員函式unlock來解鎖當前正在處理的SurfaceView的繪圖表面 - Surface.unlockCanvasAndPost
mHwuiContext.unlockAndPost(canvas);
將在前面的Step 3中所獲得的一個圖形緩衝區提交給SurfaceFlinger服務,以便SurfaceFlinger服務可以在合適的時候將該圖形緩衝區合成到螢幕上去顯示,這樣就可以將對應的SurfaceView的UI展現出來了;繪製後釋放鎖
總結:
SurfaceView有以下三個特點:
A. 具有獨立的繪圖表面;
B. 需要在宿主視窗上挖一個洞來顯示自己;
C. 它的UI繪製可以在獨立的執行緒中進行,這樣就可以進行復雜的UI繪製,並且不會影響應用程式的主執行緒響應使用者輸入。