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

Android系統(tǒng)中辩撑,有一種特殊的視圖蛹屿,稱為SurfaceView,它擁有獨(dú)立的繪圖表面放钦,即它不與其宿主窗口共享同一個(gè)繪圖表面色徘。由于擁有獨(dú)立的繪圖表面,因此SurfaceView的UI就可以在一個(gè)獨(dú)立的線程中進(jìn)行繪制操禀。又由于不會占用主線程資源褂策,SurfaceView一方面可以實(shí)現(xiàn)復(fù)雜而高效的UI,另一方面又不會導(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ù)中就對應(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軸位置的翎冲,但是前者會在后者的上面挖一個(gè)“洞”出來,以便它的UI可以對用戶可見媳荒。實(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仍然是會參與到宿主窗口的某些執(zhí)行流程中去。

每當(dāng)一個(gè)窗口需要刷新UI時(shí)搓劫,就會調(diào)用ViewRoot類的成員函數(shù)performTraversals劣光。ViewRoot類的成員函數(shù)performTraversals在執(zhí)行的過程中,如果發(fā)現(xiàn)當(dāng)前窗口的繪圖表面還沒有創(chuàng)建糟把,或者發(fā)現(xiàn)當(dāng)前窗口的繪圖表面已經(jīng)失效了,那么就會請求WindowManagerService服務(wù)創(chuàng)建一個(gè)新的繪圖表面牲剃,同時(shí)遣疯,它還會通過一系列的回調(diào)函數(shù)來讓嵌入在窗口里面的SurfaceView有機(jī)會創(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對象被辑,這個(gè)DecorView對象描述的當(dāng)前窗口的頂層視圖燎悍。

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

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

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

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

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

我們假設(shè)當(dāng)前窗口有一個(gè)SurfaceView米愿,那么當(dāng)該SurfaceView接收到它被附加到宿主窗口以及它的宿主窗口的可見性發(fā)生變化的通知時(shí),就會相應(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)很簡單勇垛,它只是簡單地調(diào)用當(dāng)前正在處理的視圖容器的每一個(gè)子視圖的成員函數(shù)dispatchAttachedToWindow,以便可以通知這些子視圖士鸥,它們被附加到宿主窗口上去了闲孤。

當(dāng)前正在處理的視圖容器即為當(dāng)前正在處理的窗口的頂層視圖,由于前面我們當(dāng)前正在處理的窗口有一個(gè)SurfaceView烤礁,因此這一步就會調(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對象保存在自己的成員變量mAttachInfo中鲤脏,以便當(dāng)前視圖可以獲得其所附加在的窗口的相關(guān)信息们颜,接下來再調(diào)用另外一個(gè)成員函數(shù)onAttachedToWindow來讓子類有機(jī)會處理它被附加到宿主窗口的事件吕朵。

前面我們已經(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對象的成員函數(shù)requestTransparentRegion刨秆,就可以通知到當(dāng)前正在處理的SurfaceView的父視圖凳谦,當(dāng)前正在處理的SurfaceView需要在宿主窗口的繪圖表面上設(shè)置一塊透明區(qū)域。在后面第2部分的內(nèi)容中衡未,我們再詳細(xì)分析SurfaceView在宿主窗口的繪圖表面的挖洞過程晾蜘。

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

這一步執(zhí)行完成之后板惑,返回到前面的Step 1中橄镜,即ViewRoot類的成員函數(shù)performTraversals中,我們假設(shè)當(dāng)前窗口的可見性發(fā)生了變化冯乘,那么接下來就會調(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)很簡單禾唁,它只是簡單地調(diào)用當(dāng)前正在處理的視圖容器的每一個(gè)子視圖的成員函數(shù)dispatchWindowVisibilityChanged效览,以便可以通知這些子視圖,它們所附加在的宿主窗口的可見性發(fā)生變化了荡短。

當(dāng)前正在處理的視圖容器即為當(dāng)前正在處理的窗口的頂層視圖丐枉,由于前面我們當(dāng)前正在處理的窗口有一個(gè)SurfaceView,因此這一步就會調(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)很簡單,它只是調(diào)用另外一個(gè)成員函數(shù)onWindowVisibilityChanged來讓子類有機(jī)會處理它所附加在的宿主窗口的可見性變化事件锭沟。

前面我們已經(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就會調(diào)用另外一個(gè)成員函數(shù)updateWindow來更新當(dāng)前正在處理的SurfaceView。在更新的過程中垦搬,如果發(fā)現(xiàn)當(dāng)前正在處理的SurfaceView還沒有創(chuà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對象交汤,這個(gè)Surface對象描述的便是SurfaceView專有的繪圖表面雏赦。對于一般的視圖來說,例如芙扎,TextView或者Button星岗,它們是沒有專有的繪圖表面的,而是與專宿主窗口共享同一個(gè)繪圖表面戒洼,因此俏橘,它們就不會像SurfaceView一樣,有一個(gè)專門的類型為Surface的成員變量來描述自己的繪圖表面圈浇。每一個(gè)Activity窗口都關(guān)聯(lián)有一個(gè)W對象寥掐。這個(gè)W對象是一個(gè)實(shí)現(xiàn)了IWindow接口的Binder本地對象,它是用來傳遞給WindowManagerService服務(wù)的磷蜀,以便WindowManagerService服務(wù)可以通過它來和它所關(guān)聯(lián)的Activity窗口通信召耘。例如,WindowManagerService服務(wù)通過這個(gè)W對象來通知它所關(guān)聯(lián)的Activity窗口的大小或者可見性發(fā)生變化了褐隆。同時(shí)污它,這個(gè)W對象還用來在WindowManagerService服務(wù)這一側(cè)唯一地標(biāo)志一個(gè)窗口,也就是說妓灌,WindowManagerService服務(wù)會為這個(gè)W對象創(chuàng)建一個(gè)WindowState對象。

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

SurfaceView類的成員變量mWindowType描述的是SurfaceView的窗口類型源祈,它的默認(rèn)值等于TYPE_APPLICATION_MEDIA。也就是說色迂,我們在創(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è)就會對應(yīng)有三個(gè)WindowState對象,其中慕趴,用來描述SurfaceView的WindowState對象是附加在用來描述Activity窗口的WindowState對象上的泛啸。如果一個(gè)WindowState對象所描述的窗口的類型為TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY吨铸,那么它就會位于它所附加在的窗口的下面。也就是說,類型為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軸位置帝际。

注意,我們在創(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對象來描述該SurfaceView的繪圖表面恭取。

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

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

注意撮慨,我們在創(chuàng)建了一個(gè)SurfaceView之后竿痰,可以調(diào)用它的成員函數(shù)getHolder獲得一個(gè)SurfaceHolder對象脆粥,然后再調(diào)用該SurfaceHolder對象的成員函數(shù)setType來修改該SurfaceView的繪圖表面的類型砌溺,即修改該SurfaceView的成員變量mRequestedType的值。

介紹完成SurfaceView類的成員變量mSurface变隔、mWindow规伐、mWindowType和mRequestedType的含義之后,我們再介紹其它幾個(gè)接下來要用到的其它成員變量的含義:

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

--mRequestedWidth培慌,用來描述SurfaceView最后一次被請求的寬度豁陆。

--mRequestedHeight,用來描述SurfaceView最后一次被請求的高度吵护。

--mRequestedFormat盒音,用來描述SurfaceView最后一次被請求的繪圖表面的像素格式。

--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對象狞甚,用來傳遞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沒有被請求設(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就需要對SurfaceView的各種信息進(jìn)行更新,即執(zhí)行以下第5步至第7步操作靠益。

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

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

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

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

2. SurfaceView的挖洞過程

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

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

從SurfaceView的繪圖表面的創(chuàng)建過程可以知道,SurfaceView在被附加到宿主窗口之上的時(shí)候荣暮,SurfaceView類的成員函數(shù)onAttachedToWindow就會被調(diào)用庭惜。SurfaceView類的成員函數(shù)onAttachedToWindow在被調(diào)用的期間,就會請求在宿主窗口上設(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就會調(diào)用DecorView類的成員函數(shù)requestTransparentRegion來請求在宿主窗口之上挖一個(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ù)向上請求設(shè)置透明區(qū)域仙蚜,這個(gè)過程會一直持續(xù)到當(dāng)前正在處理的視圖容器為窗口的頂層視圖為止。

前面我們已經(jīng)假設(shè)了參數(shù)child所描述的SurfaceView是直接嵌入在宿主窗口的頂層視圖中的厂汗,而窗口的頂層視圖的父視圖是使用一個(gè)ViewRoot對象來描述的委粉,也就是說,當(dāng)前正在處理的視圖容器的成員變量mParent指向的是一個(gè)ViewRoot對象娶桦,因此贾节,接下來我們就繼續(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)用程序的主線程知牌,如果不是的話,那么就會拋出一個(gè)類型為CalledFromWrongThreadException的異常戴差。

通過了上面的檢查之后,ViewRoot類的成員函數(shù)requestTransparentRegion再檢查參數(shù)child所描述的視圖是否就是當(dāng)前正在處理的ViewRoot對象所關(guān)聯(lián)的窗口的頂層視圖铛嘱,即檢查它與ViewRoot類的成員變量mView是否是指向同一個(gè)View對象暖释。由于一個(gè)ViewRoot對象有且僅有一個(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è)窗口被請求設(shè)置了一塊透明區(qū)域之后煮纵,它的窗口屬性就發(fā)生變化了,因此狸驳,這時(shí)候除了要將與它所關(guān)聯(lián)的一個(gè)ViewRoot對象的成員變量mWindowAttributesChanged的值設(shè)置為true之外乡摹,還要調(diào)用該ViewRoot對象的成員函數(shù)requestLayout來請求刷新一下窗口的UI役耕,即請求對窗口的UI進(jìn)行重新布局和繪制。

ViewRoot類的成員函數(shù)requestLayout最終會調(diào)用到另外一個(gè)成員函數(shù)performTraversals來實(shí)際執(zhí)行刷新窗口UI的操作聪廉。ViewRoot類的成員函數(shù)performTraversals在刷新窗口UI的過程中瞬痘,就會將嵌入在它里面的SurfaceView所要設(shè)置的透明區(qū)域收集起來,以便可以請求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對象,這個(gè)DecorView對象描述的便是當(dāng)前正在處理的窗口的頂層視圖娶视。從前面的Step 3可以知道晒哄,如果當(dāng)前正在處理的窗口的頂層視圖內(nèi)嵌有SurfaceView睁宰,那么用來描述它的一個(gè)DecorView對象的成員變量mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位就會等于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代理對象的成員函數(shù)setTransparentRegion通知WindowManagerService為窗口設(shè)置由成員變量mTransparentRegion所指定的透明區(qū)域虹蒋。

其中糜芳,第(3)步是通過調(diào)用變量host所描述的一個(gè)DecorView對象的成員函數(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)前正在處理的視圖容器是否被請求設(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)前正在處理的視圖容器被請求設(shè)置有透明區(qū)域郁竟,并且參數(shù)region的值不等于null玛迄,那么接下來ViewGroup類的成員函數(shù)gatherTransparentRegion就執(zhí)行以下兩個(gè)操作:

(1). 調(diào)用父類View的成員函數(shù)gatherTransparentRegion來檢查當(dāng)前正在處理的視圖容器是否需要繪制。如果需要繪制的話棚亩,那么就會將它所占據(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中纺阔,我們再詳細(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就會將當(dāng)前正在處理的視圖所占據(jù)的區(qū)域從參數(shù)region所描述的區(qū)域中移除啃奴,以便當(dāng)前正在處理的視圖的前景可以顯示出來潭陪。

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

回到ViewGroup類的成員函數(shù)gatherTransparentRegion中慷嗜,當(dāng)前正在處理的視圖容器即為當(dāng)前正在處理的窗口的頂層視圖淀弹,前面我們已經(jīng)假設(shè)它里面嵌入有一個(gè)SurfaceView子視圖,因此庆械,接下來就會收集該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。如果等于的話奴紧,那么就會調(diào)用父類View的成員函數(shù)gatherTransparentRegion來檢查該面板是否需要繪制特姐。如果需要繪制,那么就會將它所占據(jù)的區(qū)域從參數(shù)region所描述的區(qū)域移除黍氮。

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

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

最后,SurfaceView類的成員函數(shù)gatherTransparentRegion判斷當(dāng)前正在處理的SurfaceView的繪圖表面的像素格式是否設(shè)置有透明值蚕苇。如果有的話哩掺,那么就會將變量opaque的值設(shè)置為false,否則的話涩笤,變量opaque的值就保持為true嚼吞。變量opaque的值最終會返回給調(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)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文可以知道渤闷,窗口在繪制的過程中疾瓮,每一個(gè)子視圖的成員函數(shù)draw或者dispatchDraw都會被調(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只是簡單地將它所占據(jù)的區(qū)域繪制為黑色削饵。

本來SurfaceView類的成員函數(shù)dispatchDraw是用來繪制SurfaceView的子視圖的岩瘦,但是這里我們同樣看到未巫,如果當(dāng)前正在處理的SurfaceView不是用作宿主窗口面板的時(shí)候,那么SurfaceView類的成員函數(shù)dispatchDraw只是簡單地將它所占據(jù)的區(qū)域繪制為黑色启昧,同時(shí)叙凡,它還會通過調(diào)用另外一個(gè)成員函數(shù)updateWindow更新自己的UI,實(shí)際上就是請求WindowManagerService服務(wù)對自己的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)的測量(Measure)迷帜、布局(Layout)和繪制(Draw)過程分析一文可以知道物舒,如果要在一個(gè)繪圖表面進(jìn)行UI繪制,那么就順序執(zhí)行以下的操作:

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

(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í),一般就是會將它的繪圖表面的類型設(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í)行赶袄,那么就需要保證它們不會占用過多的時(shí)間揽涮,否則的話,就會導(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)很簡單烹困,它只是將成員變量mSurfaceHolder所指向的一個(gè)SurfaceHolder對象返回給調(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,如果是的話绎签,那么就會拋出一個(gè)類型為BadSurfaceTypeException的異常枯饿,原因如前面所述。

由于接下來SurfaceHolder類的成員函數(shù)internalLockCanvas要在當(dāng)前正在處理的SurfaceView的繪圖表面上建立一塊畫布诡必,并且返回給調(diào)用者訪問奢方,而這塊畫布不是線程安全的,也就是說它不能同時(shí)被多個(gè)線程訪問爸舒,因此蟋字,就需要對當(dāng)前正在處理的SurfaceView的繪圖表面進(jìn)行鎖保護(hù),這是通過它的鎖定它的成員變量mSurfaceLock所指向的一個(gè)ReentrantLock對象來實(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就會通過調(diào)用該SurfaceView的成員變量mSurface所指向的一個(gè)Surface對象的成員函數(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)用者之前回懦,還會將該SurfaceView的繪制表面就會解鎖。

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

Step 3.?Surface.lockCanvas

Surface類的成員函數(shù)lockCanvas的具體實(shí)現(xiàn)可以參考前面Android應(yīng)用程序窗口(Activity)的測量(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對象的成員函數(shù)unlockCanvasAndPost來將參數(shù)canvas所描述的一塊畫布的圖形緩沖區(qū)提交給SurfaceFlinger服務(wù)處理的摸柄。

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

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

Step 5.?Surface.unlockCanvasAndPost

Surface類的成員函數(shù)unlockCanvasAndPost的具體實(shí)現(xiàn)同樣是可以參考前面Android應(yīng)用程序窗口(Activity)的測量(Measure)苛吱、布局(Layout)和繪制(Draw)過程分析一文酪术,它大致就是將在前面的Step 3中所獲得的一個(gè)圖形緩沖區(qū)提交給SurfaceFlinger服務(wù),以便SurfaceFlinger服務(wù)可以在合適的時(shí)候?qū)⒃搱D形緩沖區(qū)合成到屏幕上去顯示,這樣就可以將對應(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繪制政敢,并且不會影響應(yīng)用程序的主線程響應(yīng)用戶輸入。

M凇L蟆卓研!特別說明

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末埃脏,一起剝皮案震驚了整個(gè)濱河市搪锣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌彩掐,老刑警劉巖构舟,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異堵幽,居然都是意外死亡狗超,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門朴下,熙熙樓的掌柜王于貴愁眉苦臉地迎上來努咐,“玉大人,你說我怎么就攤上這事殴胧∩裕” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長竿屹。 經(jīng)常有香客問我音五,道長,這世上最難降的妖魔是什么羔沙? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任躺涝,我火速辦了婚禮,結(jié)果婚禮上扼雏,老公的妹妹穿的比我還像新娘坚嗜。我一直安慰自己,他們只是感情好诗充,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布苍蔬。 她就那樣靜靜地躺著,像睡著了一般蝴蜓。 火紅的嫁衣襯著肌膚如雪碟绑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天茎匠,我揣著相機(jī)與錄音格仲,去河邊找鬼。 笑死诵冒,一個(gè)胖子當(dāng)著我的面吹牛凯肋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播汽馋,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼侮东,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了豹芯?” 一聲冷哼從身側(cè)響起悄雅,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎铁蹈,沒想到半個(gè)月后宽闲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡木缝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年便锨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片我碟。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖姚建,靈堂內(nèi)的尸體忽然破棺而出矫俺,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布厘托,位于F島的核電站友雳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铅匹。R本人自食惡果不足惜押赊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望包斑。 院中可真熱鬧流礁,春花似錦、人聲如沸罗丰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萌抵。三九已至找御,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绍填,已是汗流浹背霎桅。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讨永,地道東北人哆档。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像住闯,于是被迫代替她去往敵國和親瓜浸。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

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