Android檢視SurfaceView的實現原理分析
在Android系統中,有一種特殊的檢視,稱為SurfaceView,它擁有獨立的繪圖表面,即它不與其宿主視窗共享同一個繪圖表面。由於擁有獨立的繪圖表面,因此SurfaceView的UI就可以在一個獨立的執行緒中進行繪製。又由於不會佔用主執行緒資源,SurfaceView一方面可以實現複雜而高效的UI,另一方面又不會導致使用者輸入得不到及時響應。在本文中,我們就詳細分析SurfaceView的實現原理。
《Android系統原始碼情景分析》一書正在進擊的程式設計師網(http://0xcc0xcd.com)中連載,點選進入!
在前面Android應用程式與SurfaceFlinger服務的關係概述和學習計劃和Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃這兩個系統的文章中,我們主要分析了Android應用程式視窗是如何通過SurfaceFlinger服務來繪製自己的UI的。一般來說,每一個視窗在SurfaceFlinger服務中都對應有一個Layer,用來描述它的繪圖表面。對於那些具有SurfaceView的視窗來說,每一個SurfaceView在SurfaceFlinger服務中還對應有一個獨立的Layer或者LayerBuffer,用來單獨描述它的繪圖表面,以區別於它的宿主視窗的繪圖表面。
無論是LayerBuffer,還是Layer,它們都是以LayerBase為基類的,也就是說,SurfaceFlinger服務把所有的LayerBuffer和Layer都抽象為LayerBase,因此就可以用統一的流程來繪製和合成它們的UI。由於LayerBuffer的繪製和合成與Layer的繪製和合成是類似的,因此本文不打算對LayerBuffer的繪製和合成操作進行分析。需要深入理解LayerBuffer的繪製和合成操作的,可以參考Android應用程式與SurfaceFlinger服務的關係概述和學習計劃和Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃這兩個系統的文章。
為了接下來可以方便地描述SurfaceView的實現原理分析,我們假設在一個Activity視窗的檢視結構中,除了有一個DecorView頂層檢視之外,還有兩個TextView控制元件,以及一個SurfaceView檢視,這樣該Activity視窗在SurfaceFlinger服務中就對應有兩個Layer或者一個Layer的一個LayerBuffer,如圖1所示:
圖1 SurfaceView及其宿主Activity視窗的繪圖表面示意圖
在圖1中,Activity視窗的頂層檢視DecorView及其兩個TextView控制元件的UI都是繪製在SurfaceFlinger服務中的同一個Layer上面的,而SurfaceView的UI是繪製在SurfaceFlinger服務中的另外一個Layer或者LayerBuffer上的。
注意,用來描述SurfaceView的Layer或者LayerBuffer的Z軸位置是小於用來其宿主Activity視窗的Layer的Z軸位置的,但是前者會在後者的上面挖一個“洞”出來,以便它的UI可以對使用者可見。實際上,SurfaceView在其宿主Activity視窗上所挖的“洞”只不過是在其宿主Activity視窗上設定了一塊透明區域。
從總體上描述了SurfaceView的大致實現原理之後,接下來我們就詳細分析它的具體實現過程,包括它的繪圖表面的建立過程、在宿主視窗上面進行挖洞的過程,以及繪製過程。
1. SurfaceView的繪圖表面的建立過程
由於SurfaceView具有獨立的繪圖表面,因此,在它的UI內容可以繪製之前,我們首先要將它的繪圖表面創建出來。儘管SurfaceView不與它的宿主視窗共享同一個繪圖表面,但是它仍然是屬於宿主視窗的檢視結構的一個結點的,也就是說,SurfaceView仍然是會參與到宿主視窗的某些執行流程中去。
從前面Android應用程式視窗(Activity)的繪圖表面(Surface)的建立過程分析一文可以知道,每當一個視窗需要重新整理UI時,就會呼叫ViewRoot類的成員函式performTraversals。ViewRoot類的成員函式performTraversals在執行的過程中,如果發現當前視窗的繪圖表面還沒有建立,或者發現當前視窗的繪圖表面已經失效了,那麼就會請求WindowManagerService服務建立一個新的繪圖表面,同時,它還會通過一系列的回撥函式來讓嵌入在窗口裡面的SurfaceView有機會建立自己的繪圖表面。
接下來,我們就從ViewRoot類的成員函式performTraversals開始,分析SurfaceView的繪圖表面的建立過程,如圖2所示:
圖2 SurfaceView的繪圖表面的建立過程
這個過程可以分為8個步驟,接下來我們就詳細分析每一個步驟。
Step 1. ViewRoot.performTraversals
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
......
final View.AttachInfo attachInfo = mAttachInfo;
final int viewVisibility = getHostVisibility();
boolean viewVisibilityChanged = mViewVisibility != viewVisibility
|| mNewSurfaceNeeded;
......
if (mFirst) {
......
if (!mAttached) {
host.dispatchAttachedToWindow(attachInfo, 0);
mAttached = true;
}
......
}
......
if (viewVisibilityChanged) {
......
host.dispatchWindowVisibilityChanged(viewVisibility);
......
}
......
mFirst = false;
......
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewRoot.java中。
我們首先分析在ViewRoot類的成員函式performTraversals中四個相關的變數host、attachInfo、viewVisibility和viewVisibilityChanged。
變數host與ViewRoot類的成員變數mView指向的是同一個DecorView物件,這個DecorView物件描述的當前視窗的頂層檢視。
變數attachInfo與ViewRoot類的成員變數mAttachInfo指向的是同一個AttachInfo物件。在Android系統中,每一個檢視附加到它的宿主視窗的時候,都會獲得一個AttachInfo物件,用來描述被附加的視窗的資訊。
變數viewVisibility描述的是當前視窗的可見性。
變數viewVisibilityChanged描述的是當前視窗的可見性是否發生了變化。
ViewRoot類的成員變數mFirst表示當前視窗是否是第一次被重新整理UI。如果是的話,那麼它的值就會等於true,說明當前視窗的繪圖表面還未建立。在這種情況下,如果ViewRoot類的另外一個成員變數mAttached的值也等於true,那麼就表示當前視窗還沒有將它的各個子檢視附加到它的上面來。這時候ViewRoot類的成員函式performTraversals就會從當前視窗的頂層檢視開始,通知每一個子檢視它要被附加到宿主視窗上去了,這是通過呼叫變數host所指向的一個DecorView物件的成員函式dispatchAttachedToWindow來實現的。DecorView類的成員函式dispatchAttachedToWindow是從父類ViewGroup繼承下來的,在後面的Step 2中,我們再詳細分析ViewGroup類的成員數dispatchAttachedToWindow的實現。
接下來, ViewRoot類的成員函式performTraversals判斷當前視窗的可見性是否發生了變化,即檢查變數viewVisibilityChanged的值是否等於true。如果發生了變化,那麼就會從當前視窗的頂層檢視開始,通知每一個子檢視它的宿主視窗的可見發生變化了,這是通過呼叫變數host所指向的一個DecorView物件的成員函式dispatchWindowVisibilityChanged來實現的。DecorView類的成員函式dispatchWindowVisibilityChanged是從父類ViewGroup繼承下來的,在後面的Step 5中,我們再詳細分析ViewGroup類的成員數dispatchWindowVisibilityChanged的實現。
我們假設當前視窗有一個SurfaceView,那麼當該SurfaceView接收到它被附加到宿主視窗以及它的宿主視窗的可見性發生變化的通知時,就會相應地將自己的繪圖表面創建出來。接下來,我們就分別分析ViewGroup類的成員數dispatchAttachedToWindow和dispatchWindowVisibilityChanged的實現,以便可以瞭解SurfaceView的繪圖表面的建立過程。
Step 2. ViewGroup.dispatchAttachedToWindow
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......
// Child views of this ViewGroup
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
private int mChildrenCount;
......
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
super.dispatchAttachedToWindow(info, visibility);
visibility |= mViewFlags & VISIBILITY_MASK;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchAttachedToWindow(info, visibility);
}
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewGroup.java中。ViewGroup類的成員變數mChildren儲存的是當前正在處理的檢視容器的子檢視,而另外一個成員變數mChildrenCount儲存的是這些子檢視的數量。
ViewGroup類的成員函式dispatchAttachedToWindow的實現很簡單,它只是簡單地呼叫當前正在處理的檢視容器的每一個子檢視的成員函式dispatchAttachedToWindow,以便可以通知這些子檢視,它們被附加到宿主視窗上去了。
當前正在處理的檢視容器即為當前正在處理的視窗的頂層檢視,由於前面我們當前正在處理的視窗有一個SurfaceView,因此這一步就會呼叫到該SurfaceView的成員函式dispatchAttachedToWindow。
由於SurfaceView類的成員函式dispatchAttachedToWindow是從父類View繼承下來的,因此,接下來我們就繼續分析View類的成員函式dispatchAttachedToWindow的實現。
Step 3. View.dispatchAttachedToWindow
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
AttachInfo mAttachInfo;
......
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
//System.out.println("Attached! " + this);
mAttachInfo = info;
......
onAttachedToWindow();
......
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/View.java中。View類的成員函式dispatchAttachedToWindow首先將引數info所指向的一個AttachInfo物件儲存在自己的成員變數mAttachInfo中,以便當前檢視可以獲得其所附加在的視窗的相關資訊,接下來再呼叫另外一個成員函式onAttachedToWindow來讓子類有機會處理它被附加到宿主視窗的事件。
前面我們已經假設了當前處理的是一個SurfaceView。SurfaceView類重寫了父類View的成員函式onAttachedToWindow,接下來我們就繼續分析SurfaceView的成員函式onAttachedToWindow的實現,以便可以瞭解SurfaceView的繪圖表面的建立過程。
Step 4. SurfaceView.onAttachedToWindow
public class SurfaceView extends View {
......
IWindowSession mSession;
......
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent.requestTransparentRegion(this);
mSession = getWindowSession();
......
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/SurfaceView.java中。SurfaceView類的成員函式onAttachedToWindow做了兩件重要的事。
第一件事情是通知父檢視,當前正在處理的SurfaceView需要在宿主視窗的繪圖表面上挖一個洞,即需要在宿主視窗的繪圖表面上設定一塊透明區域。當前正在處理的SurfaceView的父檢視儲存在父類View的成員變數mParent中,通過呼叫這個成員變數mParent所指向的一個ViewGroup物件的成員函式requestTransparentRegion,就可以通知到當前正在處理的SurfaceView的父檢視,當前正在處理的SurfaceView需要在宿主視窗的繪圖表面上設定一塊透明區域。在後面第2部分的內容中,我們再詳細分析SurfaceView在宿主視窗的繪圖表面的挖洞過程。
第二件事情是呼叫從父類View繼承下來的成員函式getWindowSession來獲得一個實現了IWindowSession介面的Binder代理物件,並且將該Binder代理物件儲存在SurfaceView類的成員變數mSession中。從前面Android應用程式視窗(Activity)實現框架簡要介紹和學習計劃這個系列的文章可以知道,在Android系統中,每一個應用程式程序都有一個實現了IWindowSession介面的Binder代理物件,這個Binder代理物件是用來與WindowManagerService服務進行通訊的,View類的成員函式getWindowSession返回的就是該Binder代理物件。在接下來的Step 8中,我們就可以看到,SurfaceView就可以通過這個實現了IWindowSession介面的Binder代理物件來請求WindowManagerService服務為自己建立繪圖表面的。
這一步執行完成之後,返回到前面的Step 1中,即ViewRoot類的成員函式performTraversals中,我們假設當前視窗的可見性發生了變化,那麼接下來就會呼叫頂層檢視的成員函式dispatchWindowVisibilityChanged,以便可以通知各個子檢視,它的宿主視窗的可見性發生化了。
視窗的頂層檢視是使用DecorView類來描述的,而DecroView類的成員函式dispatchWindowVisibilityChanged是從父類ViewGroup類繼承下來的,因此,接下來我們就繼續分析GroupView類的成員函式dispatchWindowVisibilityChanged的實現,以便可以瞭解包含在當前窗口裡面的一個SurfaceView的繪圖表面的建立過程。
Step 5. ViewGroup.dispatchWindowVisibilityChanged
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......
@Override
public void dispatchWindowVisibilityChanged(int visibility) {
super.dispatchWindowVisibilityChanged(visibility);
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
children[i].dispatchWindowVisibilityChanged(visibility);
}
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewGroup.java中。
ViewGroup類的成員函式dispatchWindowVisibilityChanged的實現很簡單,它只是簡單地呼叫當前正在處理的檢視容器的每一個子檢視的成員函式dispatchWindowVisibilityChanged,以便可以通知這些子檢視,它們所附加在的宿主視窗的可見性發生變化了。
當前正在處理的檢視容器即為當前正在處理的視窗的頂層檢視,由於前面我們當前正在處理的視窗有一個SurfaceView,因此這一步就會呼叫到該SurfaceView的成員函式dispatchWindowVisibilityChanged。
由於SurfaceView類的成員函式dispatchWindowVisibilityChanged是從父類View繼承下來的,因此,接下來我們就繼續分析View類的成員函式dispatchWindowVisibilityChanged的實現。
Step 6. View.dispatchWindowVisibilityChanged
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
public void dispatchWindowVisibilityChanged(int visibility) {
onWindowVisibilityChanged(visibility);
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/View.java中。View類的成員函式dispatchWindowVisibilityChanged的實現很簡單,它只是呼叫另外一個成員函式onWindowVisibilityChanged來讓子類有機會處理它所附加在的宿主視窗的可見性變化事件。
前面我們已經假設了當前處理的是一個SurfaceView。SurfaceView類重寫了父類View的成員函式onWindowVisibilityChanged,接下來我們就繼續分析SurfaceView的成員函式onWindowVisibilityChanged的實現,以便可以瞭解SurfaceView的繪圖表面的建立過程。
Step 7. SurfaceView.onWindowVisibilityChanged
public class SurfaceView extends View {
......
boolean mRequestedVisible = false;
boolean mWindowVisibility = false;
boolean mViewVisibility = false;
.....
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
updateWindow(false, false);
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/SurfaceView.java中。SurfaceView類有三個用來描述可見性的成員變數mRequestedVisible、mWindowVisibility和mViewVisibility。其中,mWindowVisibility表示SurfaceView的宿主視窗的可見性,mViewVisibility表示SurfaceView自身的可見性。只有當mWindowVisibility和mViewVisibility的值均等於true的時候,mRequestedVisible的值才為true,表示SurfaceView是可見的。
引數visibility描述的便是當前正在處理的SurfaceView的宿主視窗的可見性,因此,SurfaceView類的成員函式onWindowVisibilityChanged首先將它記錄在成員變數mWindowVisibility,接著再綜合另外一個成員變數mViewVisibility來判斷當前正在處理的SurfaceView是否是可見的,並且記錄在成員變數mRequestedVisible中。
最後,SurfaceView類的成員函式onWindowVisibilityChanged就會呼叫另外一個成員函式updateWindow來更新當前正在處理的SurfaceView。在更新的過程中,如果發現當前正在處理的SurfaceView還沒有建立繪圖表面,那麼就地請求WindowManagerService服務為它建立一個。
接下來,我們就繼續分析SurfaceView類的成員函式updateWindow的實現,以便可以瞭解SurfaceView的繪圖表面的建立過程。
Step 8. SurfaceView.updateWindow
public class SurfaceView extends View {
......
final Surface mSurface = new Surface();
......
MyWindow mWindow;
.....
int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
......
int mRequestedType = -1;
......
private void updateWindow(boolean force, boolean redrawNeeded) {
if (!mHaveFrame) {
return;
}
......
int myWidth = mRequestedWidth;
if (myWidth <= 0) myWidth = getWidth();
int myHeight = mRequestedHeight;
if (myHeight <= 0) myHeight = getHeight();
getLocationInWindow(mLocation);
final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible
|| mNewSurfaceNeeded;
final boolean typeChanged = mType != mRequestedType;
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
......
try {
final boolean visible = mVisible = mRequestedVisible;
mLeft = mLocation[0];
mTop = mLocation[1];
mWidth = myWidth;
mHeight = myHeight;
mFormat = mRequestedFormat;
mType = mRequestedType;
......
// Places the window relative
mLayout.x = mLeft;
mLayout.y = mTop;
mLayout.width = getWidth();
mLayout.height = getHeight();
......
mLayout.memoryType = mRequestedType;
if (mWindow == null) {
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
......
mSession.addWithoutInputChannel(mWindow, mLayout,
mVisible ? VISIBLE : GONE, mContentInsets);
}
......
mSurfaceLock.lock();
try {
......
final int relayoutResult = mSession.relayout(
mWindow, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
mVisibleInsets, mConfiguration, mSurface);
......
} finally {
mSurfaceLock.unlock();
}
......
} catch (RemoteException ex) {
}
.....
}
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/SurfaceView.java中。
在分析SurfaceView類的成員函式updateWindow的實現之前,我們首先介紹一些相關的成員變數的含義,其中,mSurface、mWindow、mWindowType和mRequestedType這四個成員變數是最重要的。
SurfaceView類的成員變數mSurface指向的是一個Surface物件,這個Surface物件描述的便是SurfaceView專有的繪圖表面。對於一般的檢視來說,例如,TextView或者Button,它們是沒有專有的繪圖表面的,而是與專宿主視窗共享同一個繪圖表面,因此,它們就不會像SurfaceView一樣,有一個專門的型別為Surface的成員變數來描述自己的繪圖表面。
在前面Android應用程式視窗(Activity)與WindowManagerService服務的連線過程分析一文提到,每一個Activity視窗都關聯有一個W物件。這個W物件是一個實現了IWindow介面的Binder本地物件,它是用來傳遞給WindowManagerService服務的,以便WindowManagerService服務可以通過它來和它所關聯的Activity視窗通訊。例如,WindowManagerService服務通過這個W物件來通知它所關聯的Activity視窗的大小或者可見性發生變化了。同時,這個W物件還用來在WindowManagerService服務這一側唯一地標誌一個視窗,也就是說,WindowManagerService服務會為這個W物件建立一個WindowState物件。
SurfaceView類的成員變數mWindow指向的是一個MyWindow物件。MyWindow類是從BaseIWindow類繼承下來的,後者與W類一樣,實現了IWindow介面。也就是說,每一個SurfaceView都關聯有一個實現了IWindow介面的Binder本地物件,就如第一個Activity視窗都關聯有一個實現了IWindow介面的W物件一樣。從這裡我們就可以推斷出,每一個SurfaceView在WindowManagerService服務這一側都對應有一個WindowState物件。從這一點來看,WindowManagerService服務認為Activity視窗和SurfaceView的地位是一樣的,即認為它們都是一個視窗,並且具有繪圖表面。接下來我們就會通過SurfaceView類的成員函式updateWindow的實現來證實這個推斷。
SurfaceView類的成員變數mWindowType描述的是SurfaceView的視窗型別,它的預設值等於TYPE_APPLICATION_MEDIA。也就是說,我們在建立一個SurfaceView的時候,預設是用來顯示多媒體的,例如,用來顯示視訊。SurfaceView還有另外一個視窗型別TYPE_APPLICATION_MEDIA_OVERLAY,它是用來在視訊上面顯示一個Overlay的,這個Overlay可以用來顯示視字幕等資訊。
我們假設一個Activity視窗嵌入有兩個SurfaceView,其中一個SurfaceView的視窗型別為TYPE_APPLICATION_MEDIA,另外一個SurfaceView的視窗型別為TYPE_APPLICATION_MEDIA_OVERLAY,那麼在WindowManagerService服務這一側就會對應有三個WindowState物件,其中,用來描述SurfaceView的WindowState物件是附加在用來描述Activity視窗的WindowState物件上的。從前面Android視窗管理服務WindowManagerService計算視窗Z軸位置的過程分析一文可以知道,如果一個WindowState物件所描述的視窗的型別為TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,那麼它就會位於它所附加在的視窗的下面。也就是說,型別為TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY的視窗的Z軸位置是小於它所附加在的視窗的Z軸位置的。同時,如果一個視窗同時附加有型別為TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的兩個視窗,那麼型別為TYPE_APPLICATION_MEDIA_OVERLAY的視窗的Z軸大於型別為TYPE_APPLICATION_MEDIA的視窗的Z軸位置。
從上面的描述就可以得出一個結論:如果一個Activity視窗嵌入有兩個型別分別為TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView,那麼該Activity視窗的Z軸位置大於型別為TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z軸位置,而型別為TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z軸位置又大於型別為TYPE_APPLICATION_MEDIA的視窗的Z軸位置。
注意,我們在建立了一個SurfaceView之後,可以呼叫它的成員函式setZOrderMediaOverlay、setZOrderOnTop或者setWindowType來修改該SurfaceView的視窗型別,也就是修改該SurfaceView的成員變數mWindowType的值。
SurfaceView類的成員變數mRequestedType描述的是SurfaceView的繪圖表面的型別,一般來說,它的值可能等於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服務分配的,因而我們不能夠在應用程式內部對它進行操作。例如,當一個SurfaceView是用來顯示攝像頭預覽或者視訊播放的時候,我們就會將它的繪圖表面的型別設定為SURFACE_TYPE_PUSH_BUFFERS,這樣攝像頭服務或者視訊播放服務就會為該SurfaceView繪圖表面建立一塊記憶體,並且將採集的預覽影象資料或者視訊幀資料來源源不斷地填充到該記憶體中去。注意,這塊記憶體有可能是來自專用的硬體的,例如,它可能是來自視訊卡的。在這種情況下,SurfaceFlinger服務使用一個LayerBuffer物件來描述該SurfaceView的繪圖表面。
從上面的描述就得到一個重要的結論:繪圖表面型別為SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由應用程式來控制的,而是由專門的服務來控制的,例如,攝像頭服務或者視訊播放服務,同時,SurfaceFlinger服務會使用一種特殊的LayerBuffer來描述這種繪圖表面。使用LayerBuffer來描述的繪圖表面在進行渲染的時候,可以使用硬體加速,例如,使用copybit或者overlay來加快渲染速度,從而可以獲得更流暢的攝像頭預覽或者視訊播放。
注意,我們在建立了一個SurfaceView之後,可以呼叫它的成員函式getHolder獲得一個SurfaceHolder物件,然後再呼叫該SurfaceHolder物件的成員函式setType來修改該SurfaceView的繪圖表面的型別,即修改該SurfaceView的成員變數mRequestedType的值。
介紹完成SurfaceView類的成員變數mSurface、mWindow、mWindowType和mRequestedType的含義之後,我們再介紹其它幾個接下來要用到的其它成員變數的含義:
--mHaveFrame,用來描述SurfaceView的宿主視窗的大小是否已經計算好了。只有當宿主視窗的大小計算之後,SurfaceView才可以更新自己的視窗。
--mRequestedWidth,用來描述SurfaceView最後一次被請求的寬度。
--mRequestedHeight,用來描述SurfaceView最後一次被請求的高度。
--mRequestedFormat,用來描述SurfaceView最後一次被請求的繪圖表面的畫素格式。
--mNewSurfaceNeeded,用來描述SurfaceView是否需要新建立一個繪圖表面。
--mLeft、mTop、mWidth、mHeight,用來描述SurfaceView上一次所在的位置以及大小。
--mFormat,用來描述SurfaceView的繪圖表面上一次所設定的格式。
--mVisible,用來描述SurfaceView上一次被設定的可見性。
--mType,用來描述SurfaceView的繪圖表面上一次所設定的型別。
--mUpdateWindowNeeded,用來描述SurfaceView是否被WindowManagerService服務通知執行一次UI更新操作。
--mReportDrawNeeded,用來描述SurfaceView是否被WindowManagerService服務通知執行一次UI繪製操作。
--mLayout,指向的是一個WindowManager.LayoutParams物件,用來傳遞SurfaceView的佈局引數以及屬性值給WindowManagerService服務,以便WindowManagerService服務可以正確地維護它的狀態。
理解了上述成員變數的含義的之後,接下來我們就可以分析SurfaceView類的成員函式updateWindow建立繪圖表面的過程了,如下所示:
(1). 判斷成員變數mHaveFrame的值是否等於false。如果是的話,那麼就說明現在還不是時候為SurfaceView建立繪圖表面,因為它的宿主視窗還沒有準備就緒。
(2). 獲得SurfaceView當前要使用的寬度和高度,並且儲存在變數myWidth和myHeight中。注意,如果SurfaceView沒有被請求設定寬度或者高度,那麼就通過呼叫父類View的成員函式getWidth和getHeight來獲得它預設所使用的寬度和高度。
(3). 呼叫父類View的成員函式getLocationInWindow來獲得SurfaceView的左上角位置,並且儲存在成員變數mLocation所描述的一個數組中。
(4). 判斷以下條件之一是否成立:
--SurfaceView的繪圖表面是否還未建立,即成員變數mWindow的值是否等於null;
--SurfaceView的繪圖表面的畫素格式是否發生了變化,即成員變數mFormat和mRequestedFormat的值是否不相等;
--SurfaceView的大小是否發生了變化,即變數myWidth和myHeight是否與成員變數mWidth和mHeight的值不相等;
--SurfaceView的可見性是否發生了變化,即成員變數mVisible和mRequestedVisible的值是否不相等,或者成員變數NewSurfaceNeeded的值是否等於true;
--SurfaceView的繪圖表面的型別是否發生了變化,即成員變數mType和mRequestedType的值是否不相等;
--SurfaceView的位置是否發生了變化,即成員變數mLeft和mTop的值是否不等於前面計算得到的mLocation[0]和mLocation[1]的值;
--SurfaceView是否被WindowManagerService服務通知執行一次UI更新操作,即成員變數mUpdateWindowNeeded的值是否等於true;
--SurfaceView是否被WindowManagerService服務通知執行一次UI繪製操作,即成員變數mReportDrawNeeded的值是否等於true;
--SurfaceView類的成員函式updateWindow是否被呼叫者強制要求重新整理或者繪製SurfaceView,即引數force或者redrawNeeded的值是否等於true。
只要上述條件之一成立,那麼SurfaceView類的成員函式updateWindow就需要對SurfaceView的各種資訊進行更新,即執行以下第5步至第7步操作。
(5). 將SurfaceView接下來要設定的可見性、位置、大小、繪圖表面畫素格式和型別分別記錄在成員變數mVisible、mLeft、mTop、mWidth、mHeight、mFormat和mType,同時還會將這些資訊整合到成員變數mLayout所指向的一個WindowManager.LayoutParams物件中去,以便接下來可以傳遞給WindowManagerService服務。
(6). 檢查成員變數mWindow的值是否等於null。如果等於null的話,那麼就說明該SurfaceView還沒有增加到WindowManagerService服務中去。在這種情況下,就會建立一個MyWindow物件儲存在該成員變數中,並且呼叫成員變數mSession所描述的一個Binder代理物件的成員函式addWithoutInputChannel來將該MyWindow物件傳遞給WindowManagerService服務。在前面的Step 4中提到,SurfaceView類的成員變數mSession指向的是一個實現了IWindowSession介面的Binder代理物件,該Binder代理物件引用的是執行在WindowManagerService服務這一側的一個Session物件。Session類的成員函式addWithoutInputChannel與另外一個成員函式add的實現是類似的,它們都是用來在WindowManagerService服務內部為指定的視窗增加一個WindowState物件,具體可以參考前面Android應用程式視窗(Activity)與WindowManagerService服務的連線過程分析一文。不過,Session類的成員函式addWithoutInputChannel只是在WindowManagerService服務內部為指定的視窗增加一個WindowState物件,而Session類的成員函式add除了會在WindowManagerService服務內部為指定的視窗增加一個WindowState物件之外,還會為該視窗建立一個用來接收使用者輸入的通道,具體可以參考Android應用程式鍵盤(Keyboard)訊息處理機制分析一文。
(7). 呼叫成員變數mSession所描述的一個Binder代理物件的成員函式relayout來請求WindowManagerService服務對SurfaceView的UI進行佈局。從前面Android應用程式視窗(Activity)的繪圖表面(Surface)的建立過程分析一文可以知道,WindowManagerService服務在對一個視窗進行佈局的時候,如果發現該視窗的繪製表面還未建立,或者需要需要重新建立,那麼就會為請求SurfaceFlinger服務為該視窗建立一個新的繪圖表面,並且將該繪圖表面返回來給呼叫者。在我們這個情景中,WindowManagerService服務返回來的繪圖表面就會儲存在成員變數mSurface。注意,這一步由於可能會修改SurfaceView的繪圖表面,即修改成員變數mSurface的指向的一個Surface物件的內容,因此,就需要在獲得成員變數mSurfaceLock所描述的一個鎖的情況下執行,避免其它執行緒同時修改該繪圖表面的內容,這是因為我們可能會使用一個獨立的執行緒來來繪製SurfaceView的UI。
執行完成上述步驟之後,SurfaceView的繪圖表面的建立操作就執行完成了,而當SurfaceView有了繪圖表面之後,我們就可以使用獨立的執行緒來繪製它的UI了,不過,在繪製之前,我們還需要在SurfaceView的宿主視窗上挖一個洞,以便繪製出來的UI不會被擋住。
2. SurfaceView的挖洞過程
SurfaceView的視窗型別一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,也就是說,它的Z軸位置是小於其宿主視窗的Z位置的。為了保證SurfaceView的UI是可見的,SurfaceView就需要在其宿主視窗的上面挖一個洞出來,實際上就是在其宿主視窗的繪圖表面上設定一塊透明區域,以便可以將自己顯示出來。
從SurfaceView的繪圖表面的建立過程可以知道,SurfaceView在被附加到宿主視窗之上的時候,會請求在宿主視窗上設定透明區域,而每當其宿主視窗重新整理自己的UI的時候,就會將所有嵌入在它裡面的SurfaceView所設定的透明區域收集起來,然後再通知WindowManagerService服務為其設定一個總的透明區域。
從SurfaceView的繪圖表面的建立過程可以知道,SurfaceView在被附加到宿主視窗之上的時候,SurfaceView類的成員函式onAttachedToWindow就會被呼叫。SurfaceView類的成員函式onAttachedToWindow在被呼叫的期間,就會請求在宿主視窗上設定透明區域。接下來,我們就從SurfaceView類的成員函式onAttachedToWindow開始,分析SurfaceView的挖洞過程,如圖3所示:
圖3 SurfaceView的挖洞過程
這個過程可以分為6個步驟,接下來我們就詳細分析每一個步驟。
Step 1. SurfaceView.onAttachedToWindow
public class SurfaceView extends View {
......
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent.requestTransparentRegion(this);
......
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/SurfaceView.java中。SurfaceView類的成員變數mParent是從父類View繼承下來的,用來描述當前正在處理的SurfaceView的父檢視。我們假設當前正在處理的SurfaceView的父檢視就為其宿主視窗的頂層檢視,因此,接下來SurfaceView類的成員函式onAttachedToWindow就會呼叫DecorView類的成員函式requestTransparentRegion來請求在宿主視窗之上挖一個洞。
DecorView類的成員函式requestTransparentRegion是從父類ViewGroup繼承下來的,因此,接下來我們就繼續分析ViewGroup類的成員函式requestTransparentRegion的實現。
Step 2. ViewGroup.requestTransparentRegion
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......
public void requestTransparentRegion(View child) {
if (child != null) {
child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
if (mParent != null) {
mParent.requestTransparentRegion(this);
}
}
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewGroup.java中。引數child描述的便是要在宿主視窗設定透明區域的SurfaceView,ViewGroup類的成員函式requestTransparentRegion首先將它的成員變數mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位設定為1,表示它要在宿主視窗上設定透明區域,接著再呼叫從父類View繼承下來的成員變數mParent所指向的一個檢視容器的成員函式requestTransparentRegion來繼續向上請求設定透明區域,這個過程會一直持續到當前正在處理的檢視容器為視窗的頂層檢視為止。
前面我們已經假設了引數child所描述的SurfaceView是直接嵌入在宿主視窗的頂層檢視中的,而視窗的頂層檢視的父檢視是使用一個ViewRoot物件來描述的,也就是說,當前正在處理的檢視容器的成員變數mParent指向的是一個ViewRoot物件,因此,接下來我們就繼續分析ViewRoot類的成員函式requestTransparentRegion的實現,以便可以繼續瞭解SurfaceView的挖洞過程。
Step 3. ViewRoot.requestTransparentRegion
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
public void requestTransparentRegion(View child) {
// the test below should not fail unless someone is messing with us
checkThread();
if (mView == child) {
mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
// Need to make sure we re-evaluate the window attributes next
// time around, to ensure the window has the correct format.
mWindowAttributesChanged = true;
requestLayout();
}
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewRoot.java中。ViewRoot類的成員函式requestTransparentRegion首先呼叫另外一個成員函式checkThread來檢查當前執行的執行緒是否是應用程式的主執行緒,如果不是的話,那麼就會丟擲一個型別為CalledFromWrongThreadException的異常。
通過了上面的檢查之後,ViewRoot類的成員函式requestTransparentRegion再檢查引數child所描述的檢視是否就是當前正在處理的ViewRoot物件所關聯的視窗的頂層檢視,即檢查它與ViewRoot類的成員變數mView是否是指向同一個View物件。由於一個ViewRoot物件有且僅有一個子檢視,因此,如果上述檢查不通過的話,那麼就說明呼叫者正在非法呼叫ViewRoot類的成員函式requestTransparentRegion來設定透明區域。
通過了上述兩個檢查之後,ViewRoot類的成員函式requestTransparentRegion就將成員變數mView所描述的一個視窗的頂層檢視的成員變數mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位設定為1,表示該視窗被設定了一塊透明區域。
當一個視窗被請求設定了一塊透明區域之後,它的視窗屬性就發生變化了,因此,這時候除了要將與它所關聯的一個ViewRoot物件的成員變數mWindowAttributesChanged的值設定為true之外,還要呼叫該ViewRoot物件的成員函式requestLayout來請求重新整理一下視窗的UI,即請求對視窗的UI進行重新佈局和繪製。
從前面Android應用程式視窗(Activity)的繪圖表面(Surface)的建立過程分析一文可以知道,ViewRoot類的成員函式requestLayout最終會呼叫到另外一個成員函式performTraversals來實際執行重新整理視窗UI的操作。ViewRoot類的成員函式performTraversals在重新整理視窗UI的過程中,就會將嵌入在它裡面的SurfaceView所要設定的透明區域收集起來,以便可以請求WindowManagerService將這塊透明區域設定到它的繪圖表面上去。
接下來,我們就繼續分析ViewRoot類的成員函式performTraversals的實現,以便可以繼續瞭解SurfaceView的挖洞過程。
Step 4. ViewRoot.performTraversals
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
......
private void performTraversals() {
......
// cache mView since it is used so much below...
final View host = mView;
......
final boolean didLayout = mLayoutRequested;
......
if (didLayout) {
......
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
......
if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
......
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
// reconfigure window manager
try {
sWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
......
}
......
boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
if (!cancelDraw && !newSurface) {
......
draw(fullRedrawNeeded);
......
}
......
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewRoot.java中。ViewRoot類的成員函式performTraversals是在視窗的UI佈局完成之後,並且在視窗的UI繪製之前,收集嵌入在它裡面的SurfaceView所設定的透明區域的,這是因為視窗的UI佈局完成之後,各個子檢視的大小和位置才能確定下來,這樣SurfaceView才知道自己要設定的透明區域的位置和大小。
變數host與ViewRoot類的成員變數mView指向的是同一個DecorView物件,這個DecorView物件描述的便是當前正在處理的視窗的頂層檢視。從前面的Step 3可以知道,如果當前正在處理的視窗的頂層檢視內嵌有SurfaceView,那麼用來描述它的一個DecorView物件的成員變數mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位就會等於1。在這種情況下,ViewRoot類的成員函式performTraversals就知道需要在當前正在處理的視窗的上面設定一塊透明區域了。這塊透明區域的收集過程如下所示:
(1). 計算頂層檢視的位置和大小,即計算頂層檢視所佔據的區域。
(2). 將頂層檢視所佔據的區域作為視窗的初始化透明區域,儲存在ViewRoot類的成員變數mTransparentRegion中。
(3). 從頂層檢視開始,從上到下收集每一個子檢視所要設定的區域,最終收集到的總透明區域也是儲存在ViewRoot類的成員變數mTransparentRegion中。
(4). 檢查ViewRoot類的成員變數mTransparentRegion和mPreviousTransparentRegion所描述的區域是否相等。如果不相等的話,那麼就說明視窗的透明區域發生了變化,這時候就需要呼叫ViewRoot類的的靜態成員變數sWindowSession所描述的一個Binder代理物件的成員函式setTransparentRegion通知WindowManagerService為視窗設定由成員變數mTransparentRegion所指定的透明區域。
其中,第(3)步是通過呼叫變數host所描述的一個DecorView物件的成員函式gatherTransparentRegion來實現的。 DecorView類的成員函式gatherTransparentRegion是從父類ViewGroup繼承下來的,因此,接下來我們就繼續分析ViewGroup類的成員函式gatherTransparentRegion的實現,以便可以瞭解SurfaceView的挖洞過程。
Step 5. ViewGroup.gatherTransparentRegion
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......
@Override
public boolean gatherTransparentRegion(Region region) {
// If no transparent regions requested, we are always opaque.
final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0;
if (meOpaque && region == null) {
// The caller doesn't care about the region, so stop now.
return true;
}
super.gatherTransparentRegion(region);
final View[] children = mChildren;
final int count = mChildrenCount;
boolean noneOfTheChildrenAreTransparent = true;
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
if (!child.gatherTransparentRegion(region)) {
noneOfTheChildrenAreTransparent = false;
}
}
}
return meOpaque || noneOfTheChildrenAreTransparent;
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/ViewGroup.java中。ViewGroup類的成員函式gatherTransparentRegion首先是檢查當前正在處理的檢視容器是否被請求設定透明區域,即檢查成員變數mPrivateFlags的值的 View.REQUEST_TRANSPARENT_REGIONS位是否等於1。如果不等於1,那麼就說明不用往下繼續收集視窗的透明區域了,因為在這種情況下,當前正在處理的檢視容器及其子檢視都不可能設定有透明區域。另一方面,如果引數region的值等於null,那麼就說明呼叫者不關心當前正在處理的檢視容器的透明區域,而是關心它是透明的,還是不透明的。在上述兩種情況下,ViewGroup類的成員函式gatherTransparentRegion都不用進一步處理了。
假設當前正在處理的檢視容器被請求設定有透明區域,並且引數region的值不等於null,那麼接下來ViewGroup類的成員函式gatherTransparentRegion就執行以下兩個操作:
(1). 呼叫父類View的成員函式gatherTransparentRegion來檢查當前正在處理的檢視容器是否需要繪製。如果需要繪製的話,那麼就會將它所佔據的區域從引數region所佔據的區域移除,這是因為引數region所描述的區域開始的時候是等於視窗的頂層檢視的大小的,也就是等於視窗的整個大小的。
(2). 呼叫當前正在處理的檢視容器的每一個子檢視的成員函式gatherTransparentRegion來繼續往下收集透明區域。
在接下來的Step 6中,我們再詳細分析當前正在處理的檢視容器的每一個子檢視的透明區域的收集過程,現在我們主要分析View類的成員函式gatherTransparentRegion的實現,如下所示:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
......
public boolean gatherTransparentRegion(Region region) {
final AttachInfo attachInfo = mAttachInfo;
if (region != null && attachInfo != null) {
final int pflags = mPrivateFlags;
if ((pflags & SKIP_DRAW) == 0) {
// The SKIP_DRAW flag IS NOT set, so this view draws. We need to
// remove it from the transparent region.
final int[] location = attachInfo.mTransparentLocation;
getLocationInWindow(location);
region.op(location[0], location[1], location[0] + mRight - mLeft,
location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
} else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {
// The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
// exists, so we remove the background drawable's non-transparent
// parts from this transparent region.
applyDrawableToTransparentRegion(mBGDrawable, region);
}
}
return true;
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/View.java中。View類的成員函式gatherTransparentRegion首先是檢查當前正在處理的檢視的前景是否需要繪製,即檢查成員變數mPrivateFlags的值的SKIP_DRAW位是否等於0。如果等於0的話,那麼就說明當前正在處理的檢視的前景是需要繪製的。在這種情況下,View類的成員函式gatherTransparentRegion就會將當前正在處理的檢視所佔據的區域從引數region所描述的區域中移除,以便當前正在處理的檢視的前景可以顯示出來。
另一方面,如果當前正在處理的檢視的前景不需要繪製,但是該檢視的背景需要繪製,並且該檢視是設定有的,即成員變數mPrivateFlags的值的SKIP_DRAW位不等於0,並且成員變數mBGDrawable的值不等於null,這時候View類的成員函式gatherTransparentRegion就會呼叫另外一個成員函式applyDrawableToTransparentRegion來將該背景中的不透明區域從引數region所描述的區域中移除,以便當前正在處理的檢視的背景可以顯示出來。
回到ViewGroup類的成員函式gatherTransparentRegion中,當前正在處理的檢視容器即為當前正在處理的視窗的頂層檢視,前面我們已經假設它裡面嵌入有一個SurfaceView子檢視,因此,接下來就會收集該SurfaceView子檢視所設定的透明區域,這是通過呼叫SurfaceView類的成員函式gatherTransparentRegion來實現的。
接下來,我們就繼續分析SurfaceView類的成員函式gatherTransparentRegion的實現,以便可以繼續瞭解SurfaceView的挖洞過程。
Step 6. SurfaceView.gatherTransparentRegion
public class SurfaceView extends View {
......
@Override
public boolean gatherTransparentRegion(Region region) {
if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
return super.gatherTransparentRegion(region);
}
boolean opaque = true;
if ((mPrivateFlags & SKIP_DRAW) == 0) {
// this view draws, remove it from the transparent region
opaque = super.gatherTransparentRegion(region);
} else if (region != null) {
int w = getWidth();
int h = getHeight();
if (w>0 && h>0) {
getLocationInWindow(mLocation);
// otherwise, punch a hole in the whole hierarchy
int l = mLocation[0];
int t = mLocation[1];
region.op(l, t, l+w, t+h, Region.Op.UNION);
}
}
if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
opaque = false;
}
return opaque;
}
......
}
這個函式定義在檔案frameworks/base/core/java/android/view/SurfaceView.java中。SurfaceView類的成員函式gatherTransparentRegion首先是檢查當前正在處理的SurfaceView是否是用作視窗面板的,即它的成員變數mWindowType的值是否等於WindowManager.LayoutParams.TYPE_APPLICATION_PANEL。如果等於的話,那麼就會呼叫父類View的成員函式gatherTransparentRegion來檢查該面板是否需要繪製。如果需要繪製,那麼就會將它所佔據的區域從引數region所描述的區域移除。
假設當前正在處理的SurfaceView不是用作視窗面板的,那麼SurfaceView類的成員函式gatherTransparentRegion接下來就會直接檢查當前正在處理的SurfaceView是否是需要在宿主視窗的繪圖表面上進行繪製,即檢查成員變數mPrivateFlags的值的SKIP_DRAW位是否等於1。如果需要的話,那麼也會呼叫父類View的成員函式gatherTransparentRegion來將它所佔據的區域從引數region所描述的區域移除。
假設當前正在處理的SurfaceView不是用作視窗面板,並且也是不需要在宿主視窗的繪圖表面上進行繪製的,而引數region的值又不等於null,那麼SurfaceView類的成員函式gatherTransparentRegion就會先計算好當前正在處理的SurfaceView所佔據的區域,然後再將該區域新增到引數region所描述的區域中去,這樣就可以得到視窗的一個新的透明區域。
最後,SurfaceView類的成員函式gatherTransparentRegion判斷當前正在處理的SurfaceView的繪圖表面的畫素格式是否設定有透明值。如果有的話,那麼就會將變數opaque的值設定為false,否則的話,變數opaque的值就保持為true。變數opaque的值最終會返回給呼叫者,這樣呼叫者就可以知道當前正在處理的SurfaceView的繪圖表面是否是半透明的了。
至此,我們就分析完成SurfaceView的挖洞過程了,接下來我們繼續分析SurfaceView的繪製過程。
3. SurfaceView的繪製過程
SurfaceView雖然具有獨立的繪圖表面,不過它仍然是宿主視窗的檢視結構中的一個結點,因此,它仍然是可以參與到宿主視窗的繪製流程中去的。從前面Android應用程式視窗(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文可以知道,視窗在繪製的過程中,每一個子檢視的成員函式draw或者dispatchDraw都會被呼叫到,以便它們可以繪製自己的UI。
SurfaceView類的成員函式draw和dispatchDraw的實現如下所示:
public class SurfaceView extends View {
......
@Override
public void draw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
super.draw(canvas);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// if SKIP_DRAW is cleared, draw() has already punched a hole
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
// reposition ourselves where the surface is
mHaveFrame = true;
updateWindow(false, false);
super.dispatchDraw(canvas);
}
......
}
這兩個函式定義在檔案frameworks/base/core/java/android/view/SurfaceView.java中。SurfaceView類的成員函式draw和dispatchDraw的引數canvas所描述的都是建立在宿主視窗的繪圖表面上的畫布,因此,在這塊畫布上繪製的任何UI都是出現在宿主視窗的繪圖表面上的。
本來SurfaceView類的成員函式draw是用來將自己的UI繪製在宿主視窗的繪圖表面上的,但是這裡我們可以看到,如果當前正在處理的SurfaceView不是用作宿主視窗面板的時候,即其成員變數mWindowType的值不等於WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的時候,SurfaceView類的成員函式draw只是簡單地將它所佔據的區域繪製為黑色。
本來SurfaceView類的成員函式dispatchDraw是用來繪製SurfaceView的子檢視的,但是這裡我們同樣看到,如果當前正在處理的SurfaceView不是用作宿主視窗面板的時候,那麼SurfaceView類的成員函式dispatchDraw只是簡單地將它所佔據的區域繪製為黑色,同時,它還會通過呼叫另外一個成員函式updateWindow更新自己的UI,實際上就是請求WindowManagerService服務對自己的UI進行佈局,以及建立繪圖表面,具體可以參考前面第1部分的內容。
從SurfaceView類的成員函式draw和dispatchDraw的實現就可以看出,SurfaceView在其宿主視窗的繪圖表面上面所做的操作就是將自己所佔據的區域繪為黑色,除此之外,就沒有其它更多的操作了,這是因為SurfaceView的UI是要展現在它自己的繪圖表面上面的。接下來我們就分析如何在SurfaceView的繪圖表面上面進行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的繪圖表面的型別不是SURFACE_TYPE_PUSH_BUFFERS的時候,我們才可以自由地在上面繪製UI。我們使用SurfaceView來顯示攝像頭預覽或者播放視訊時,一般就是會將它的繪圖表面的型別設定為SURFACE_TYPE_PUSH_BUFFERS。在這種情況下,SurfaceView的繪圖表面所使用的圖形緩衝區是完全由攝像頭服務或者視訊播放服務來提供的,因此,我們就