Android視圖SurfaceView的實(shí)現(xiàn)原理分析

Android系統(tǒng)中,有一種特殊的視圖,稱為SurfaceView,它擁有獨(dú)立的繪圖表面,即它不與其宿主窗口共享同一個(gè)繪圖表面。由于擁有獨(dú)立的繪圖表面,因此SurfaceView的UI就可以在一個(gè)獨(dú)立的線程中進(jìn)行繪制。又由于不會(huì)占用主線程資源,SurfaceView一方面可以實(shí)現(xiàn)復(fù)雜而高效的UI,另一方面又不會(huì)導(dǎo)致用戶輸入得不到及時(shí)響應(yīng)。在本文中,我們就詳細(xì)分析SurfaceView的實(shí)現(xiàn)原理。

為了接下來可以方便地描述SurfaceView的實(shí)現(xiàn)原理分析,我們假設(shè)在一個(gè)Activity窗口的視圖結(jié)構(gòu)中,除了有一個(gè)DecorView頂層視圖之外,還有兩個(gè)TextView控件,以及一個(gè)SurfaceView視圖,這樣該Activity窗口在SurfaceFlinger服務(wù)中就對(duì)應(yīng)有兩個(gè)Layer或者一個(gè)Layer的一個(gè)LayerBuffer,如圖1所示:

圖1 SurfaceView及其宿主Activity窗口的繪圖表面示意圖

在圖1中,Activity窗口的頂層視圖DecorView及其兩個(gè)TextView控件的UI都是繪制在SurfaceFlinger服務(wù)中的同一個(gè)Layer上面的,而SurfaceView的UI是繪制在SurfaceFlinger服務(wù)中的另外一個(gè)Layer或者LayerBuffer上的。

注意,用來描述SurfaceView的Layer或者LayerBuffer的Z軸位置是小于用來其宿主Activity窗口的Layer的Z軸位置的,但是前者會(huì)在后者的上面挖一個(gè)“洞”出來,以便它的UI可以對(duì)用戶可見。實(shí)際上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不過是在其宿主Activity窗口上設(shè)置了一塊透明區(qū)域。

從總體上描述了SurfaceView的大致實(shí)現(xiàn)原理之后,接下來我們就詳細(xì)分析它的具體實(shí)現(xiàn)過程,包括它的繪圖表面的創(chuàng)建過程、在宿主窗口上面進(jìn)行挖洞的過程,以及繪制過程。

1.?SurfaceView的繪圖表面的創(chuàng)建過程

由于SurfaceView具有獨(dú)立的繪圖表面,因此,在它的UI內(nèi)容可以繪制之前,我們首先要將它的繪圖表面創(chuàng)建出來。盡管SurfaceView不與它的宿主窗口共享同一個(gè)繪圖表面,但是它仍然是屬于宿主窗口的視圖結(jié)構(gòu)的一個(gè)結(jié)點(diǎn)的,也就是說,SurfaceView仍然是會(huì)參與到宿主窗口的某些執(zhí)行流程中去。

每當(dāng)一個(gè)窗口需要刷新UI時(shí),就會(huì)調(diào)用ViewRoot類的成員函數(shù)performTraversals。ViewRoot類的成員函數(shù)performTraversals在執(zhí)行的過程中,如果發(fā)現(xiàn)當(dāng)前窗口的繪圖表面還沒有創(chuàng)建,或者發(fā)現(xiàn)當(dāng)前窗口的繪圖表面已經(jīng)失效了,那么就會(huì)請(qǐng)求WindowManagerService服務(wù)創(chuàng)建一個(gè)新的繪圖表面,同時(shí),它還會(huì)通過一系列的回調(diào)函數(shù)來讓嵌入在窗口里面的SurfaceView有機(jī)會(huì)創(chuàng)建自己的繪圖表面。

接下來,我們就從ViewRoot類的成員函數(shù)performTraversals開始,分析SurfaceView的繪圖表面的創(chuàng)建過程,如圖2所示:

圖2 SurfaceView的繪圖表面的創(chuàng)建過程

這個(gè)過程可以分為8個(gè)步驟,接下來我們就詳細(xì)分析每一個(gè)步驟。

Step 1. ViewRoot.performTraversals

這個(gè)函數(shù)定義在文件frameworks/base/core/Java/android/view/ViewRoot.java中。

我們首先分析在ViewRoot類的成員函數(shù)performTraversals中四個(gè)相關(guān)的變量host、attachInfo、viewVisibility和viewVisibilityChanged。

變量host與ViewRoot類的成員變量mView指向的是同一個(gè)DecorView對(duì)象,這個(gè)DecorView對(duì)象描述的當(dāng)前窗口的頂層視圖。

變量attachInfo與ViewRoot類的成員變量mAttachInfo指向的是同一個(gè)AttachInfo對(duì)象。在Android系統(tǒng)中,每一個(gè)視圖附加到它的宿主窗口的時(shí)候,都會(huì)獲得一個(gè)AttachInfo對(duì)象,用來描述被附加的窗口的信息。

變量viewVisibility描述的是當(dāng)前窗口的可見性。

變量viewVisibilityChanged描述的是當(dāng)前窗口的可見性是否發(fā)生了變化。

ViewRoot類的成員變量mFirst表示當(dāng)前窗口是否是第一次被刷新UI。如果是的話,那么它的值就會(huì)等于true,說明當(dāng)前窗口的繪圖表面還未創(chuàng)建。在這種情況下,如果ViewRoot類的另外一個(gè)成員變量mAttached的值也等于true,那么就表示當(dāng)前窗口還沒有將它的各個(gè)子視圖附加到它的上面來。這時(shí)候ViewRoot類的成員函數(shù)performTraversals就會(huì)從當(dāng)前窗口的頂層視圖開始,通知每一個(gè)子視圖它要被附加到宿主窗口上去了,這是通過調(diào)用變量host所指向的一個(gè)DecorView對(duì)象的成員函數(shù)dispatchAttachedToWindow來實(shí)現(xiàn)的。DecorView類的成員函數(shù)dispatchAttachedToWindow是從父類ViewGroup繼承下來的,在后面的Step 2中,我們?cè)僭敿?xì)分析ViewGroup類的成員數(shù)dispatchAttachedToWindow的實(shí)現(xiàn)。

接下來,?ViewRoot類的成員函數(shù)performTraversals判斷當(dāng)前窗口的可見性是否發(fā)生了變化,即檢查變量viewVisibilityChanged的值是否等于true。如果發(fā)生了變化,那么就會(huì)從當(dāng)前窗口的頂層視圖開始,通知每一個(gè)子視圖它的宿主窗口的可見發(fā)生變化了,這是通過調(diào)用變量host所指向的一個(gè)DecorView對(duì)象的成員函數(shù)dispatchWindowVisibilityChanged來實(shí)現(xiàn)的。DecorView類的成員函數(shù)dispatchWindowVisibilityChanged是從父類ViewGroup繼承下來的,在后面的Step 5中,我們?cè)僭敿?xì)分析ViewGroup類的成員數(shù)dispatchWindowVisibilityChanged的實(shí)現(xiàn)。

我們假設(shè)當(dāng)前窗口有一個(gè)SurfaceView,那么當(dāng)該SurfaceView接收到它被附加到宿主窗口以及它的宿主窗口的可見性發(fā)生變化的通知時(shí),就會(huì)相應(yīng)地將自己的繪圖表面創(chuàng)建出來。接下來,我們就分別分析ViewGroup類的成員數(shù)dispatchAttachedToWindow和dispatchWindowVisibilityChanged的實(shí)現(xiàn),以便可以了解SurfaceView的繪圖表面的創(chuàng)建過程。

Step 2. ViewGroup.dispatchAttachedToWindow

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup類的成員變量mChildren保存的是當(dāng)前正在處理的視圖容器的子視圖,而另外一個(gè)成員變量mChildrenCount保存的是這些子視圖的數(shù)量。

ViewGroup類的成員函數(shù)dispatchAttachedToWindow的實(shí)現(xiàn)很簡(jiǎn)單,它只是簡(jiǎn)單地調(diào)用當(dāng)前正在處理的視圖容器的每一個(gè)子視圖的成員函數(shù)dispatchAttachedToWindow,以便可以通知這些子視圖,它們被附加到宿主窗口上去了。

當(dāng)前正在處理的視圖容器即為當(dāng)前正在處理的窗口的頂層視圖,由于前面我們當(dāng)前正在處理的窗口有一個(gè)SurfaceView,因此這一步就會(huì)調(diào)用到該SurfaceView的成員函數(shù)dispatchAttachedToWindow。

由于SurfaceView類的成員函數(shù)dispatchAttachedToWindow是從父類View繼承下來的,因此,接下來我們就繼續(xù)分析View類的成員函數(shù)dispatchAttachedToWindow的實(shí)現(xiàn)。

Step 3.?View.dispatchAttachedToWindow

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/View.java中。

View類的成員函數(shù)dispatchAttachedToWindow首先將參數(shù)info所指向的一個(gè)AttachInfo對(duì)象保存在自己的成員變量mAttachInfo中,以便當(dāng)前視圖可以獲得其所附加在的窗口的相關(guān)信息,接下來再調(diào)用另外一個(gè)成員函數(shù)onAttachedToWindow來讓子類有機(jī)會(huì)處理它被附加到宿主窗口的事件。

前面我們已經(jīng)假設(shè)了當(dāng)前處理的是一個(gè)SurfaceView。SurfaceView類重寫了父類View的成員函數(shù)onAttachedToWindow,接下來我們就繼續(xù)分析SurfaceView的成員函數(shù)onAttachedToWindow的實(shí)現(xiàn),以便可以了解SurfaceView的繪圖表面的創(chuàng)建過程。

Step 4.?SurfaceView.onAttachedToWindow

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView類的成員函數(shù)onAttachedToWindow做了兩件重要的事。

第一件事情是通知父視圖,當(dāng)前正在處理的SurfaceView需要在宿主窗口的繪圖表面上挖一個(gè)洞,即需要在宿主窗口的繪圖表面上設(shè)置一塊透明區(qū)域。當(dāng)前正在處理的SurfaceView的父視圖保存在父類View的成員變量mParent中,通過調(diào)用這個(gè)成員變量mParent所指向的一個(gè)ViewGroup對(duì)象的成員函數(shù)requestTransparentRegion,就可以通知到當(dāng)前正在處理的SurfaceView的父視圖,當(dāng)前正在處理的SurfaceView需要在宿主窗口的繪圖表面上設(shè)置一塊透明區(qū)域。在后面第2部分的內(nèi)容中,我們?cè)僭敿?xì)分析SurfaceView在宿主窗口的繪圖表面的挖洞過程。

第二件事情是調(diào)用從父類View繼承下來的成員函數(shù)getWindowSession來獲得一個(gè)實(shí)現(xiàn)了IWindowSession接口的Binder代理對(duì)象,并且將該Binder代理對(duì)象保存在SurfaceView類的成員變量mSession中。在Android系統(tǒng)中,每一個(gè)應(yīng)用程序進(jìn)程都有一個(gè)實(shí)現(xiàn)了IWindowSession接口的Binder代理對(duì)象,這個(gè)Binder代理對(duì)象是用來與WindowManagerService服務(wù)進(jìn)行通信的,View類的成員函數(shù)getWindowSession返回的就是該Binder代理對(duì)象。在接下來的Step 8中,我們就可以看到,SurfaceView就可以通過這個(gè)實(shí)現(xiàn)了IWindowSession接口的Binder代理對(duì)象來請(qǐng)求WindowManagerService服務(wù)為自己創(chuàng)建繪圖表面的。

這一步執(zhí)行完成之后,返回到前面的Step 1中,即ViewRoot類的成員函數(shù)performTraversals中,我們假設(shè)當(dāng)前窗口的可見性發(fā)生了變化,那么接下來就會(huì)調(diào)用頂層視圖的成員函數(shù)dispatchWindowVisibilityChanged,以便可以通知各個(gè)子視圖,它的宿主窗口的可見性發(fā)生化了。

窗口的頂層視圖是使用DecorView類來描述的,而DecroView類的成員函數(shù)dispatchWindowVisibilityChanged是從父類ViewGroup類繼承下來的,因此,接下來我們就繼續(xù)分析GroupView類的成員函數(shù)dispatchWindowVisibilityChanged的實(shí)現(xiàn),以便可以了解包含在當(dāng)前窗口里面的一個(gè)SurfaceView的繪圖表面的創(chuàng)建過程。

Step 5.?ViewGroup.dispatchWindowVisibilityChanged

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup類的成員函數(shù)dispatchWindowVisibilityChanged的實(shí)現(xiàn)很簡(jiǎn)單,它只是簡(jiǎn)單地調(diào)用當(dāng)前正在處理的視圖容器的每一個(gè)子視圖的成員函數(shù)dispatchWindowVisibilityChanged,以便可以通知這些子視圖,它們所附加在的宿主窗口的可見性發(fā)生變化了。

當(dāng)前正在處理的視圖容器即為當(dāng)前正在處理的窗口的頂層視圖,由于前面我們當(dāng)前正在處理的窗口有一個(gè)SurfaceView,因此這一步就會(huì)調(diào)用到該SurfaceView的成員函數(shù)dispatchWindowVisibilityChanged。

由于SurfaceView類的成員函數(shù)dispatchWindowVisibilityChanged是從父類View繼承下來的,因此,接下來我們就繼續(xù)分析View類的成員函數(shù)dispatchWindowVisibilityChanged的實(shí)現(xiàn)。

Step 6.?View.dispatchWindowVisibilityChanged

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/View.java中。

View類的成員函數(shù)dispatchWindowVisibilityChanged的實(shí)現(xiàn)很簡(jiǎn)單,它只是調(diào)用另外一個(gè)成員函數(shù)onWindowVisibilityChanged來讓子類有機(jī)會(huì)處理它所附加在的宿主窗口的可見性變化事件。

前面我們已經(jīng)假設(shè)了當(dāng)前處理的是一個(gè)SurfaceView。SurfaceView類重寫了父類View的成員函數(shù)onWindowVisibilityChanged,接下來我們就繼續(xù)分析SurfaceView的成員函數(shù)onWindowVisibilityChanged的實(shí)現(xiàn),以便可以了解SurfaceView的繪圖表面的創(chuàng)建過程。

Step 7.?SurfaceView.onWindowVisibilityChanged

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView類有三個(gè)用來描述可見性的成員變量mRequestedVisible、mWindowVisibility和mViewVisibility。其中,mWindowVisibility表示SurfaceView的宿主窗口的可見性,mViewVisibility表示SurfaceView自身的可見性。只有當(dāng)mWindowVisibility和mViewVisibility的值均等于true的時(shí)候,mRequestedVisible的值才為true,表示SurfaceView是可見的。

參數(shù)visibility描述的便是當(dāng)前正在處理的SurfaceView的宿主窗口的可見性,因此,SurfaceView類的成員函數(shù)onWindowVisibilityChanged首先將它記錄在成員變量mWindowVisibility,接著再綜合另外一個(gè)成員變量mViewVisibility來判斷當(dāng)前正在處理的SurfaceView是否是可見的,并且記錄在成員變量mRequestedVisible中。

最后,SurfaceView類的成員函數(shù)onWindowVisibilityChanged就會(huì)調(diào)用另外一個(gè)成員函數(shù)updateWindow來更新當(dāng)前正在處理的SurfaceView。在更新的過程中,如果發(fā)現(xiàn)當(dāng)前正在處理的SurfaceView還沒有創(chuàng)建繪圖表面,那么就地請(qǐng)求WindowManagerService服務(wù)為它創(chuàng)建一個(gè)。

接下來,我們就繼續(xù)分析SurfaceView類的成員函數(shù)updateWindow的實(shí)現(xiàn),以便可以了解SurfaceView的繪圖表面的創(chuàng)建過程。

Step 8.?SurfaceView.updateWindow

publicclassSurfaceViewextendsView {

......

finalSurface?mSurface?=newSurface();

......

MyWindow?mWindow;

.....

intmWindowType?=?WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;

......

intmRequestedType?=?-1;

......

privatevoidupdateWindow(booleanforce,booleanredrawNeeded)?{

if(!mHaveFrame)?{

return;

}

......

intmyWidth?=?mRequestedWidth;

if(myWidth?<=0)?myWidth?=?getWidth();

intmyHeight?=?mRequestedHeight;

if(myHeight?<=0)?myHeight?=?getHeight();

getLocationInWindow(mLocation);

finalbooleancreating?=?mWindow?==null;

finalbooleanformatChanged?=?mFormat?!=?mRequestedFormat;

finalbooleansizeChanged?=?mWidth?!=?myWidth?||?mHeight?!=?myHeight;

finalbooleanvisibleChanged?=?mVisible?!=?mRequestedVisible

||?mNewSurfaceNeeded;

finalbooleantypeChanged?=?mType?!=?mRequestedType;

if(force?||?creating?||?formatChanged?||?sizeChanged?||?visibleChanged

||?typeChanged?||?mLeft?!=?mLocation[0]?||?mTop?!=?mLocation[1]

||?mUpdateWindowNeeded?||?mReportDrawNeeded?||?redrawNeeded)?{

......

try{

finalbooleanvisible?=?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?=newMyWindow(this);

mLayout.type?=?mWindowType;

......

mSession.addWithoutInputChannel(mWindow,?mLayout,

mVisible???VISIBLE?:?GONE,?mContentInsets);

}

......

mSurfaceLock.lock();

try{

......

finalintrelayoutResult?=?mSession.relayout(

mWindow,?mLayout,?mWidth,?mHeight,

visible???VISIBLE?:?GONE,false,?mWinFrame,?mContentInsets,

mVisibleInsets,?mConfiguration,?mSurface);

......

}finally{

mSurfaceLock.unlock();

}

......

}catch(RemoteException?ex)?{

}

.....

}

}

......

}

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

在分析SurfaceView類的成員函數(shù)updateWindow的實(shí)現(xiàn)之前,我們首先介紹一些相關(guān)的成員變量的含義,其中,mSurface、mWindow、mWindowType和mRequestedType這四個(gè)成員變量是最重要的。

SurfaceView類的成員變量mSurface指向的是一個(gè)Surface對(duì)象,這個(gè)Surface對(duì)象描述的便是SurfaceView專有的繪圖表面。對(duì)于一般的視圖來說,例如,TextView或者Button,它們是沒有專有的繪圖表面的,而是與專宿主窗口共享同一個(gè)繪圖表面,因此,它們就不會(huì)像SurfaceView一樣,有一個(gè)專門的類型為Surface的成員變量來描述自己的繪圖表面。每一個(gè)Activity窗口都關(guān)聯(lián)有一個(gè)W對(duì)象。這個(gè)W對(duì)象是一個(gè)實(shí)現(xiàn)了IWindow接口的Binder本地對(duì)象,它是用來傳遞給WindowManagerService服務(wù)的,以便WindowManagerService服務(wù)可以通過它來和它所關(guān)聯(lián)的Activity窗口通信。例如,WindowManagerService服務(wù)通過這個(gè)W對(duì)象來通知它所關(guān)聯(lián)的Activity窗口的大小或者可見性發(fā)生變化了。同時(shí),這個(gè)W對(duì)象還用來在WindowManagerService服務(wù)這一側(cè)唯一地標(biāo)志一個(gè)窗口,也就是說,WindowManagerService服務(wù)會(huì)為這個(gè)W對(duì)象創(chuàng)建一個(gè)WindowState對(duì)象。

SurfaceView類的成員變量mWindow指向的是一個(gè)MyWindow對(duì)象。MyWindow類是從BaseIWindow類繼承下來的,后者與W類一樣,實(shí)現(xiàn)了IWindow接口。也就是說,每一個(gè)SurfaceView都關(guān)聯(lián)有一個(gè)實(shí)現(xiàn)了IWindow接口的Binder本地對(duì)象,就如第一個(gè)Activity窗口都關(guān)聯(lián)有一個(gè)實(shí)現(xiàn)了IWindow接口的W對(duì)象一樣。從這里我們就可以推斷出,每一個(gè)SurfaceView在WindowManagerService服務(wù)這一側(cè)都對(duì)應(yīng)有一個(gè)WindowState對(duì)象。從這一點(diǎn)來看,WindowManagerService服務(wù)認(rèn)為Activity窗口和SurfaceView的地位是一樣的,即認(rèn)為它們都是一個(gè)窗口,并且具有繪圖表面。接下來我們就會(huì)通過SurfaceView類的成員函數(shù)updateWindow的實(shí)現(xiàn)來證實(shí)這個(gè)推斷。

SurfaceView類的成員變量mWindowType描述的是SurfaceView的窗口類型,它的默認(rèn)值等于TYPE_APPLICATION_MEDIA。也就是說,我們?cè)趧?chuàng)建一個(gè)SurfaceView的時(shí)候,默認(rèn)是用來顯示多媒體的,例如,用來顯示視頻。SurfaceView還有另外一個(gè)窗口類型TYPE_APPLICATION_MEDIA_OVERLAY,它是用來在視頻上面顯示一個(gè)Overlay的,這個(gè)Overlay可以用來顯示視字幕等信息。

我們假設(shè)一個(gè)Activity窗口嵌入有兩個(gè)SurfaceView,其中一個(gè)SurfaceView的窗口類型為TYPE_APPLICATION_MEDIA,另外一個(gè)SurfaceView的窗口類型為TYPE_APPLICATION_MEDIA_OVERLAY,那么在WindowManagerService服務(wù)這一側(cè)就會(huì)對(duì)應(yīng)有三個(gè)WindowState對(duì)象,其中,用來描述SurfaceView的WindowState對(duì)象是附加在用來描述Activity窗口的WindowState對(duì)象上的。如果一個(gè)WindowState對(duì)象所描述的窗口的類型為TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,那么它就會(huì)位于它所附加在的窗口的下面。也就是說,類型為TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY的窗口的Z軸位置是小于它所附加在的窗口的Z軸位置的。同時(shí),如果一個(gè)窗口同時(shí)附加有類型為TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的兩個(gè)窗口,那么類型為TYPE_APPLICATION_MEDIA_OVERLAY的窗口的Z軸大于類型為TYPE_APPLICATION_MEDIA的窗口的Z軸位置。

從上面的描述就可以得出一個(gè)結(jié)論:如果一個(gè)Activity窗口嵌入有兩個(gè)類型分別為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軸位置。

注意,我們?cè)趧?chuàng)建了一個(gè)SurfaceView之后,可以調(diào)用它的成員函數(shù)setZOrderMediaOverlay、setZOrderOnTop或者setWindowType來修改該SurfaceView的窗口類型,也就是修改該SurfaceView的成員變量mWindowType的值。

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

當(dāng)一個(gè)SurfaceView的繪圖表面的類型等于SURFACE_TYPE_NORMAL的時(shí)候,就表示該SurfaceView的繪圖表面所使用的內(nèi)存是一塊普通的內(nèi)存。一般來說,這塊內(nèi)存是由SurfaceFlinger服務(wù)來分配的,我們可以在應(yīng)用程序內(nèi)部自由地訪問它,即可以在它上面填充任意的UI數(shù)據(jù),然后交給SurfaceFlinger服務(wù)來合成,并且顯示在屏幕上。在這種情況下,SurfaceFlinger服務(wù)使用一個(gè)Layer對(duì)象來描述該SurfaceView的繪圖表面。

當(dāng)一個(gè)SurfaceView的繪圖表面的類型等于SURFACE_TYPE_PUSH_BUFFERS的時(shí)候,就表示該SurfaceView的繪圖表面所使用的內(nèi)存不是由SurfaceFlinger服務(wù)分配的,因而我們不能夠在應(yīng)用程序內(nèi)部對(duì)它進(jìn)行操作。例如,當(dāng)一個(gè)SurfaceView是用來顯示攝像頭預(yù)覽或者視頻播放的時(shí)候,我們就會(huì)將它的繪圖表面的類型設(shè)置為SURFACE_TYPE_PUSH_BUFFERS,這樣攝像頭服務(wù)或者視頻播放服務(wù)就會(huì)為該SurfaceView繪圖表面創(chuàng)建一塊內(nèi)存,并且將采集的預(yù)覽圖像數(shù)據(jù)或者視頻幀數(shù)據(jù)源源不斷地填充到該內(nèi)存中去。注意,這塊內(nèi)存有可能是來自專用的硬件的,例如,它可能是來自視頻卡的。在這種情況下,SurfaceFlinger服務(wù)使用一個(gè)LayerBuffer對(duì)象來描述該SurfaceView的繪圖表面。

從上面的描述就得到一個(gè)重要的結(jié)論:繪圖表面類型為SURFACE_TYPE_PUSH_BUFFERS的SurfaceView的UI是不能由應(yīng)用程序來控制的,而是由專門的服務(wù)來控制的,例如,攝像頭服務(wù)或者視頻播放服務(wù),同時(shí),SurfaceFlinger服務(wù)會(huì)使用一種特殊的LayerBuffer來描述這種繪圖表面。使用LayerBuffer來描述的繪圖表面在進(jìn)行渲染的時(shí)候,可以使用硬件加速,例如,使用copybit或者overlay來加快渲染速度,從而可以獲得更流暢的攝像頭預(yù)覽或者視頻播放。

注意,我們?cè)趧?chuàng)建了一個(gè)SurfaceView之后,可以調(diào)用它的成員函數(shù)getHolder獲得一個(gè)SurfaceHolder對(duì)象,然后再調(diào)用該SurfaceHolder對(duì)象的成員函數(shù)setType來修改該SurfaceView的繪圖表面的類型,即修改該SurfaceView的成員變量mRequestedType的值。

介紹完成SurfaceView類的成員變量mSurface、mWindow、mWindowType和mRequestedType的含義之后,我們?cè)俳榻B其它幾個(gè)接下來要用到的其它成員變量的含義:

--mHaveFrame,用來描述SurfaceView的宿主窗口的大小是否已經(jīng)計(jì)算好了。只有當(dāng)宿主窗口的大小計(jì)算之后,SurfaceView才可以更新自己的窗口。

--mRequestedWidth,用來描述SurfaceView最后一次被請(qǐng)求的寬度。

--mRequestedHeight,用來描述SurfaceView最后一次被請(qǐng)求的高度。

--mRequestedFormat,用來描述SurfaceView最后一次被請(qǐng)求的繪圖表面的像素格式。

--mNewSurfaceNeeded,用來描述SurfaceView是否需要新創(chuàng)建一個(gè)繪圖表面。

--mLeft、mTop、mWidth、mHeight,用來描述SurfaceView上一次所在的位置以及大小。

--mFormat,用來描述SurfaceView的繪圖表面上一次所設(shè)置的格式。

--mVisible,用來描述SurfaceView上一次被設(shè)置的可見性。

--mType,用來描述SurfaceView的繪圖表面上一次所設(shè)置的類型。

--mUpdateWindowNeeded,用來描述SurfaceView是否被WindowManagerService服務(wù)通知執(zhí)行一次UI更新操作。

--mReportDrawNeeded,用來描述SurfaceView是否被WindowManagerService服務(wù)通知執(zhí)行一次UI繪制操作。

--mLayout,指向的是一個(gè)WindowManager.LayoutParams對(duì)象,用來傳遞SurfaceView的布局參數(shù)以及屬性值給WindowManagerService服務(wù),以便WindowManagerService服務(wù)可以正確地維護(hù)它的狀態(tài)。

理解了上述成員變量的含義的之后,接下來我們就可以分析SurfaceView類的成員函數(shù)updateWindow創(chuàng)建繪圖表面的過程了,如下所示:

(1). 判斷成員變量mHaveFrame的值是否等于false。如果是的話,那么就說明現(xiàn)在還不是時(shí)候?yàn)镾urfaceView創(chuàng)建繪圖表面,因?yàn)樗乃拗鞔翱谶€沒有準(zhǔn)備就緒。

(2). 獲得SurfaceView當(dāng)前要使用的寬度和高度,并且保存在變量myWidth和myHeight中。注意,如果SurfaceView沒有被請(qǐng)求設(shè)置寬度或者高度,那么就通過調(diào)用父類View的成員函數(shù)getWidth和getHeight來獲得它默認(rèn)所使用的寬度和高度。

(3). 調(diào)用父類View的成員函數(shù)getLocationInWindow來獲得SurfaceView的左上角位置,并且保存在成員變量mLocation所描述的一個(gè)數(shù)組中。

(4). 判斷以下條件之一是否成立:

--SurfaceView的繪圖表面是否還未創(chuàng)建,即成員變量mWindow的值是否等于null;

--SurfaceView的繪圖表面的像素格式是否發(fā)生了變化,即成員變量mFormat和mRequestedFormat的值是否不相等;

--SurfaceView的大小是否發(fā)生了變化,即變量myWidth和myHeight是否與成員變量mWidth和mHeight的值不相等;

--SurfaceView的可見性是否發(fā)生了變化,即成員變量mVisible和mRequestedVisible的值是否不相等,或者成員變量NewSurfaceNeeded的值是否等于true;

--SurfaceView的繪圖表面的類型是否發(fā)生了變化,即成員變量mType和mRequestedType的值是否不相等;

--SurfaceView的位置是否發(fā)生了變化,即成員變量mLeft和mTop的值是否不等于前面計(jì)算得到的mLocation[0]和mLocation[1]的值;

--SurfaceView是否被WindowManagerService服務(wù)通知執(zhí)行一次UI更新操作,即成員變量mUpdateWindowNeeded的值是否等于true;

--SurfaceView是否被WindowManagerService服務(wù)通知執(zhí)行一次UI繪制操作,即成員變量mReportDrawNeeded的值是否等于true;

--SurfaceView類的成員函數(shù)updateWindow是否被調(diào)用者強(qiáng)制要求刷新或者繪制SurfaceView,即參數(shù)force或者redrawNeeded的值是否等于true。

只要上述條件之一成立,那么SurfaceView類的成員函數(shù)updateWindow就需要對(duì)SurfaceView的各種信息進(jìn)行更新,即執(zhí)行以下第5步至第7步操作。

(5). 將SurfaceView接下來要設(shè)置的可見性、位置、大小、繪圖表面像素格式和類型分別記錄在成員變量mVisible、mLeft、mTop、mWidth、mHeight、mFormat和mType,同時(shí)還會(huì)將這些信息整合到成員變量mLayout所指向的一個(gè)WindowManager.LayoutParams對(duì)象中去,以便接下來可以傳遞給WindowManagerService服務(wù)。

(6). 檢查成員變量mWindow的值是否等于null。如果等于null的話,那么就說明該SurfaceView還沒有增加到WindowManagerService服務(wù)中去。在這種情況下,就會(huì)創(chuàng)建一個(gè)MyWindow對(duì)象保存在該成員變量中,并且調(diào)用成員變量mSession所描述的一個(gè)Binder代理對(duì)象的成員函數(shù)addWithoutInputChannel來將該MyWindow對(duì)象傳遞給WindowManagerService服務(wù)。在前面的Step 4中提到,SurfaceView類的成員變量mSession指向的是一個(gè)實(shí)現(xiàn)了IWindowSession接口的Binder代理對(duì)象,該Binder代理對(duì)象引用的是運(yùn)行在WindowManagerService服務(wù)這一側(cè)的一個(gè)Session對(duì)象。Session類的成員函數(shù)addWithoutInputChannel與另外一個(gè)成員函數(shù)add的實(shí)現(xiàn)是類似的,它們都是用來在WindowManagerService服務(wù)內(nèi)部為指定的窗口增加一個(gè)WindowState對(duì)象,不過,Session類的成員函數(shù)addWithoutInputChannel只是在WindowManagerService服務(wù)內(nèi)部為指定的窗口增加一個(gè)WindowState對(duì)象,而Session類的成員函數(shù)add除了會(huì)在WindowManagerService服務(wù)內(nèi)部為指定的窗口增加一個(gè)WindowState對(duì)象之外,還會(huì)為該窗口創(chuàng)建一個(gè)用來接收用戶輸入的通道。

(7). 調(diào)用成員變量mSession所描述的一個(gè)Binder代理對(duì)象的成員函數(shù)relayout來請(qǐng)求WindowManagerService服務(wù)對(duì)SurfaceView的UI進(jìn)行布局。WindowManagerService服務(wù)在對(duì)一個(gè)窗口進(jìn)行布局的時(shí)候,如果發(fā)現(xiàn)該窗口的繪制表面還未創(chuàng)建,或者需要需要重新創(chuàng)建,那么就會(huì)為請(qǐng)求SurfaceFlinger服務(wù)為該窗口創(chuàng)建一個(gè)新的繪圖表面,并且將該繪圖表面返回來給調(diào)用者。在我們這個(gè)情景中,WindowManagerService服務(wù)返回來的繪圖表面就會(huì)保存在成員變量mSurface。注意,這一步由于可能會(huì)修改SurfaceView的繪圖表面,即修改成員變量mSurface的指向的一個(gè)Surface對(duì)象的內(nèi)容,因此,就需要在獲得成員變量mSurfaceLock所描述的一個(gè)鎖的情況下執(zhí)行,避免其它線程同時(shí)修改該繪圖表面的內(nèi)容,這是因?yàn)槲覀兛赡軙?huì)使用一個(gè)獨(dú)立的線程來來繪制SurfaceView的UI。

執(zhí)行完成上述步驟之后,SurfaceView的繪圖表面的創(chuàng)建操作就執(zhí)行完成了,而當(dāng)SurfaceView有了繪圖表面之后,我們就可以使用獨(dú)立的線程來繪制它的UI了,不過,在繪制之前,我們還需要在SurfaceView的宿主窗口上挖一個(gè)洞,以便繪制出來的UI不會(huì)被擋住。

2. SurfaceView的挖洞過程

SurfaceView的窗口類型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,也就是說,它的Z軸位置是小于其宿主窗口的Z位置的。為了保證SurfaceView的UI是可見的,SurfaceView就需要在其宿主窗口的上面挖一個(gè)洞出來,實(shí)際上就是在其宿主窗口的繪圖表面上設(shè)置一塊透明區(qū)域,以便可以將自己顯示出來。

從SurfaceView的繪圖表面的創(chuàng)建過程可以知道,SurfaceView在被附加到宿主窗口之上的時(shí)候,會(huì)請(qǐng)求在宿主窗口上設(shè)置透明區(qū)域,而每當(dāng)其宿主窗口刷新自己的UI的時(shí)候,就會(huì)將所有嵌入在它里面的SurfaceView所設(shè)置的透明區(qū)域收集起來,然后再通知WindowManagerService服務(wù)為其設(shè)置一個(gè)總的透明區(qū)域。

從SurfaceView的繪圖表面的創(chuàng)建過程可以知道,SurfaceView在被附加到宿主窗口之上的時(shí)候,SurfaceView類的成員函數(shù)onAttachedToWindow就會(huì)被調(diào)用。SurfaceView類的成員函數(shù)onAttachedToWindow在被調(diào)用的期間,就會(huì)請(qǐng)求在宿主窗口上設(shè)置透明區(qū)域。接下來,我們就從SurfaceView類的成員函數(shù)onAttachedToWindow開始,分析SurfaceView的挖洞過程,如圖3所示:

圖3 SurfaceView的挖洞過程

這個(gè)過程可以分為6個(gè)步驟,接下來我們就詳細(xì)分析每一個(gè)步驟。

Step 1.?SurfaceView.onAttachedToWindow

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView類的成員變量mParent是從父類View繼承下來的,用來描述當(dāng)前正在處理的SurfaceView的父視圖。我們假設(shè)當(dāng)前正在處理的SurfaceView的父視圖就為其宿主窗口的頂層視圖,因此,接下來SurfaceView類的成員函數(shù)onAttachedToWindow就會(huì)調(diào)用DecorView類的成員函數(shù)requestTransparentRegion來請(qǐng)求在宿主窗口之上挖一個(gè)洞。

DecorView類的成員函數(shù)requestTransparentRegion是從父類ViewGroup繼承下來的,因此,接下來我們就繼續(xù)分析ViewGroup類的成員函數(shù)requestTransparentRegion的實(shí)現(xiàn)。

Step 2.?ViewGroup.requestTransparentRegion

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。

參數(shù)child描述的便是要在宿主窗口設(shè)置透明區(qū)域的SurfaceView,ViewGroup類的成員函數(shù)requestTransparentRegion首先將它的成員變量mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位設(shè)置為1,表示它要在宿主窗口上設(shè)置透明區(qū)域,接著再調(diào)用從父類View繼承下來的成員變量mParent所指向的一個(gè)視圖容器的成員函數(shù)requestTransparentRegion來繼續(xù)向上請(qǐng)求設(shè)置透明區(qū)域,這個(gè)過程會(huì)一直持續(xù)到當(dāng)前正在處理的視圖容器為窗口的頂層視圖為止。

前面我們已經(jīng)假設(shè)了參數(shù)child所描述的SurfaceView是直接嵌入在宿主窗口的頂層視圖中的,而窗口的頂層視圖的父視圖是使用一個(gè)ViewRoot對(duì)象來描述的,也就是說,當(dāng)前正在處理的視圖容器的成員變量mParent指向的是一個(gè)ViewRoot對(duì)象,因此,接下來我們就繼續(xù)分析ViewRoot類的成員函數(shù)requestTransparentRegion的實(shí)現(xiàn),以便可以繼續(xù)了解SurfaceView的挖洞過程。

Step 3. ViewRoot.requestTransparentRegion

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot類的成員函數(shù)requestTransparentRegion首先調(diào)用另外一個(gè)成員函數(shù)checkThread來檢查當(dāng)前執(zhí)行的線程是否是應(yīng)用程序的主線程,如果不是的話,那么就會(huì)拋出一個(gè)類型為CalledFromWrongThreadException的異常。

通過了上面的檢查之后,ViewRoot類的成員函數(shù)requestTransparentRegion再檢查參數(shù)child所描述的視圖是否就是當(dāng)前正在處理的ViewRoot對(duì)象所關(guān)聯(lián)的窗口的頂層視圖,即檢查它與ViewRoot類的成員變量mView是否是指向同一個(gè)View對(duì)象。由于一個(gè)ViewRoot對(duì)象有且僅有一個(gè)子視圖,因此,如果上述檢查不通過的話,那么就說明調(diào)用者正在非法調(diào)用ViewRoot類的成員函數(shù)requestTransparentRegion來設(shè)置透明區(qū)域。

通過了上述兩個(gè)檢查之后,ViewRoot類的成員函數(shù)requestTransparentRegion就將成員變量mView所描述的一個(gè)窗口的頂層視圖的成員變量mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位設(shè)置為1,表示該窗口被設(shè)置了一塊透明區(qū)域。

當(dāng)一個(gè)窗口被請(qǐng)求設(shè)置了一塊透明區(qū)域之后,它的窗口屬性就發(fā)生變化了,因此,這時(shí)候除了要將與它所關(guān)聯(lián)的一個(gè)ViewRoot對(duì)象的成員變量mWindowAttributesChanged的值設(shè)置為true之外,還要調(diào)用該ViewRoot對(duì)象的成員函數(shù)requestLayout來請(qǐng)求刷新一下窗口的UI,即請(qǐng)求對(duì)窗口的UI進(jìn)行重新布局和繪制。

ViewRoot類的成員函數(shù)requestLayout最終會(huì)調(diào)用到另外一個(gè)成員函數(shù)performTraversals來實(shí)際執(zhí)行刷新窗口UI的操作。ViewRoot類的成員函數(shù)performTraversals在刷新窗口UI的過程中,就會(huì)將嵌入在它里面的SurfaceView所要設(shè)置的透明區(qū)域收集起來,以便可以請(qǐng)求WindowManagerService將這塊透明區(qū)域設(shè)置到它的繪圖表面上去。

接下來,我們就繼續(xù)分析ViewRoot類的成員函數(shù)performTraversals的實(shí)現(xiàn),以便可以繼續(xù)了解SurfaceView的挖洞過程。

Step 4.?ViewRoot.performTraversals

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。

ViewRoot類的成員函數(shù)performTraversals是在窗口的UI布局完成之后,并且在窗口的UI繪制之前,收集嵌入在它里面的SurfaceView所設(shè)置的透明區(qū)域的,這是因?yàn)榇翱诘腢I布局完成之后,各個(gè)子視圖的大小和位置才能確定下來,這樣SurfaceView才知道自己要設(shè)置的透明區(qū)域的位置和大小。

變量host與ViewRoot類的成員變量mView指向的是同一個(gè)DecorView對(duì)象,這個(gè)DecorView對(duì)象描述的便是當(dāng)前正在處理的窗口的頂層視圖。從前面的Step 3可以知道,如果當(dāng)前正在處理的窗口的頂層視圖內(nèi)嵌有SurfaceView,那么用來描述它的一個(gè)DecorView對(duì)象的成員變量mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位就會(huì)等于1。在這種情況下,ViewRoot類的成員函數(shù)performTraversals就知道需要在當(dāng)前正在處理的窗口的上面設(shè)置一塊透明區(qū)域了。這塊透明區(qū)域的收集過程如下所示:

(1). 計(jì)算頂層視圖的位置和大小,即計(jì)算頂層視圖所占據(jù)的區(qū)域。

(2). 將頂層視圖所占據(jù)的區(qū)域作為窗口的初始化透明區(qū)域,保存在ViewRoot類的成員變量mTransparentRegion中。

(3). 從頂層視圖開始,從上到下收集每一個(gè)子視圖所要設(shè)置的區(qū)域,最終收集到的總透明區(qū)域也是保存在ViewRoot類的成員變量mTransparentRegion中。

(4). 檢查ViewRoot類的成員變量mTransparentRegion和mPreviousTransparentRegion所描述的區(qū)域是否相等。如果不相等的話,那么就說明窗口的透明區(qū)域發(fā)生了變化,這時(shí)候就需要調(diào)用ViewRoot類的的靜態(tài)成員變量sWindowSession所描述的一個(gè)Binder代理對(duì)象的成員函數(shù)setTransparentRegion通知WindowManagerService為窗口設(shè)置由成員變量mTransparentRegion所指定的透明區(qū)域。

其中,第(3)步是通過調(diào)用變量host所描述的一個(gè)DecorView對(duì)象的成員函數(shù)gatherTransparentRegion來實(shí)現(xiàn)的。 DecorView類的成員函數(shù)gatherTransparentRegion是從父類ViewGroup繼承下來的,因此,接下來我們就繼續(xù)分析ViewGroup類的成員函數(shù)gatherTransparentRegion的實(shí)現(xiàn),以便可以了解SurfaceView的挖洞過程。

Step 5.?ViewGroup.gatherTransparentRegion

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。

ViewGroup類的成員函數(shù)gatherTransparentRegion首先是檢查當(dāng)前正在處理的視圖容器是否被請(qǐng)求設(shè)置透明區(qū)域,即檢查成員變量mPrivateFlags的值的 View.REQUEST_TRANSPARENT_REGIONS位是否等于1。如果不等于1,那么就說明不用往下繼續(xù)收集窗口的透明區(qū)域了,因?yàn)樵谶@種情況下,當(dāng)前正在處理的視圖容器及其子視圖都不可能設(shè)置有透明區(qū)域。另一方面,如果參數(shù)region的值等于null,那么就說明調(diào)用者不關(guān)心當(dāng)前正在處理的視圖容器的透明區(qū)域,而是關(guān)心它是透明的,還是不透明的。在上述兩種情況下,ViewGroup類的成員函數(shù)gatherTransparentRegion都不用進(jìn)一步處理了。

假設(shè)當(dāng)前正在處理的視圖容器被請(qǐng)求設(shè)置有透明區(qū)域,并且參數(shù)region的值不等于null,那么接下來ViewGroup類的成員函數(shù)gatherTransparentRegion就執(zhí)行以下兩個(gè)操作:

(1). 調(diào)用父類View的成員函數(shù)gatherTransparentRegion來檢查當(dāng)前正在處理的視圖容器是否需要繪制。如果需要繪制的話,那么就會(huì)將它所占據(jù)的區(qū)域從參數(shù)region所占據(jù)的區(qū)域移除,這是因?yàn)閰?shù)region所描述的區(qū)域開始的時(shí)候是等于窗口的頂層視圖的大小的,也就是等于窗口的整個(gè)大小的。

(2). 調(diào)用當(dāng)前正在處理的視圖容器的每一個(gè)子視圖的成員函數(shù)gatherTransparentRegion來繼續(xù)往下收集透明區(qū)域。

在接下來的Step 6中,我們?cè)僭敿?xì)分析當(dāng)前正在處理的視圖容器的每一個(gè)子視圖的透明區(qū)域的收集過程,現(xiàn)在我們主要分析View類的成員函數(shù)gatherTransparentRegion的實(shí)現(xiàn),如下所示:


這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/View.java中。

View類的成員函數(shù)gatherTransparentRegion首先是檢查當(dāng)前正在處理的視圖的前景是否需要繪制,即檢查成員變量mPrivateFlags的值的SKIP_DRAW位是否等于0。如果等于0的話,那么就說明當(dāng)前正在處理的視圖的前景是需要繪制的。在這種情況下,View類的成員函數(shù)gatherTransparentRegion就會(huì)將當(dāng)前正在處理的視圖所占據(jù)的區(qū)域從參數(shù)region所描述的區(qū)域中移除,以便當(dāng)前正在處理的視圖的前景可以顯示出來。

另一方面,如果當(dāng)前正在處理的視圖的前景不需要繪制,但是該視圖的背景需要繪制,并且該視圖是設(shè)置有的,即成員變量mPrivateFlags的值的SKIP_DRAW位不等于0,并且成員變量mBGDrawable的值不等于null,這時(shí)候View類的成員函數(shù)gatherTransparentRegion就會(huì)調(diào)用另外一個(gè)成員函數(shù)applyDrawableToTransparentRegion來將該背景中的不透明區(qū)域從參數(shù)region所描述的區(qū)域中移除,以便當(dāng)前正在處理的視圖的背景可以顯示出來。

回到ViewGroup類的成員函數(shù)gatherTransparentRegion中,當(dāng)前正在處理的視圖容器即為當(dāng)前正在處理的窗口的頂層視圖,前面我們已經(jīng)假設(shè)它里面嵌入有一個(gè)SurfaceView子視圖,因此,接下來就會(huì)收集該SurfaceView子視圖所設(shè)置的透明區(qū)域,這是通過調(diào)用SurfaceView類的成員函數(shù)gatherTransparentRegion來實(shí)現(xiàn)的。

接下來,我們就繼續(xù)分析SurfaceView類的成員函數(shù)gatherTransparentRegion的實(shí)現(xiàn),以便可以繼續(xù)了解SurfaceView的挖洞過程。

Step 6.?SurfaceView.gatherTransparentRegion

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView類的成員函數(shù)gatherTransparentRegion首先是檢查當(dāng)前正在處理的SurfaceView是否是用作窗口面板的,即它的成員變量mWindowType的值是否等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL。如果等于的話,那么就會(huì)調(diào)用父類View的成員函數(shù)gatherTransparentRegion來檢查該面板是否需要繪制。如果需要繪制,那么就會(huì)將它所占據(jù)的區(qū)域從參數(shù)region所描述的區(qū)域移除。

假設(shè)當(dāng)前正在處理的SurfaceView不是用作窗口面板的,那么SurfaceView類的成員函數(shù)gatherTransparentRegion接下來就會(huì)直接檢查當(dāng)前正在處理的SurfaceView是否是需要在宿主窗口的繪圖表面上進(jìn)行繪制,即檢查成員變量mPrivateFlags的值的SKIP_DRAW位是否等于1。如果需要的話,那么也會(huì)調(diào)用父類View的成員函數(shù)gatherTransparentRegion來將它所占據(jù)的區(qū)域從參數(shù)region所描述的區(qū)域移除。

假設(shè)當(dāng)前正在處理的SurfaceView不是用作窗口面板,并且也是不需要在宿主窗口的繪圖表面上進(jìn)行繪制的,而參數(shù)region的值又不等于null,那么SurfaceView類的成員函數(shù)gatherTransparentRegion就會(huì)先計(jì)算好當(dāng)前正在處理的SurfaceView所占據(jù)的區(qū)域,然后再將該區(qū)域添加到參數(shù)region所描述的區(qū)域中去,這樣就可以得到窗口的一個(gè)新的透明區(qū)域。

最后,SurfaceView類的成員函數(shù)gatherTransparentRegion判斷當(dāng)前正在處理的SurfaceView的繪圖表面的像素格式是否設(shè)置有透明值。如果有的話,那么就會(huì)將變量opaque的值設(shè)置為false,否則的話,變量opaque的值就保持為true。變量opaque的值最終會(huì)返回給調(diào)用者,這樣調(diào)用者就可以知道當(dāng)前正在處理的SurfaceView的繪圖表面是否是半透明的了。

至此,我們就分析完成SurfaceView的挖洞過程了,接下來我們繼續(xù)分析SurfaceView的繪制過程。

3.?SurfaceView的繪制過程

SurfaceView雖然具有獨(dú)立的繪圖表面,不過它仍然是宿主窗口的視圖結(jié)構(gòu)中的一個(gè)結(jié)點(diǎn),因此,它仍然是可以參與到宿主窗口的繪制流程中去的。從前面Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過程分析一文可以知道,窗口在繪制的過程中,每一個(gè)子視圖的成員函數(shù)draw或者dispatchDraw都會(huì)被調(diào)用到,以便它們可以繪制自己的UI。

SurfaceView類的成員函數(shù)draw和dispatchDraw的實(shí)現(xiàn)如下所示:

這兩個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView類的成員函數(shù)draw和dispatchDraw的參數(shù)canvas所描述的都是建立在宿主窗口的繪圖表面上的畫布,因此,在這塊畫布上繪制的任何UI都是出現(xiàn)在宿主窗口的繪圖表面上的。

本來SurfaceView類的成員函數(shù)draw是用來將自己的UI繪制在宿主窗口的繪圖表面上的,但是這里我們可以看到,如果當(dāng)前正在處理的SurfaceView不是用作宿主窗口面板的時(shí)候,即其成員變量mWindowType的值不等于WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的時(shí)候,SurfaceView類的成員函數(shù)draw只是簡(jiǎn)單地將它所占據(jù)的區(qū)域繪制為黑色。

本來SurfaceView類的成員函數(shù)dispatchDraw是用來繪制SurfaceView的子視圖的,但是這里我們同樣看到,如果當(dāng)前正在處理的SurfaceView不是用作宿主窗口面板的時(shí)候,那么SurfaceView類的成員函數(shù)dispatchDraw只是簡(jiǎn)單地將它所占據(jù)的區(qū)域繪制為黑色,同時(shí),它還會(huì)通過調(diào)用另外一個(gè)成員函數(shù)updateWindow更新自己的UI,實(shí)際上就是請(qǐng)求WindowManagerService服務(wù)對(duì)自己的UI進(jìn)行布局,以及創(chuàng)建繪圖表面,具體可以參考前面第1部分的內(nèi)容。

從SurfaceView類的成員函數(shù)draw和dispatchDraw的實(shí)現(xiàn)就可以看出,SurfaceView在其宿主窗口的繪圖表面上面所做的操作就是將自己所占據(jù)的區(qū)域繪為黑色,除此之外,就沒有其它更多的操作了,這是因?yàn)镾urfaceView的UI是要展現(xiàn)在它自己的繪圖表面上面的。接下來我們就分析如何在SurfaceView的繪圖表面上面進(jìn)行UI繪制。

從前面Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過程分析一文可以知道,如果要在一個(gè)繪圖表面進(jìn)行UI繪制,那么就順序執(zhí)行以下的操作:

(1). 在繪圖表面的基礎(chǔ)上建立一塊畫布,即獲得一個(gè)Canvas對(duì)象。

(2). 利用Canvas類提供的繪圖接口在前面獲得的畫布上繪制任意的UI。

(3). 將已經(jīng)填充好了UI數(shù)據(jù)的畫布緩沖區(qū)提交給SurfaceFlinger服務(wù),以便SurfaceFlinger服務(wù)可以將它合成到屏幕上去。

SurfaceView提供了一個(gè)SurfaceHolder接口,通過這個(gè)SurfaceHolder接口就可以執(zhí)行上述的第(1)和引(3)個(gè)操作,示例代碼如下所示:

SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);

SurfaceHolder?sh?=?sv.getHolder();

Cavas?canvas?=?sh.lockCanvas()

//Draw?something?on?canvas

sh.unlockCanvasAndPost(canvas);

注意,只有在一個(gè)SurfaceView的繪圖表面的類型不是SURFACE_TYPE_PUSH_BUFFERS的時(shí)候,我們才可以自由地在上面繪制UI。我們使用SurfaceView來顯示攝像頭預(yù)覽或者播放視頻時(shí),一般就是會(huì)將它的繪圖表面的類型設(shè)置為SURFACE_TYPE_PUSH_BUFFERS。在這種情況下,SurfaceView的繪圖表面所使用的圖形緩沖區(qū)是完全由攝像頭服務(wù)或者視頻播放服務(wù)來提供的,因此,我們就不可以隨意地去訪問該圖形緩沖區(qū),而是要由攝像頭服務(wù)或者視頻播放服務(wù)來訪問,因?yàn)樵搱D形緩沖區(qū)有可能是在專門的硬件里面分配的。

另外還有一個(gè)地方需要注意的是,上述代碼既可以在應(yīng)用程序的主線程中執(zhí)行,也可以是在一個(gè)獨(dú)立的線程中執(zhí)行。如果上述代碼是在應(yīng)用程序的主線程中執(zhí)行,那么就需要保證它們不會(huì)占用過多的時(shí)間,否則的話,就會(huì)導(dǎo)致應(yīng)用程序的主線程不能及時(shí)地響應(yīng)用戶輸入,從而導(dǎo)致ANR問題。

為了方便起見,我們假設(shè)一個(gè)SurfaceView的繪圖表面的類型不是SURFACE_TYPE_PUSH_BUFFERS,接下來,我們就從SurfaceView的成員函數(shù)getHolder開始,分析這個(gè)SurfaceView的繪制過程,如下所示:

圖4 SurfaceView的繪制過程

這個(gè)過程可以分為5個(gè)步驟,接下來我們就詳細(xì)分析每一個(gè)步驟。

Step 1. SurfaceView.getHolder

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceView類的成員函數(shù)getHolder的實(shí)現(xiàn)很簡(jiǎn)單,它只是將成員變量mSurfaceHolder所指向的一個(gè)SurfaceHolder對(duì)象返回給調(diào)用者。

Step 2.?SurfaceHolder.lockCanvas

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceHolder類的成員函數(shù)lockCanvas通過調(diào)用另外一個(gè)成員函數(shù)internalLockCanvas來在當(dāng)前正在處理的SurfaceView的繪圖表面上建立一塊畫布返回給調(diào)用者。

SurfaceHolder類的成員函數(shù)internalLockCanvas首先是判斷當(dāng)前正在處理的SurfaceView的繪圖表面的類型是否是SURFACE_TYPE_PUSH_BUFFERS,如果是的話,那么就會(huì)拋出一個(gè)類型為BadSurfaceTypeException的異常,原因如前面所述。

由于接下來SurfaceHolder類的成員函數(shù)internalLockCanvas要在當(dāng)前正在處理的SurfaceView的繪圖表面上建立一塊畫布,并且返回給調(diào)用者訪問,而這塊畫布不是線程安全的,也就是說它不能同時(shí)被多個(gè)線程訪問,因此,就需要對(duì)當(dāng)前正在處理的SurfaceView的繪圖表面進(jìn)行鎖保護(hù),這是通過它的鎖定它的成員變量mSurfaceLock所指向的一個(gè)ReentrantLock對(duì)象來實(shí)現(xiàn)的。

注意,如果當(dāng)前正在處理的SurfaceView的成員變量mWindow的值等于null,那么就說明它的繪圖表面還沒有創(chuàng)建好,這時(shí)候就無法創(chuàng)建一塊畫布返回給調(diào)用者。同時(shí),如果當(dāng)前正在處理的SurfaceView的繪圖表面已經(jīng)創(chuàng)建好,但是該SurfaceView當(dāng)前是處于停止繪制的狀態(tài),即它的成員變量mDrawingStopped的值等于true,那么也是無法創(chuàng)建一塊畫布返回給調(diào)用者的。

假設(shè)當(dāng)前正在處理的SurfaceView的繪制表面已經(jīng)創(chuàng)建好,并且它不是處于停止繪制的狀態(tài),那么SurfaceHolder類的成員函數(shù)internalLockCanvas就會(huì)通過調(diào)用該SurfaceView的成員變量mSurface所指向的一個(gè)Surface對(duì)象的成員函數(shù)lockCanvas來創(chuàng)建一塊畫布,并且返回給調(diào)用者。注意,在這種情況下,當(dāng)前正在處理的SurfaceView的繪制表面還是處于鎖定狀態(tài)的。

另一方面,如果SurfaceHolder類的成員函數(shù)internalLockCanvas不能成功地在當(dāng)前正在處理的SurfaceView的繪制表面上創(chuàng)建一塊畫布,即變量c的值等于null,那么SurfaceHolder類的成員函數(shù)internalLockCanvas在返回一個(gè)null值調(diào)用者之前,還會(huì)將該SurfaceView的繪制表面就會(huì)解鎖。

從前面第1部分的內(nèi)容可以知道,SurfaceView類的成員變量mSurface描述的是就是SurfaceView的專有繪圖表面,接下來我們就繼續(xù)分析它所指向的一個(gè)Surface對(duì)象的成員函數(shù)lockCanvas的實(shí)現(xiàn),以便可以了解SurfaceView的畫布的創(chuàng)建過程。

Step 3.?Surface.lockCanvas

Surface類的成員函數(shù)lockCanvas的具體實(shí)現(xiàn)可以參考前面Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過程分析一文,它大致就是通過JNI方法來在當(dāng)前正在處理的繪圖表面上獲得一個(gè)圖形緩沖區(qū),并且將這個(gè)圖形繪沖區(qū)封裝在一塊類型為Canvas的畫布中返回給調(diào)用者使用。

調(diào)用者獲得了一塊類型為Canvas的畫布之后,就可以調(diào)用Canvas類所提供的繪圖函數(shù)來繪制任意的UI了,例如,調(diào)用Canvas類的成員函數(shù)drawLine、drawRect和drawCircle可以分別用來畫直線、矩形和圓。

調(diào)用者在畫布上繪制完成所需要的UI之后,就可以將這塊畫布的圖形繪沖區(qū)的UI數(shù)據(jù)提交給SurfaceFlinger服務(wù)來處理了,這是通過調(diào)用SurfaceHolder類的成員函數(shù)unlockCanvasAndPost來實(shí)現(xiàn)的。

Step 4.?SurfaceHolder.unlockCanvasAndPost

這個(gè)函數(shù)定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

SurfaceHolder類的成員函數(shù)unlockCanvasAndPost是通過調(diào)用當(dāng)前正在處理的SurfaceView的成員變量mSurface所指向的一個(gè)Surface對(duì)象的成員函數(shù)unlockCanvasAndPost來將參數(shù)canvas所描述的一塊畫布的圖形緩沖區(qū)提交給SurfaceFlinger服務(wù)處理的。

提交完成參數(shù)canvas所描述的一塊畫布的圖形緩沖區(qū)給SurfaceFlinger服務(wù)之后,SurfaceHolder類的成員函數(shù)unlockCanvasAndPost再調(diào)用當(dāng)前正在處理的SurfaceView的成員變量mSurfaceLock所指向的一個(gè)ReentrantLock對(duì)象的成員函數(shù)unlock來解鎖當(dāng)前正在處理的SurfaceView的繪圖表面,因?yàn)樵谇懊娴腟tep 2中,我們?cè)?jīng)將該繪圖表面鎖住了。

接下來,我們就繼續(xù)分析Surface類的成員函數(shù)unlockCanvasAndPost的實(shí)現(xiàn),以便可以了解SurfaceView的繪制過程。

Step 5.?Surface.unlockCanvasAndPost

Surface類的成員函數(shù)unlockCanvasAndPost的具體實(shí)現(xiàn)同樣是可以參考前面Android應(yīng)用程序窗口(Activity)的測(cè)量(Measure)、布局(Layout)和繪制(Draw)過程分析一文,它大致就是將在前面的Step 3中所獲得的一個(gè)圖形緩沖區(qū)提交給SurfaceFlinger服務(wù),以便SurfaceFlinger服務(wù)可以在合適的時(shí)候?qū)⒃搱D形緩沖區(qū)合成到屏幕上去顯示,這樣就可以將對(duì)應(yīng)的SurfaceView的UI展現(xiàn)出來了。

至此,我們就分析完成SurfaceView的繪制過程了,整個(gè)SurfaceView的實(shí)現(xiàn)原理也就分析完了。總結(jié)來說,就是SurfaceView有以下三個(gè)特點(diǎn):

A. 具有獨(dú)立的繪圖表面;

B. 需要在宿主窗口上挖一個(gè)洞來顯示自己;

C. 它的UI繪制可以在獨(dú)立的線程中進(jìn)行,這樣就可以進(jìn)行復(fù)雜的UI繪制,并且不會(huì)影響應(yīng)用程序的主線程響應(yīng)用戶輸入。

!!!特別說明

這篇文章不是我寫的,復(fù)制粘貼下來當(dāng)作筆記,后來好多同行看到了給我一頓評(píng)論一頓罵,我本以為自己公開代碼或者文章就不要怕別人抄,都是擼代碼的誰沒有復(fù)制粘貼過別人的代碼,你復(fù)制粘貼別人的代碼到自己的項(xiàng)目中,你加注釋說這是別人的代碼我復(fù)制的了嗎?經(jīng)過被人一頓臭罵我發(fā)現(xiàn)我的意識(shí)錯(cuò)了,不管怎么樣不想扯這些,如果其他同學(xué)看到,還請(qǐng)多看原作者的原文https://blog.csdn.net/luoshengyang/article/details/8661317/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,328評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,878評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,098評(píng)論 0 286
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,554評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,725評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,971評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評(píng)論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,339評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,695評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容