原文出處——>[Android視圖SurfaceView的實現原理分析](http://blog.csdn.net/luoshengyang/article/details/8661317)
在Android系統中,有一種特殊的視圖,稱為SurfaceView,它擁有獨立的繪圖表面,即它不與其宿主窗口共享同一個繪圖表面。由于擁有獨立的繪圖表面,因此SurfaceView的UI就可以在一個獨立的線程中進行繪制。又由于不會占用主線程資源,SurfaceView一方面可以實現復雜而高效的UI,另一方面又不會導致用戶輸入得不到及時響應。在本文中,我們就詳細分析SurfaceView的實現原理。
在前面Android控件TextView的實現原理分析一文中提到,普通的Android控件,例如TextView、Button和CheckBox等,它們都是將自己的UI繪制在宿主窗口的繪圖表面之上,這意味著它們的UI是在應用程序的主線程中進行繪制的。由于應用程序的主線程除了要繪制UI之外,還需要及時地響應用戶輸入,否則的話,系統就會認為應用程序沒有響應了,因此就會彈出一個ANR對話框出來。對于一些游戲畫面,或者攝像頭預覽、視頻播放來說,它們的UI都比較復雜,而且要求能夠進行高效的繪制,因此,它們的UI就不適合在應用程序的主線程中進行繪制。這時候就必須要給那些需要復雜而高效UI的視圖生成一個獨立的繪圖表面,以及使用一個獨立的線程來繪制這些視圖的UI。
在前面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的詳細實現可以參考Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析和Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析這兩篇文章,這里我們只關注與SurfaceView的繪圖表面的創建相關的邏輯。
我們首先分析在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的具體實現可以參考前面Android應用程序窗口(Activity)實現框架簡要介紹和學習計劃這個系列的文章以及Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文,這里我們只關注窗口收集透明區域的邏輯。
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繪制。
從前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文可以知道,如果要在一個繪圖表面進行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的繪圖表面所使用的圖形緩沖區是完全由攝像頭服務或者視頻播放服務來提供的,因此,我們就不可以隨意地去訪問該圖形緩沖區,而是要由攝像頭服務或者視頻播放服務來訪問,因為該圖形緩沖區有可能是在專門的硬件里面分配的。
另外還有一個地方需要注意的是,上述代碼既可以在應用程序的主線程中執行,也可以是在一個獨立的線程中執行。如果上述代碼是在應用程序的主線程中執行,那么就需要保證它們不會占用過多的時間,否則的話,就會導致應用程序的主線程不能及時地響應用戶輸入,從而導致ANR問題。
為了方便起見,我們假設一個SurfaceView的繪圖表面的類型不是SURFACE_TYPE_PUSH_BUFFERS,接下來,我們就從SurfaceView的成員函數getHolder開始,分析這個SurfaceView的繪制過程,如下所示:
:-: 
圖4 SurfaceView的繪制過程
這個過程可以分為5個步驟,接下來我們就詳細分析每一個步驟。
**Step 1. SurfaceView.getHolder**
~~~
public class SurfaceView extends View {
......
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
......
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
......
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。
SurfaceView類的成員函數getHolder的實現很簡單,它只是將成員變量mSurfaceHolder所指向的一個SurfaceHolder對象返回給調用者。
**Step 2. SurfaceHolder.lockCanvas**
~~~
public class SurfaceView extends View {
......
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface();
......
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
......
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
......
private final Canvas internalLockCanvas(Rect dirty) {
if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
throw new BadSurfaceTypeException(
"Surface type is SURFACE_TYPE_PUSH_BUFFERS");
}
mSurfaceLock.lock();
......
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
Rect frame = dirty != null ? dirty : mSurfaceFrame;
try {
c = mSurface.lockCanvas(frame);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
......
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
......
mSurfaceLock.unlock();
return null;
}
......
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。
SurfaceHolder類的成員函數lockCanvas通過調用另外一個成員函數internalLockCanvas來在當前正在處理的SurfaceView的繪圖表面上建立一塊畫布返回給調用者。
SurfaceHolder類的成員函數internalLockCanvas首先是判斷當前正在處理的SurfaceView的繪圖表面的類型是否是SURFACE_TYPE_PUSH_BUFFERS,如果是的話,那么就會拋出一個類型為BadSurfaceTypeException的異常,原因如前面所述。
由于接下來SurfaceHolder類的成員函數internalLockCanvas要在當前正在處理的SurfaceView的繪圖表面上建立一塊畫布,并且返回給調用者訪問,而這塊畫布不是線程安全的,也就是說它不能同時被多個線程訪問,因此,就需要對當前正在處理的SurfaceView的繪圖表面進行鎖保護,這是通過它的鎖定它的成員變量mSurfaceLock所指向的一個ReentrantLock對象來實現的。
注意,如果當前正在處理的SurfaceView的成員變量mWindow的值等于null,那么就說明它的繪圖表面還沒有創建好,這時候就無法創建一塊畫布返回給調用者。同時,如果當前正在處理的SurfaceView的繪圖表面已經創建好,但是該SurfaceView當前是處于停止繪制的狀態,即它的成員變量mDrawingStopped的值等于true,那么也是無法創建一塊畫布返回給調用者的。
假設當前正在處理的SurfaceView的繪制表面已經創建好,并且它不是處于停止繪制的狀態,那么SurfaceHolder類的成員函數internalLockCanvas就會通過調用該SurfaceView的成員變量mSurface所指向的一個Surface對象的成員函數lockCanvas來創建一塊畫布,并且返回給調用者。注意,在這種情況下,當前正在處理的SurfaceView的繪制表面還是處于鎖定狀態的。
另一方面,如果SurfaceHolder類的成員函數internalLockCanvas不能成功地在當前正在處理的SurfaceView的繪制表面上創建一塊畫布,即變量c的值等于null,那么SurfaceHolder類的成員函數internalLockCanvas在返回一個null值調用者之前,還會將該SurfaceView的繪制表面就會解鎖。
從前面第1部分的內容可以知道,SurfaceView類的成員變量mSurface描述的是就是SurfaceView的專有繪圖表面,接下來我們就繼續分析它所指向的一個Surface對象的成員函數lockCanvas的實現,以便可以了解SurfaceView的畫布的創建過程。
**Step 3. Surface.lockCanvas**
Surface類的成員函數lockCanvas的具體實現可以參考前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文,它大致就是通過JNI方法來在當前正在處理的繪圖表面上獲得一個圖形緩沖區,并且將這個圖形繪沖區封裝在一塊類型為Canvas的畫布中返回給調用者使用。
調用者獲得了一塊類型為Canvas的畫布之后,就可以調用Canvas類所提供的繪圖函數來繪制任意的UI了,例如,調用Canvas類的成員函數drawLine、drawRect和drawCircle可以分別用來畫直線、矩形和圓。
調用者在畫布上繪制完成所需要的UI之后,就可以將這塊畫布的圖形繪沖區的UI數據提交給SurfaceFlinger服務來處理了,這是通過調用SurfaceHolder類的成員函數unlockCanvasAndPost來實現的。
**Step 4. SurfaceHolder.unlockCanvasAndPost**
~~~
public class SurfaceView extends View {
......
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface();
......
private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
......
public void unlockCanvasAndPost(Canvas canvas) {
mSurface.unlockCanvasAndPost(canvas);
mSurfaceLock.unlock();
}
......
}
......
}
~~~
這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。
SurfaceHolder類的成員函數unlockCanvasAndPost是通過調用當前正在處理的SurfaceView的成員變量mSurface所指向的一個Surface對象的成員函數unlockCanvasAndPost來將參數canvas所描述的一塊畫布的圖形緩沖區提交給SurfaceFlinger服務處理的。
提交完成參數canvas所描述的一塊畫布的圖形緩沖區給SurfaceFlinger服務之后,SurfaceHolder類的成員函數unlockCanvasAndPost再調用當前正在處理的SurfaceView的成員變量mSurfaceLock所指向的一個ReentrantLock對象的成員函數unlock來解鎖當前正在處理的SurfaceView的繪圖表面,因為在前面的Step 2中,我們曾經將該繪圖表面鎖住了。
接下來,我們就繼續分析Surface類的成員函數unlockCanvasAndPost的實現,以便可以了解SurfaceView的繪制過程。
**Step 5. Surface.unlockCanvasAndPost**
Surface類的成員函數unlockCanvasAndPost的具體實現同樣是可以參考前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文,它大致就是將在前面的Step 3中所獲得的一個圖形緩沖區提交給SurfaceFlinger服務,以便SurfaceFlinger服務可以在合適的時候將該圖形緩沖區合成到屏幕上去顯示,這樣就可以將對應的SurfaceView的UI展現出來了。
至此,我們就分析完成SurfaceView的繪制過程了,整個SurfaceView的實現原理也就分析完了。總結來說,就是SurfaceView有以下三個特點:
* A. 具有獨立的繪圖表面;
* B. 需要在宿主窗口上挖一個洞來顯示自己;
* C. 它的UI繪制可以在獨立的線程中進行,這樣就可以進行復雜的UI繪制,并且不會影響應用程序的主線程響應用戶輸入。
- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析