天天看點

Android視圖SurfaceView的實作原理分析

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

《Android系統源代碼情景分析》一書正在進擊的程式員網(http://0xcc0xcd.com)中連載,點選進入!

        在前面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所示:

Android視圖SurfaceView的實作原理分析

圖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所示:

Android視圖SurfaceView的實作原理分析

圖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所示:

Android視圖SurfaceView的實作原理分析

圖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中。

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

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

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

        最後,SurfaceView類的成員函數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的繪制過程,如下所示:

Android視圖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,歡迎關注!