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