Android視圖SurfaceView的實現原理分析

       在Android系統中,有一種特殊的視圖,稱爲SurfaceView,它擁有獨立的繪圖表面,即它不與其宿主窗口共享同一個繪圖表面。由於擁有獨立的繪圖表面,因此SurfaceView的UI就可以在一個獨立的線程中進行行繪製。又由於不佔用主線程資源,SurfaceView一方面可以實現複雜而高效的UI,另一方面又不會導致用戶輸入得不到及時響應。在本文中,我們就詳細分析SurfaceView的實現原理。

       在前面Android控件TextView的實現原理分析一文中提到,普通的Android控件,例如TextView、Button和CheckBox等,它們都是將自己的UI繪製在宿主窗口的繪圖表面之上,這意味着它們的UI是在應用程序的主線程中進行繪製的。由於應用程序的主線程除了要繪製UI之外,還需要及時地響應用戶輸入,否則的話,系統就會認爲應用程序沒有響應了,因此就會彈出一個ANR對話框出來。對於一些遊戲畫面,或者攝像頭預覽、視頻播放來說,它們的UI都比較複雜,而且要求能夠進行高效的繪製,因此,它們的UI就不適合在應用程序的主線程中進行繪製。這時候就必須要給那些需要複雜而高效UI的視圖生成一個獨立的繪圖表面,以及使用一個獨立的線程來繪製這些視圖的UI。

       在前面Android應用程序與SurfaceFlinger服務的關係概述和學習計劃Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃這兩個系統的文章中,我們主要分析了Android應用程序窗口是如何通過SurfaceFlinger服務來繪製自己的UI的。一般來說,每一個窗口在SurfaceFlinger服務中都對應有一個Layer,用來描述它的繪圖表面。對於那些具有SurfaceView的窗口來說,每一個SurfaceView在SurfaceFlinger服務中還對應有一個獨立的Layer或者LayerBuffer,用來單獨描述它的繪圖表面,以區別於它的宿主窗口的繪圖表面。

       無論是LayerBuffer,還是Layer,它們都是以LayerBase爲基類的,也就是說,SurfaceFlinger服務把所有的LayerBuffer和Layer都抽象爲LayerBase,因此就可以用統一的流程來繪製和合成它們的UI。由於LayerBuffer的繪製和合成與Layer的繪製和合成是類似的,因此本文不打算對LayerBuffer的繪製和合成操作進行分析。需要深入理解LayerBuffer的繪製和合成操作的,可以參考Android應用程序與SurfaceFlinger服務的關係概述和學習計劃Android系統Surface機制的SurfaceFlinger服務簡要介紹和學習計劃這兩個系統的文章。

       爲了接下來可以方便地描述SurfaceView的實現原理分析,我們假設在一個Activity窗口的視圖結構中,除了有一個DecorView頂層視圖之外,還有兩個TextView控件,以及一個SurfaceView視圖,這樣該Activity窗口在SurfaceFlinger服務中就對應有兩個Layer或者一個Layer的一個LayerBuffer,如圖1所示:


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

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

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

       從總體上描述了SurfaceView的大致實現原理之後,接下來我們就詳細分析它的具體實現過程,包括它的繪圖表面的創建過程、在宿主窗口上面進行挖洞的過程,以及繪製過程。

       1. SurfaceView的繪圖表面的創建過程

       由於SurfaceView具有獨立的繪圖表面,因此,在它的UI內容可以繪製之前,我們首先要將它的繪圖表面創建出來。儘管SurfaceView不與它的宿主窗口共享同一個繪圖表面,但是它仍然是屬於宿主窗口的視圖結構的一個結點的,也就是說,SurfaceView仍然是會參與到宿主窗口的某些執行流程中去。

       從前面Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析一文可以知道,每當一個窗口需要刷新UI時,就會調用ViewRoot類的成員函數performTraversals。ViewRoot類的成員函數performTraversals在執行的過程中,如果發現當前窗口的繪圖表面還沒有創建,或者發現當前窗口的繪圖表面已經失效了,那麼就會請求WindowManagerService服務創建一個新的繪圖表面,同時,它還會通過一系列的回調函數來讓嵌入在窗口裏面的SurfaceView有機會創建自己的繪圖表面。

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


圖2 SurfaceView的繪圖表面的創建過程

       這個過程可以分爲8個步驟,接下來我們就詳細分析每一個步驟。

       Step 1. ViewRoot.performTraversals


public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {
    ......
    private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        ......
        final View.AttachInfo attachInfo = mAttachInfo;
        final int viewVisibility = getHostVisibility();
        boolean viewVisibilityChanged = mViewVisibility != viewVisibility
                || mNewSurfaceNeeded;
        ......
        if (mFirst) {
            ......
            if (!mAttached) {
                host.dispatchAttachedToWindow(attachInfo, 0);
                mAttached = true;
            }
                                                                                                                      
            ......
        }
                                                                                                           
        ......
        if (viewVisibilityChanged) {
            ......
            host.dispatchWindowVisibilityChanged(viewVisibility);
            ......
        }
        ......
        mFirst = false;
        ......
    }
    ......
}


       這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。

       ViewRoot類的成員函數performTraversals的詳細實現可以參考Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析和Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析這兩篇文章,這裏我們只關注與SurfaceView的繪圖表面的創建相關的邏輯。

       我們首先分析在ViewRoot類的成員函數performTraversals中四個相關的變量host、attachInfo、viewVisibility和viewVisibilityChanged。

       變量host與ViewRoot類的成員變量mView指向的是同一個DecorView對象,這個DecorView對象描述的當前窗口的頂層視圖。

       變量attachInfo與ViewRoot類的成員變量mAttachInfo指向的是同一個AttachInfo對象。在Android系統中,每一個視圖附加到它的宿主窗口的時候,都會獲得一個AttachInfo對象,用來被附加的窗口的信息。

       變量viewVisibility描述的是當前窗口的可見性。

       變量viewVisibilityChanged描述的是當前窗口的可見性是否發生了變化。

       ViewRoot類的成員變量mFirst表示當前窗口是否是第一次被刷新UI。如果是的話,那麼它的值就會等於true,說明當前窗口的繪圖表面還未創建。在這種情況下,如果ViewRoot類的另外一個成員變量mAttached的值也等於true,那麼就表示當前窗口還沒有將它的各個子視圖附加到它的上面來。這時候ViewRoot類的成員函數performTraversals就會從當前窗口的頂層視圖開始,通知每一個子視圖它要被附加到宿主窗口上去了,這是通過調用變量host所指向的一個DecorView對象的成員函數dispatchAttachedToWindow來實現的。DecorView類的成員函數dispatchAttachedToWindow是從父類ViewGroup繼承下來的,在後面的Step 2中,我們再詳細分析ViewGroup類的成員數dispatchAttachedToWindow的實現。

       接下來, ViewRoot類的成員函數performTraversals判斷當前窗口的可見性是否發生了變化,即檢查變量viewVisibilityChanged的值是否等於true。如果發生了變化,那麼就會從當前窗口的頂層視圖開始,通知每一個子視圖它的宿主窗口的可見發生變化了,這是通過調用變量host所指向的一個DecorView對象的成員函數dispatchWindowVisibilityChanged來實現的。DecorView類的成員函數dispatchWindowVisibilityChanged是從父類ViewGroup繼承下來的,在後面的Step 5中,我們再詳細分析ViewGroup類的成員數dispatchWindowVisibilityChanged的實現。

       我們假設當前窗口有一個SurfaceView,那麼當該SurfaceView接收到它被附加到宿主窗口以及它的宿主窗口的可見性發生變化的通知時,就會相應地將自己的繪圖表面創建出來。接下來,我們就分別分析ViewGroup類的成員數dispatchAttachedToWindow和dispatchWindowVisibilityChanged的實現,以便可以瞭解SurfaceView的繪圖表面的創建過程。

        Step 2. ViewGroup.dispatchAttachedToWindow


public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ......
    // Child views of this ViewGroup
    private View[] mChildren;
    // Number of valid children in the mChildren array, the rest should be null or not
    // considered as children
    private int mChildrenCount;
    ......
    @Override
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        super.dispatchAttachedToWindow(info, visibility);
        visibility |= mViewFlags & VISIBILITY_MASK;
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            children[i].dispatchAttachedToWindow(info, visibility);
        }
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。


       ViewGroup類的成員變量mChildren保存的是當前正在處理的視圖容器的子視圖,而另外一個成員變量mChildrenCount保存的是這些子視圖的數量。

       ViewGroup類的成員函數dispatchAttachedToWindow的實現很簡單,它只是簡單地調用當前正在處理的視圖容器的每一個子視圖的成員函數dispatchAttachedToWindow,以便可以通知這些子視圖,它們被附加到宿主窗口上去了。

       當前正在處理的視圖容器即爲當前正在處理的窗口的頂層視圖,由於前面我們當前正在處理的窗口有一個SurfaceView,因此這一步就會調用到該SurfaceView的成員函數dispatchAttachedToWindow。

       由於SurfaceView類的成員函數dispatchAttachedToWindow是從父類View繼承下來的,因此,接下來我們就繼續分析View類的成員函數dispatchAttachedToWindow的實現。

       Step 3. View.dispatchAttachedToWindow


public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    ......
    AttachInfo mAttachInfo;
    ......
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        //System.out.println("Attached! " + this);
        mAttachInfo = info;
        ......
        onAttachedToWindow();
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/View.java中。


       View類的成員函數dispatchAttachedToWindow首先將參數info所指向的一個AttachInfo對象保存在自己的成員變量mAttachInfo中,以便當前視圖可以獲得其所附加在的窗口的相關信息,接下來再調用另外一個成員函數onAttachedToWindow來讓子類有機會處理它被附加到宿主窗口的事件。

       前面我們已經假設了當前處理的是一個SurfaceView。SurfaceView類重寫了父類View的成員函數onAttachedToWindow,接下來我們就繼續分析SurfaceView的成員函數onAttachedToWindow的實現,以便可以瞭解SurfaceView的繪圖表面的創建過程。

       Step 4. SurfaceView.onAttachedToWindow


public class SurfaceView extends View {
    ......
    IWindowSession mSession;
    ......
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mParent.requestTransparentRegion(this);
        mSession = getWindowSession();
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


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

       第一件事情是通知父視圖,當前正在處理的SurfaceView需要在宿主窗口的繪圖表面上挖一個洞,即需要在宿主窗口的繪圖表面上設置一塊透明區域。當前正在處理的SurfaceView的父視圖保存在父類View的成員變量mParent中,通過調用這個成員變量mParent所指向的一個ViewGroup對象的成員函數requestTransparentRegion,就可以通知到當前正在處理的SurfaceView的父視圖,當前正在處理的SurfaceView需要在宿主窗口的繪圖表面上設置一塊透明區域。在後面第2部分的內容中,我們再詳細分析SurfaceView在宿主窗口的繪圖表面的挖洞過程。

      第二件事情是調用從父類View繼承下來的成員函數getWindowSession來獲得一個實現了IWindowSession接口的Binder代理對象,並且將該Binder代理對象保存在SurfaceView類的成員變量mSession中。從前面Android應用程序窗口(Activity)實現框架簡要介紹和學習計劃這個系列的文章可以知道,在Android系統中,每一個應用程序進程都有一個實現了IWindowSession接口的Binder代理對象,這個Binder代理對象是用來與WindowManagerService服務進行通信的,View類的成員函數getWindowSession返回的就是該Binder代理對象。在接下來的Step 8中,我們就可以看到,SurfaceView就可以通過這個實現了IWindowSession接口的Binder代理對象來請求WindowManagerService服務爲自己創建繪圖表面的。

       這一步執行完成之後,返回到前面的Step 1中,即ViewRoot類的成員函數performTraversals中,我們假設當前窗口的可見性發生了變化,那麼接下來就會調用頂層視圖的成員函數dispatchWindowVisibilityChanged,以便可以通知各個子視圖,它的宿主窗口的可見性發生化了。

       窗口的頂層視圖是使用DecorView類來描述的,而DecroView類的成員函數dispatchWindowVisibilityChanged是從父類ViewGroup類繼承下來的,因此,接下來我們就繼續分析GroupView類的成員函數dispatchWindowVisibilityChanged的實現,以便可以瞭解包含在當前窗口裏面的一個SurfaceView的繪圖表面的創建過程。

       Step 5. ViewGroup.dispatchWindowVisibilityChanged


public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ......
    @Override
    public void dispatchWindowVisibilityChanged(int visibility) {
        super.dispatchWindowVisibilityChanged(visibility);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            children[i].dispatchWindowVisibilityChanged(visibility);
        }
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。

       ViewGroup類的成員函數dispatchWindowVisibilityChanged的實現很簡單,它只是簡單地調用當前正在處理的視圖容器的每一個子視圖的成員函數dispatchWindowVisibilityChanged,以便可以通知這些子視圖,它們所附加在的宿主窗口的可見性發生變化了。

       當前正在處理的視圖容器即爲當前正在處理的窗口的頂層視圖,由於前面我們當前正在處理的窗口有一個SurfaceView,因此這一步就會調用到該SurfaceView的成員函數dispatchWindowVisibilityChanged。

       由於SurfaceView類的成員函數dispatchWindowVisibilityChanged是從父類View繼承下來的,因此,接下來我們就繼續分析View類的成員函數dispatchWindowVisibilityChanged的實現。

       Step 6. View.dispatchWindowVisibilityChanged


public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    ......
    public void dispatchWindowVisibilityChanged(int visibility) {
        onWindowVisibilityChanged(visibility);
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/View.java中。


       View類的成員函數dispatchWindowVisibilityChanged的實現很簡單,它只是調用另外一個成員函數onWindowVisibilityChanged來讓子類有機會處理它所附加在的宿主窗口的可見性變化事件。

       前面我們已經假設了當前處理的是一個SurfaceView。SurfaceView類重寫了父類View的成員函數onWindowVisibilityChanged,接下來我們就繼續分析SurfaceView的成員函數onWindowVisibilityChanged的實現,以便可以瞭解SurfaceView的繪圖表面的創建過程。

       Step 7. SurfaceView.onWindowVisibilityChanged


public class SurfaceView extends View {
    ......
    boolean mRequestedVisible = false;
    boolean mWindowVisibility = false;
    boolean mViewVisibility = false;
    .....
    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mWindowVisibility = visibility == VISIBLE;
        mRequestedVisible = mWindowVisibility && mViewVisibility;
        updateWindow(false, false);
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


       SurfaceView類有三個用來描述可見性的成員變量mRequestedVisible、mWindowVisibility和mViewVisibility。其中,mWindowVisibility表示SurfaceView的宿主窗口的可見性,mViewVisibility表示SurfaceView自身的可見性。只有當mWindowVisibility和mViewVisibility的值均等於true的時候,mRequestedVisible的值才爲true,表示SurfaceView是可見的。

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

       最後,SurfaceView類的成員函數onWindowVisibilityChanged就會調用另外一個成員函數updateWindow來更新當前正在處理的SurfaceView。在更新的過程中,如果發現當前正在處理的SurfaceView還沒有創建繪圖表面,那麼就地請求WindowManagerService服務爲它創建一個。

       接下來,我們就繼續分析SurfaceView類的成員函數updateWindow的實現,以便可以瞭解SurfaceView的繪圖表面的創建過程。

       Step 8. SurfaceView.updateWindow


public class SurfaceView extends View {
    ......
    final Surface mSurface = new Surface();
    ......
    MyWindow mWindow;
    .....
    int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
    ......
    int mRequestedType = -1;
    ......
    private void updateWindow(boolean force, boolean redrawNeeded) {
        if (!mHaveFrame) {
            return;
        }
        ......
        int myWidth = mRequestedWidth;
        if (myWidth <= 0) myWidth = getWidth();
        int myHeight = mRequestedHeight;
        if (myHeight <= 0) myHeight = getHeight();
        getLocationInWindow(mLocation);
        final boolean creating = mWindow == null;
        final boolean formatChanged = mFormat != mRequestedFormat;
        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
        final boolean visibleChanged = mVisible != mRequestedVisible
                || mNewSurfaceNeeded;
        final boolean typeChanged = mType != mRequestedType;
        if (force || creating || formatChanged || sizeChanged || visibleChanged
            || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
            ......
            try {
                final boolean visible = mVisible = mRequestedVisible;
                mLeft = mLocation[0];
                mTop = mLocation[1];
                mWidth = myWidth;
                mHeight = myHeight;
                mFormat = mRequestedFormat;
                mType = mRequestedType;
                ......
                // Places the window relative
                mLayout.x = mLeft;
                mLayout.y = mTop;
                mLayout.width = getWidth();
                mLayout.height = getHeight();
                ......
                mLayout.memoryType = mRequestedType;
                if (mWindow == null) {
                    mWindow = new MyWindow(this);
                    mLayout.type = mWindowType;
                    ......
                    mSession.addWithoutInputChannel(mWindow, mLayout,
                            mVisible ? VISIBLE : GONE, mContentInsets);
                }
                ......
                mSurfaceLock.lock();
                try {
                    ......
                    final int relayoutResult = mSession.relayout(
                        mWindow, mLayout, mWidth, mHeight,
                            visible ? VISIBLE : GONE, false, mWinFrame, mContentInsets,
                            mVisibleInsets, mConfiguration, mSurface);
                    ......
                } finally {
                    mSurfaceLock.unlock();
                }
                ......
            } catch (RemoteException ex) {
            }
            .....
        }
    }
    ......
}


       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

       在分析SurfaceView類的成員函數updateWindow的實現之前,我們首先介紹一些相關的成員變量的含義,其中,mSurface、mWindow、mWindowType和mRequestedType這四個成員變量是最重要的。

       SurfaceView類的成員變量mSurface指向的是一個Surface對象,這個Surface對象描述的便是SurfaceView專有的繪圖表面。對於一般的視圖來說,例如,TextView或者Button,它們是沒有專有的繪圖表面的,而是與專宿主窗口共享同一個繪圖表面,因此,它們就不會像SurfaceView一樣,有一個專門的類型爲Surface的成員變量來描述自己的繪圖表面。

       在前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文提到,每一個Activity窗口都關聯有一個W對象。這個W對象是一個實現了IWindow接口的Binder本地對象,它是用來傳遞給WindowManagerService服務的,以便WindowManagerService服務可以通過它來和它所關聯的Activity窗口通信。例如,WindowManagerService服務通過這個W對象來通知它所關聯的Activity窗口的大小或者可見性發生變化了。同時,這個W對象還用來在WindowManagerService服務這一側唯一地標誌一個窗口,也就是說,WindowManagerService服務會爲這個W對象創建一個WindowState對象。

       SurfaceView類的成員變量mWindow指向的是一個MyWindow對象。MyWindow類是從BaseIWindow類繼承下來的,後者與W類一樣,實現了IWindow接口。也就是說,每一個SurfaceView都關聯有一個實現了IWindow接口的Binder本地對象,就如第一個Activity窗口都關聯有一個實現了IWindow接口的W對象一樣。從這裏我們就可以推斷出,每一個SurfaceView在WindowManagerService服務這一側都對應有一個WindowState對象。從這一點來看,WindowManagerService服務認爲Activity窗口和SurfaceView的地位是一樣的,即認爲它們都是一個窗口,並且具有繪圖表面。接下來我們就會通過SurfaceView類的成員函數updateWindow的實現來證實這個推斷。

       SurfaceView類的成員變量mWindowType描述的是SurfaceView的窗口類型,它的默認值等於TYPE_APPLICATION_MEDIA。也就是說,我們在創建一個SurfaceView的時候,默認是用來顯示多媒體的,例如,用來顯示視頻。SurfaceView還有另外一個窗口類型TYPE_APPLICATION_MEDIA_OVERLAY,它是用來在視頻上面顯示一個Overlay的,這個Overlay可以用來顯示視字幕等信息。

       我們假設一個Activity窗口嵌入有兩個SurfaceView,其中一個SurfaceView的窗口類型爲TYPE_APPLICATION_MEDIA,另外一個SurfaceView的窗口類型爲TYPE_APPLICATION_MEDIA_OVERLAY,那麼在WindowManagerService服務這一側就會對應有三個WindowState對象,其中,用來描述SurfaceView的WindowState對象是附加在用來描述Activity窗口的WindowState對象上的。從前面Android窗口管理服務WindowManagerService計算窗口Z軸位置的過程分析一文可以知道,如果一個WindowState對象所描述的窗口的類型爲TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,那麼它就會位於它所附加在的窗口的下面。也就是說,類型爲TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY的窗口的Z軸位置是小於它所附加在的窗口的Z軸位置的。同時,如果一個窗口同時附加有類型爲TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的兩個窗口,那麼類型爲TYPE_APPLICATION_MEDIA_OVERLAY的窗口的Z軸大於類型爲TYPE_APPLICATION_MEDIA的窗口的Z軸位置。

       從上面的描述就可以得出一個結論:如果一個Activity窗口嵌入有兩個類型分別爲TYPE_APPLICATION_MEDIA和TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView,那麼該Activity窗口的Z軸位置大於類型爲TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z軸位置,而類型爲TYPE_APPLICATION_MEDIA_OVERLAY的SurfaceView的Z軸位置又大於類型爲TYPE_APPLICATION_MEDIA的窗口的Z軸位置。

       注意,我們在創建了一個SurfaceView之後,可以調用它的成員函數setZOrderMediaOverlay、setZOrderOnTop或者setWindowType來修改該SurfaceView的窗口類型,也就是修改該SurfaceView的成員變量mWindowType的值。

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

       當一個SurfaceView的繪圖表面的類型等於SURFACE_TYPE_NORMAL的時候,就表示該SurfaceView的繪圖表面所使用的內存是一塊普通的內存。一般來說,這塊內存是由SurfaceFlinger服務來分配的,我們可以在應用程序內部自由地訪問它,即可以在它上面填充任意的UI數據,然後交給SurfaceFlinger服務來合成,並且顯示在屏幕上。在這種情況下,SurfaceFlinger服務使用一個Layer對象來描述該SurfaceView的繪圖表面。

       當一個SurfaceView的繪圖表面的類型等於SURFACE_TYPE_PUSH_BUFFERS的時候,就表示該SurfaceView的繪圖表面所使用的內存不是由SurfaceFlinger服務分配的,因而我們不能夠在應用程序內部對它進行操作。例如,當一個SurfaceView是用來顯示攝像頭預覽或者視頻播放的時候,我們就會將它的繪圖表面的類型設置爲SURFACE_TYPE_PUSH_BUFFERS,這樣攝像頭服務或者視頻播放服務就會爲該SurfaceView繪圖表面創建一塊內存,並且將採集的預覽圖像數據或者視頻幀數據源源不斷地填充到該內存中去。注意,這塊內存有可能是來自專用的硬件的,例如,它可能是來自視頻卡的。在這種情況下,SurfaceFlinger服務使用一個LayerBuffer對象來描述該SurfaceView的繪圖表面。

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

       注意,我們在創建了一個SurfaceView之後,可以調用它的成員函數getHolder獲得一個SurfaceHolder對象,然後再調用該SurfaceHolder對象的成員函數setType來修改該SurfaceView的繪圖表面的類型,即修改該SurfaceView的成員變量mRequestedType的值。

       介紹完成SurfaceView類的成員變量mSurface、mWindow、mWindowType和mRequestedType的含義之後,我們再介紹其它幾個接下來要用到的其它成員變量的含義:

--mHaveFrame,用來描述SurfaceView的宿主窗口的大小是否已經計算好了。只有當宿主窗口的大小計算之後,SurfaceView纔可以更新自己的窗口。

--mRequestedWidth,用來描述SurfaceView最後一次被請求的寬度。

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

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

--mNewSurfaceNeeded,用來描述SurfaceView是否需要新創建一個繪圖表面。

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

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

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

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

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

--mReportDrawNeeded,用來描述SurfaceView是否被WindowManagerService服務通知執行一次UI繪製操作。

--mLayout,指向的是一個WindowManager.LayoutParams對象,用來傳遞SurfaceView的佈局參數以及屬性值給WindowManagerService服務,以便WindowManagerService服務可以正確地維護它的狀態。

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

        (1). 判斷成員變量mHaveFrame的值是否等於false。如果是的話,那麼就說明現在還不是時候爲SurfaceView創建繪圖表面面,因爲它的宿主窗口還沒有準備就緒。

        (2). 獲得SurfaceView當前要使用的寬度和高度,並且保存在變量myWidth和myHeight中。注意,如果SurfaceView沒有被請求設置寬度或者高度,那麼就通過調用父類View的成員函數getWidth和getHeight來獲得它默認所使用的寬度和高度。

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

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

            --SurfaceView的繪圖表面是否還未創建,即成員變量mWindow的值是否等於null;

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

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

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

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

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

            --SurfaceView是否被WindowManagerService服務通知執行一次UI更新操作,即成員變量mUpdateWindowNeeded的值是否等於true;

            --SurfaceView是否被WindowManagerService服務通知執行一次UI繪製操作,即成員變量mReportDrawNeeded的值是否等於true;

            --SurfaceView類的成員函數updateWindow是否被調用者強制要求刷新或者繪製SurfaceView,即參數force或者redrawNeeded的值是否等於true。

            只要上述條件之一成立,那麼SurfaceView類的成員函數updateWindow就需要對SurfaceView的各種信息進行更新,即執行以下第5步至第7步操作。

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

        (6). 檢查成員變量mWindow的值是否等於null。如果等於null的話,那麼就說明該SurfaceView還沒有增加到WindowManagerService服務中去。在這種情況下,就會創建一個MyWindow對象保存在該成員變量中,並且調用成員變量mSession所描述的一個Binder代理對象的成員函數addWithoutInputChannel來將該MyWindow對象傳遞給WindowManagerService服務。在前面的Step 4中提到,SurfaceView類的成員變量mSession指向的是一個實現了IWindowSession接口的Binder代理對象,該Binder代理對象引用的是運行在WindowManagerService服務這一側的一個Session對象。Session類的成員函數addWithoutInputChannel與另外一個成員函數add的實現是類似的,它們都是用來在WindowManagerService服務內部爲指定的窗口增加一個WindowState對象,具體可以參考前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文。不過,Session類的成員函數addWithoutInputChannel只是在WindowManagerService服務內部爲指定的窗口增加一個WindowState對象,而Session類的成員函數add除了會在WindowManagerService服務內部爲指定的窗口增加一個WindowState對象之外,還會爲該窗口創建一個用來接收用戶輸入的通道,具體可以參考Android應用程序鍵盤(Keyboard)消息處理機制分析一文。

       (7). 調用成員變量mSession所描述的一個Binder代理對象的成員函數relayout來請求WindowManagerService服務對SurfaceView的UI進行佈局。從前面Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析一文可以知道,WindowManagerService服務在對一個窗口進行佈局的時候,如果發現該窗口的繪製表面還未創建,或者需要需要重新創建,那麼就會爲請求SurfaceFlinger服務爲該窗口創建一個新的繪圖表面,並且將該繪圖表面返回來給調用者。在我們這個情景中,WindowManagerService服務返回來的繪圖表面就會保存在成員變量mSurface。注意,這一步由於可能會修改SurfaceView的繪圖表面,即修改成員變量mSurface的指向的一個Surface對象的內容,因此,就需要在獲得成員變量mSurfaceLock所描述的一個鎖的情況下執行,避免其它線程同時修改該繪圖表面的內容,這是因爲我們可能會使用一個獨立的線程來來繪製SurfaceView的UI。

        執行完成上述步驟之後,SurfaceView的繪圖表面的創建操作就執行完成了,而當SurfaceView有了繪圖表面之後,我們就可以使用獨立的線程來繪製它的UI了,不過,在繪製之前,我們還需要在SurfaceView的宿主窗口上挖一個洞,以便繪製出來的UI不會被擋住。

        2. SurfaceView的挖洞過程

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

       從SurfaceView的繪圖表面的創建過程可以知道,SurfaceView在被附加到宿主窗口之上的時候,會請求在宿主窗口上設置透明區域,而每當其宿主窗口刷新自己的UI的時候,就會將所有嵌入在它裏面的SurfaceView所設置的透明區域收集起來,然後再通知WindowManagerService服務爲其設置一個總的透明區域。

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


圖3 SurfaceView的挖洞過程

        這個過程可以分爲6個步驟,接下來我們就詳細分析每一個步驟。

        Step 1. SurfaceView.onAttachedToWindow

public class SurfaceView extends View {
    ......
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mParent.requestTransparentRegion(this);
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


       SurfaceView類的成員變量mParent是從父類View繼承下來的,用來描述當前正在處理的SurfaceView的父視圖。我們假設當前正在處理的SurfaceView的父視圖就爲其宿主窗口的頂層視圖,因此,接下來SurfaceView類的成員函數onAttachedToWindow就會調用DecorView類的成員函數requestTransparentRegion來請求在宿主窗口之上挖一個洞。

       DecorView類的成員函數requestTransparentRegion是從父類ViewGroup繼承下來的,因此,接下來我們就繼續分析ViewGroup類的成員函數requestTransparentRegion的實現。

       Step 2. ViewGroup.requestTransparentRegion

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ......
    public void requestTransparentRegion(View child) {
        if (child != null) {
            child.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
            if (mParent != null) {
                mParent.requestTransparentRegion(this);
            }
        }
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。


       參數child描述的便是要在宿主窗口設置透明區域的SurfaceView,ViewGroup類的成員函數requestTransparentRegion首先將它的成員變量mPrivateFlags的值的View.REQUEST_TRANSPARENT_REGIONS位設置爲1,表示它要在宿主窗口上設置透明區域,接着再調用從父類View繼承下來的成員變量mParent所指向的一個視圖容器的成員函數requestTransparentRegion來繼續向上請求設置透明區域,這個過程會一直持續到當前正在處理的視圖容器爲窗口的頂層視圖爲止。

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

       Step 3. ViewRoot.requestTransparentRegion


public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {
    ......
    public void requestTransparentRegion(View child) {
        // the test below should not fail unless someone is messing with us
        checkThread();
        if (mView == child) {
            mView.mPrivateFlags |= View.REQUEST_TRANSPARENT_REGIONS;
            // Need to make sure we re-evaluate the window attributes next
            // time around, to ensure the window has the correct format.
            mWindowAttributesChanged = true;
            requestLayout();
        }
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。


       ViewRoot類的成員函數requestTransparentRegion首先調用另外一個成員函數checkThread來檢查當前執行的線程是否是應用程序的主線程,如果不是的話,那麼就會拋出一個類型爲CalledFromWrongThreadException的異常。

       通過了上面的檢查之後,ViewRoot類的成員函數requestTransparentRegion再檢查參數child所描述的視圖是否就是當前正在處理的ViewRoot對象所關聯的窗口的頂層視圖,即檢查它與ViewRoot類的成員變量mView是否是指向同一個View對象。由於一個ViewRoot對象有且僅有一個子視圖,因此,如果上述檢查不通過的話,那麼就說明調用者正在非法調用ViewRoot類的成員函數requestTransparentRegion來設置透明區域。

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

       當一個窗口被請求設置了一塊透明區域之後,它的窗口屬性就發生變化了,因此,這時候除了要將與它所關聯的一個ViewRoot對象的成員變量mWindowAttributesChanged的值設置爲true之外,還要調用該ViewRoot對象的成員函數requestLayout來請求刷新一下窗口的UI,即請求對窗口的UI進行重新佈局和繪製。

       從前面Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析一文可以知道,ViewRoot類的成員函數requestLayout最終會調用到另外一個成員函數performTraversals來實際執行刷新窗口UI的操作。ViewRoot類的成員函數performTraversals在刷新窗口UI的過程中,就會將嵌入在它裏面的SurfaceView所要設置的透明區域收集起來,以便可以請求WindowManagerService將這塊透明區域設置到它的繪圖表面上去。

       接下來,我們就繼續分析ViewRoot類的成員函數performTraversals的實現,以便可以繼續瞭解SurfaceView的挖洞過程。

       Step 4. ViewRoot.performTraversals


public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {
    ......
    private void performTraversals() {
        ......
        // cache mView since it is used so much below...
        final View host = mView;
        ......
        final boolean didLayout = mLayoutRequested;
        ......
        if (didLayout) {
            ......
            host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
            ......
            if ((host.mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) != 0) {
                // start out transparent
                // TODO: AVOID THAT CALL BY CACHING THE RESULT?
                host.getLocationInWindow(mTmpLocation);
                mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                        mTmpLocation[0] + host.mRight - host.mLeft,
                        mTmpLocation[1] + host.mBottom - host.mTop);
                host.gatherTransparentRegion(mTransparentRegion);
                ......
                if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                    mPreviousTransparentRegion.set(mTransparentRegion);
                    // reconfigure window manager
                    try {
                        sWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                    } catch (RemoteException e) {
                    }
                }
            }
            ......
        }
                                             
        ......
        boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
        if (!cancelDraw && !newSurface) {
            ......
            draw(fullRedrawNeeded);
            ......
        }
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。

       ViewRoot類的成員函數performTraversals的具體實現可以參考前面Android應用程序窗口(Activity)實現框架簡要介紹和學習計劃這個系列的文章以及Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析一文,這裏我們只關注窗口收集透明區域的邏輯。

       ViewRoot類的成員函數performTraversals是在窗口的UI佈局完成之後,並且在窗口的UI繪製之前,收集嵌入在它裏面的SurfaceView所設置的透明區域的,這是因爲窗口的UI佈局完成之後,各個子視圖的大小和位置才能確定下來,這樣SurfaceView才知道自己要設置的透明區域的位置和大小。

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

       (1). 計算頂層視圖的位置和大小,即計算頂層視圖所佔據的區域。

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

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

       (4). 檢查ViewRoot類的成員變量mTransparentRegion和mPreviousTransparentRegion所描述的區域是否相等。如果不相等的話,那麼就說明窗口的透明區域發生了變化,這時候就需要調用ViewRoot類的的靜態成員變量sWindowSession所描述的一個Binder代理對象的成員函數setTransparentRegion通知WindowManagerService爲窗口設置由成員變量mTransparentRegion所指定的透明區域。

       其中,第(3)步是通過調用變量host所描述的一個DecorView對象的成員函數gatherTransparentRegion來實現的。 DecorView類的成員函數gatherTransparentRegion是從父類ViewGroup繼承下來的,因此,接下來我們就繼續分析ViewGroup類的成員函數gatherTransparentRegion的實現,以便可以瞭解SurfaceView的挖洞過程。

       Step 5. ViewGroup.gatherTransparentRegion


public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    ......
    @Override
    public boolean gatherTransparentRegion(Region region) {
        // If no transparent regions requested, we are always opaque.
        final boolean meOpaque = (mPrivateFlags & View.REQUEST_TRANSPARENT_REGIONS) == 0;
        if (meOpaque && region == null) {
            // The caller doesn't care about the region, so stop now.
            return true;
        }
        super.gatherTransparentRegion(region);
        final View[] children = mChildren;
        final int count = mChildrenCount;
        boolean noneOfTheChildrenAreTransparent = true;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                if (!child.gatherTransparentRegion(region)) {
                    noneOfTheChildrenAreTransparent = false;
                }
            }
        }
        return meOpaque || noneOfTheChildrenAreTransparent;
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/ViewGroup.java中。


       ViewGroup類的成員函數gatherTransparentRegion首先是檢查當前正在處理的視圖容器是否被請求設置透明區域,即檢查成員變量mPrivateFlags的值的 View.REQUEST_TRANSPARENT_REGIONS位是否等於1。如果不等於1,那麼就說明不用往下繼續收集窗口的透明區域了,因爲在這種情況下,當前正在處理的視圖容器及其子視圖都不可能設置有透明區域。另一方面,如果參數region的值等於null,那麼就說明調用者不關心當前正在處理的視圖容器的透明區域,而是關心它是透明的,還是不透明的。在上述兩種情況下,ViewGroup類的成員函數gatherTransparentRegion都不用進一步處理了。

       假設當前正在處理的視圖容器被請求設置有透明區域,並且參數region的值不等於null,那麼接下來ViewGroup類的成員函數gatherTransparentRegion就執行以下兩個操作:

       (1). 調用父類View的成員函數gatherTransparentRegion來檢查當前正在處理的視圖容器是否需要繪製。如果需要繪製的話,那麼就會將它所佔據的區域從參數region所佔據的區域移除,這是因爲參數region所描述的區域開始的時候是等於窗口的頂層視圖的大小的,也就是等於窗口的整個大小的。

       (2). 調用當前正在處理的視圖容器的每一個子視圖的成員函數gatherTransparentRegion來繼續往下收集透明區域。

        在接下來的Step 6中,我們再詳細分析當前正在處理的視圖容器的每一個子視圖的透明區域的收集過程,現在我們主要分析View類的成員函數gatherTransparentRegion的實現,如下所示:


public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
    ......
    public boolean gatherTransparentRegion(Region region) {
        final AttachInfo attachInfo = mAttachInfo;
        if (region != null && attachInfo != null) {
            final int pflags = mPrivateFlags;
            if ((pflags & SKIP_DRAW) == 0) {
                // The SKIP_DRAW flag IS NOT set, so this view draws. We need to
                // remove it from the transparent region.
                final int[] location = attachInfo.mTransparentLocation;
                getLocationInWindow(location);
                region.op(location[0], location[1], location[0] + mRight - mLeft,
                        location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
            } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) {
                // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
                // exists, so we remove the background drawable's non-transparent
                // parts from this transparent region.
                applyDrawableToTransparentRegion(mBGDrawable, region);
            }
        }
        return true;
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/View.java中。


       View類的成員函數gatherTransparentRegion首先是檢查當前正在處理的視圖的前景是否需要繪製,即檢查成員變量mPrivateFlags的值的SKIP_DRAW位是否等於0。如果等於0的話,那麼就說明當前正在處理的視圖的前景是需要繪製的。在這種情況下,View類的成員函數gatherTransparentRegion就會將當前正在處理的視圖所佔據的區域從參數region所描述的區域中移除,以便當前正在處理的視圖的前景可以顯示出來。

       另一方面,如果當前正在處理的視圖的前景不需要繪製,但是該視圖的背景需要繪製,並且該視圖是設置有的,即成員變量mPrivateFlags的值的SKIP_DRAW位不等於0,並且成員變量mBGDrawable的值不等於null,這時候View類的成員函數gatherTransparentRegion就會調用另外一個成員函數applyDrawableToTransparentRegion來將該背景中的不透明區域從參數region所描述的區域中移除,以便當前正在處理的視圖的背景可以顯示出來。

       回到ViewGroup類的成員函數gatherTransparentRegion中,當前正在處理的視圖容器即爲當前正在處理的窗口的頂層視圖,前面我們已經假設它裏面嵌入有一個SurfaceView子視圖,因此,接下來就會收集該SurfaceView子視圖所設置的透明區域,這是通過調用SurfaceView類的成員函數gatherTransparentRegion來實現的。

       接下來,我們就繼續分析SurfaceView類的成員函數gatherTransparentRegion的實現,以便可以繼續瞭解SurfaceView的挖洞過程。

       Step 6. SurfaceView.gatherTransparentRegion


public class SurfaceView extends View {
    ......
    @Override
    public boolean gatherTransparentRegion(Region region) {
        if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
            return super.gatherTransparentRegion(region);
        }
        boolean opaque = true;
        if ((mPrivateFlags & SKIP_DRAW) == 0) {
            // this view draws, remove it from the transparent region
            opaque = super.gatherTransparentRegion(region);
        } else if (region != null) {
            int w = getWidth();
            int h = getHeight();
            if (w>0 && h>0) {
                getLocationInWindow(mLocation);
                // otherwise, punch a hole in the whole hierarchy
                int l = mLocation[0];
                int t = mLocation[1];
                region.op(l, t, l+w, t+h, Region.Op.UNION);
            }
        }
        if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
            opaque = false;
        }
        return opaque;
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


       SurfaceVie類的成員函數gatherTransparentRegion首先是檢查當前正在處理的SurfaceView是否是用作窗口面板的,即它的成員變量mWindowType的值是否等於WindowManager.LayoutParams.TYPE_APPLICATION_PANEL。如果等於的話,那麼就會調用父類View的成員函數gatherTransparentRegion來檢查該面板是否需要繪製。如果需要繪製,那麼就會將它所佔據的區域從參數region所描述的區域移除。

       假設當前正在處理的SurfaceView不是用作窗口面板的,那麼SurfaceVie類的成員函數gatherTransparentRegion接下來就會直接檢查當前正在處理的SurfaceView是否是需要在宿主窗口的繪圖表面上進行繪製,即檢查成員變量mPrivateFlags的值的SKIP_DRAW位是否等於1。如果需要的話,那麼也會調用父類View的成員函數gatherTransparentRegion來將它所佔據的區域從參數region所描述的區域移除。

       假設當前正在處理的SurfaceView不是用作窗口面板,並且也是不需要在宿主窗口的繪圖表面上進行繪製的,而參數region的值又不等於null,那麼SurfaceVie類的成員函數gatherTransparentRegion就會先計算好當前正在處理的SurfaceView所佔據的區域,然後再將該區域添加到參數region所描述的區域中去,這樣就可以得到窗口的一個新的透明區域。

       最後,SurfaceVie類的成員函數gatherTransparentRegion判斷當前正在處理的SurfaceView的繪圖表面的像素格式是否設置有透明值。如果有的話,那麼就會將變量opaque的值設置爲false,否則的話,變量opaque的值就保持爲true。變量opaque的值最終會返回給調用者,這樣調用者就可以知道當前正在處理的SurfaceView的繪圖表面是否是半透明的了。

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

       3. SurfaceView的繪製過程

       SurfaceView雖然具有獨立的繪圖表面,不過它仍然是宿主窗口的視圖結構中的一個結點,因此,它仍然是可以參與到宿主窗口的繪製流程中去的。從前面Android應用程序窗口(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文可以知道,窗口在繪製的過程中,每一個子視圖的成員函數draw或者dispatchDraw都會被調用到,以便它們可以繪製自己的UI。

       SurfaceView類的成員函數draw和dispatchDraw的實現如下所示:


public class SurfaceView extends View {
    ......
    @Override
    public void draw(Canvas canvas) {
        if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
            // draw() is not called when SKIP_DRAW is set
            if ((mPrivateFlags & SKIP_DRAW) == 0) {
                // punch a whole in the view-hierarchy below us
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
        }
        super.draw(canvas);
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
            // if SKIP_DRAW is cleared, draw() has already punched a hole
            if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
                // punch a whole in the view-hierarchy below us
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
        }
        // reposition ourselves where the surface is
        mHaveFrame = true;
        updateWindow(false, false);
        super.dispatchDraw(canvas);
    }
    ......
}

       這兩個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


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

       本來SurfaceView類的成員函數draw是用來將自己的UI繪製在宿主窗口的繪圖表面上的,但是這裏我們可以看到,如果當前正在處理的SurfaceView不是用作宿主窗口面板的時候,即其成員變量mWindowType的值不等於WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的時候,SurfaceView類的成員函數draw只是簡單地將它所佔據的區域繪製爲黑色。

       本來SurfaceView類的成員函數dispatchDraw是用來繪製SurfaceView的子視圖的,但是這裏我們同樣看到,如果當前正在處理的SurfaceView不是用作宿主窗口面板的時候,那麼SurfaceView類的成員函數dispatchDraw只是簡單地將它所佔據的區域繪製爲黑色,同時,它還會通過調用另外一個成員函數updateWindow更新自己的UI,實際上就是請求WindowManagerService服務對自己的UI進行佈局,以及創建繪圖表面,具體可以參考前面第1部分的內容。

       從SurfaceView類的成員函數draw和dispatchDraw的實現就可以看出,SurfaceView在其宿主窗口的繪圖表面上面所做的操作就是將自己所佔據的區域繪爲黑色,除此之外,就沒有其它更多的操作了,這是因爲SurfaceView的UI是要展現在它自己的繪圖表面上面的。接下來我們就分析如何在SurfaceView的繪圖表面上面進行UI繪製。

       從前面Android應用程序窗口(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文可以知道,如果要在一個繪圖表面進行UI繪製,那麼就順序執行以下的操作:

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

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

       (3). 將已經填充好了UI數據的畫布緩衝區提交給SurfaceFlinger服務,以便SurfaceFlinger服務可以它合成到屏幕上去。

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


SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);
SurfaceHolder sh = sv.getHolder();
Cavas canvas = sh.lockCanvas()
//Draw something on canvas
......
sh.unlockCanvasAndPost(canvas);

        注意,只有在一個SurfaceView的繪圖表面的類型不是SURFACE_TYPE_PUSH_BUFFERS的時候,我們纔可以自由地在上面繪製UI。我們使用SurfaceView來顯示攝像頭預覽或者播放視頻時,一般就是會將它的繪圖表面的類型設置爲SURFACE_TYPE_PUSH_BUFFERS。在這種情況下,SurfaceView的繪圖表面所使用的圖形緩衝區是完全由攝像頭服務或者視頻播放服務來提供的,因此,我們就不可以隨意地去訪問該圖形緩衝區,而是要由攝像頭服務或者視頻播放服務來訪問,因爲該圖形緩衝區有可能是在專門的硬件裏面分配的。

       另外還有一個地方需要注意的是,上述代碼既可以在應用程序的主線程中執行,也可以是在一個獨立的線程中執行。如果上述代碼是在應用程序的主線程中執行,那麼就需要保證它們不會佔用過多的時間,否則的話,就會導致應用程序的主線程不能及時地響應用戶輸入,從而導致ANR問題。

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


圖4 SurfaceView的繪製過程

      這個過程可以分爲5個步驟,接下來我們就詳細分析每一個步驟。

      Step 1. SurfaceView.getHolder

public class SurfaceView extends View {
    ......
    public SurfaceHolder getHolder() {
        return mSurfaceHolder;
    }
    ......
    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        ......
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


       SurfaceView類的成員函數getHolder的實現很簡單,它只是將成員變量mSurfaceHolder所指向的一個SurfaceHolder對象返回給調用者。

       Step 2. SurfaceHolder.lockCanvas


public class SurfaceView extends View {
    ......
    final ReentrantLock mSurfaceLock = new ReentrantLock();
    final Surface mSurface = new Surface();
    ......
    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        ......
        public Canvas lockCanvas() {
            return internalLockCanvas(null);
        }
        ......
        private final Canvas internalLockCanvas(Rect dirty) {
            if (mType == SURFACE_TYPE_PUSH_BUFFERS) {
                throw new BadSurfaceTypeException(
                        "Surface type is SURFACE_TYPE_PUSH_BUFFERS");
            }
            mSurfaceLock.lock();
            ......
            Canvas c = null;
            if (!mDrawingStopped && mWindow != null) {
                Rect frame = dirty != null ? dirty : mSurfaceFrame;
                try {
                    c = mSurface.lockCanvas(frame);
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }
            ......
            if (c != null) {
                mLastLockTime = SystemClock.uptimeMillis();
                return c;
            }
            ......
            mSurfaceLock.unlock();
            return null; 
        }
        ......        
    }
    ......
}


       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。

       SurfaceHolder類的成員函數lockCanvas通過調用另外一個成員函數internalLockCanvas來在當前正在處理的SurfaceView的繪圖表面上建立一塊畫布返回給調用者。

       SurfaceHolder類的成員函數internalLockCanvas首先是判斷當前正在處理的SurfaceView的繪圖表面的類型是否是SURFACE_TYPE_PUSH_BUFFERS,如果是的話,那麼就會拋出一個類型爲BadSurfaceTypeException的異常,原因如前面所述。

       由於接下來SurfaceHolder類的成員函數internalLockCanvas要在當前正在處理的SurfaceView的繪圖表面上建立一塊畫布,並且返回給調用者訪問,而這塊畫布不是線程安全的,也就是說它不能同時被多個線程訪問,因此,就需要對當前正在處理的SurfaceView的繪圖表面進行鎖保護,這是通過它的鎖定它的成員變量mSurfaceLock所指向的一個ReentrantLock對象來實現的。

       注意,如果當前正在處理的SurfaceView的成員變量mWindow的值等於null,那麼就說明它的繪圖表面還沒有創建好,這時候就無法創建一塊畫布返回給調用者。同時,如果當前正在處理的SurfaceView的繪圖表面已經創建好,但是該SurfaceView當前是處於停止繪製的狀態,即它的成員變量mDrawingStopped的值等於true,那麼也是無法創建一塊畫布返回給調用者的。

       假設當前正在處理的SurfaceView的繪製表面已經創建好,並且它不是處於停止繪製的狀態,那麼SurfaceHolder類的成員函數internalLockCanvas就會通過調用該SurfaceView的成員變量mSurface所指向的一個Surface對象的成員函數lockCanvas來創建一塊畫布,並且返回給調用者。注意,在這種情況下,當前正在處理的SurfaceView的繪製表面還是處於鎖定狀態的。

       另一方面,如果SurfaceHolder類的成員函數internalLockCanvas不能成功地在當前正在處理的SurfaceView的繪製表面上創建一塊畫布,即變量c的值等於null,那麼SurfaceHolder類的成員函數internalLockCanvas在返回一個null值調用者之前,還會將該SurfaceView的繪製表面就會解鎖。

       從前面第1部分的內容可以知道,SurfaceView類的成員變量mSurface描述的是就是SurfaceView的專有繪圖表面,接下來我們就繼續分析它所指向的一個Surface對象的成員函數lockCanvas的實現,以便可以瞭解SurfaceView的畫布的創建過程。

       Step 3. Surface.lockCanvas

       Surface類的成員函數lockCanvas的具體實現可以參考前面Android應用程序窗口(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文,它大致就是通過JNI方法來在當前正在處理的繪圖表面上獲得一個圖形緩衝區,並且將這個圖形繪衝區封裝在一塊類型爲Canvas的畫布中返回給調用者使用。

       調用者獲得了一塊類型爲Canvas的畫布之後,就可以調用Canvas類所提供的繪圖函數來繪製任意的UI了,例如,調用Canvas類的成員函數drawLine、drawRect和drawCircle可以分別用來畫直線、矩形和圓。

       調用者在畫布上繪製完成所需要的UI之後,就可以將這塊畫布的圖形繪衝區的UI數據提交給SurfaceFlinger服務來處理了,這是通過調用SurfaceHolder類的成員函數unlockCanvasAndPost來實現的。

       Step 4. SurfaceHolder.unlockCanvasAndPost

public class SurfaceView extends View {
    ......
    final ReentrantLock mSurfaceLock = new ReentrantLock();
    final Surface mSurface = new Surface();
    ......
    private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        ......
        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            mSurfaceLock.unlock();
        }
        ......        
    }
    ......
}

       這個函數定義在文件frameworks/base/core/java/android/view/SurfaceView.java中。


       SurfaceHolder類的成員函數unlockCanvasAndPost是通過調用當前正在處理的SurfaceView的成員變量mSurface所指向的一個Surface對象的成員函數unlockCanvasAndPost來將參數canvas所描述的一塊畫布的圖形緩衝區提交給SurfaceFlinger服務處理的。

      提交完成參數canvas所描述的一塊畫布的圖形緩衝區給SurfaceFlinger服務之後,SurfaceHolder類的成員函數unlockCanvasAndPost再調用當前正在處理的SurfaceView的成員變量mSurfaceLock所指向的一個ReentrantLock對象的成員函數unlock來解鎖當前正在處理的SurfaceView的繪圖表面,因爲在前面的Step 2中,我們曾經將該繪圖表面鎖住了。

       接下來,我們就繼續分析Surface類的成員函數unlockCanvasAndPost的實現,以便可以瞭解SurfaceView的繪製過程。

       Step 5. Surface.unlockCanvasAndPost

       Surface類的成員函數unlockCanvasAndPost的具體實現同樣是可以參考前面Android應用程序窗口(Activity)的測量(Measure)、佈局(Layout)和繪製(Draw)過程分析一文,它大致就是將在前面的Step 3中所獲得的一個圖形緩衝區提交給SurfaceFlinger服務,以便SurfaceFlinger服務可以在合適的時候將該圖形緩衝區合成到屏幕上去顯示,這樣就可以將對應的SurfaceView的UI展現出來了。

       至此,我們就分析完成SurfaceView的繪製過程了,整個SurfaceView的實現原理也就分析完了。總結來說,就是SurfaceView有以下三個特點:

       A. 具有獨立的繪圖表面;

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

       C. 它的UI繪製可以在獨立的線程中進行,這樣就可以進行復雜的UI繪製,並且不會影響應用程序的主線程響應用戶輸入。

老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關注!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章