在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/