天天看點

android之surfaceview畫圖

在前文中,我們分析了應用程式視窗連接配接到windowmanagerservice服務的過程。在這個過程中,windowmanagerservice服務會為應用程式視窗建立過一個到surfaceflinger服務的連接配接。有了這個連接配接之後,windowmanagerservice服務就可以為應用程式視窗建立繪圖表面了,以便可以用來渲染視窗的ui。在本文中,我們就詳細分析應用程式視窗的繪圖表面的建立過程。

        對于在java層實作的android應用程式視窗來說,它也需要請求surfaceflinger服務為它建立繪圖表面,這個繪圖表面使用一個surface對象來描述。由于在java層實作的android應用程式視窗還要接受windowmanagerservice服務管理,是以,它的繪圖表面的建立流程就會比在c++層實作的應用程式視窗複雜一些。具體來說,就是在在java層實作的android應用程式視窗的繪圖表面是通過兩個surface對象來描述,一個是在應用程式程序這一側建立的,另一個是在windowmanagerservice服務這一側建立的,它們對應于surfaceflinger服務這一側的同一個layer對象,如圖1所示:

android之surfaceview畫圖

圖1 應用程式視窗的繪圖表面的模型圖

        在應用程式程序這一側,每一個應用程式視窗,即每一個activity元件,都有一個關聯的surface對象,這個surface對象是保在在一個關聯的viewroot對象的成員變量msurface中的,如圖2所示:

android之surfaceview畫圖

圖2 應用程式視窗在應用程式程序這一側的surface的實作

        在windowmanagerservice服務這一側,每一個應用程式視窗,即每一個activity元件,都有一個對應的windowstate對象,這個windowstate對象的成員變量msurface同樣是指向了一個surface對象,如圖3所示:

android之surfaceview畫圖

圖3 應用程式視窗在windowmanagerservice服務這一側的surface的實作

       一個應用程式視窗分别位于應用程式程序和windowmanagerservice服務中的兩個surface對象有什麼差別呢?雖然它們都是用來操作位于surfaceflinger服務中的同一個layer對象的,不過,它們的操作方式卻不一樣。具體來說,就是位于應用程式程序這一側的surface對象負責繪制應用程式視窗的ui,即往應用程式視窗的圖形緩沖區填充ui資料,而位于windowmanagerservice服務這一側的surface對象負責設定應用程式視窗的屬性,例如位置、大小等屬性。這兩種不同的操作方式分别是通過c++層的surface對象和surfacecontrol對象來完成的,是以,位于應用程式程序和windowmanagerservice服務中的兩個surface對象的用法是有差別的。之是以會有這樣的差別,是因為繪制應用程式視窗是獨立的,由應用程式程序來完即可,而設定應用程式視窗的屬性卻需要全局考慮,即需要由windowmanagerservice服務來統籌安排,例如,一個應用程式視窗的z軸坐标大小要考慮它到的視窗類型以及它與系統中的其它視窗的關系。

        說到這裡,另外一個問題又來了,由于一個應用程式視窗對應有兩個surface對象,那麼它們是如何建立出來的呢?簡單地說,就是按照以下步驟來建立:

       1. 應用程式程序請求windowmanagerservice服務為一個應用程式視窗建立一個surface對象;

       2. windowmanagerservice服務請求surfaceflinger服務建立一個layer對象,并且獲得一個isurface接口;

       3. windowmanagerservice服務将獲得的isurface接口儲存在其内部的一個surface對象中,并且将該isurface接口傳回給應用程式程序;

       4. 應用程式程序得到windowmanagerservice服務傳回的isurface接口之後,再将其封裝成其内部的另外一個surface對象中。

android之surfaceview畫圖

圖4 應用程式視窗的繪圖表面的建立過程

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

        step 1. viewroot.requestlayout

android之surfaceview畫圖

public final class viewroot extends handler implements viewparent,  

        view.attachinfo.callbacks {  

    ......  

    boolean mlayoutrequested;  

    public void requestlayout() {  

        checkthread();  

        mlayoutrequested = true;  

        scheduletraversals();  

    }  

}  

        這個函數定義在檔案frameworks/base/core/java/android/view/viewroot.java中。

        viewroot類的成員函數requestlayout首先調用另外一個成員函數checkthread來檢查目前線程是否就是建立目前正在處理的viewroot對象的線程。如果不是的話,那麼viewroot類的成員函數checkthread就會抛出一個異常出來。viewroot類是從handler類繼承下來的,用來處理應用程式視窗的ui布局和渲染等消息。由于這些消息都是與ui相關的,是以它們就需要在ui線程中處理,這樣我們就可以推斷出目前正在處理的viewroot對象是要應用程式程序的ui線程中建立的。進一步地,我們就可以推斷出viewroot類的成員函數checkthread實際上就是用來檢查目前線程是否是應用程式程序的ui線程,如果不是的話,它就會抛出一個異常出來。

        通過了上述檢查之後,viewroot類的成員函數requestlayout首先将其成員變量mlayoutrequested的值設定為true,表示應用程式程序的ui線程正在被請求執行一個ui布局操作,接着再調用另外一個成員函數scheduletraversals來繼續執行ui布局的操作。

        step 2. viewroot.scheduletraversals

android之surfaceview畫圖

    boolean mtraversalscheduled;  

    public void scheduletraversals() {  

        if (!mtraversalscheduled) {  

            mtraversalscheduled = true;  

            sendemptymessage(do_traversal);  

        }  

        viewroot類的成員變量mtraversalscheduled用來表示應用程式程序的ui線程是否已經排程了一個do_traversal消息。如果已經排程了的話,它的值就會等于true。在這種情況下, viewroot類的成員函數scheduletraversals就什麼也不做,否則的話,它就會首先将成員變量mtraversalscheduled的值設定為true,然後再調用從父類handler繼承下來的成員函數sendemptymessage來往應用程式程序的ui線程發送一個do_traversal消息。

        這個類型為do_traversal的消息是由viewroot類的成員函數performtraversals來處理的,是以,接下來我們就繼續分析viewroot類的成員函數performtraversals的實作。

        step 3. viewroot.performtraversals

android之surfaceview畫圖

    view mview;  

    boolean mfirst;  

    boolean mfullredrawneeded;  

    private final surface msurface = new surface();  

    private void performtraversals() {  

        ......  

        final view host = mview;  

        mtraversalscheduled = false;  

        boolean fullredrawneeded = mfullredrawneeded;  

        boolean newsurface = false;  

        if (mlayoutrequested) {  

            ......  

            host.measure(childwidthmeasurespec, childheightmeasurespec);  

            .......  

        int relayoutresult = 0;  

        if (mfirst || windowshouldresize || insetschanged  

                || viewvisibilitychanged || params != null) {  

            boolean hadsurface = msurface.isvalid();  

            try {  

                ......  

                relayoutresult = relayoutwindow(params, viewvisibility, insetspending);  

                if (!hadsurface) {  

                    if (msurface.isvalid()) {  

                        ......  

                        newsurface = true;  

                        fullredrawneeded = true;  

                    }  

                }   

            } catch (remoteexception e) {  

            }  

        final boolean didlayout = mlayoutrequested;  

        if (didlayout) {  

            mlayoutrequested = false;  

            host.layout(0, 0, host.mmeasuredwidth, host.mmeasuredheight);  

        mfirst = false;  

        boolean canceldraw = attachinfo.mtreeobserver.dispatchonpredraw();  

        if (!canceldraw && !newsurface) {  

            mfullredrawneeded = false;  

            draw(fullredrawneeded);  

        } else {  

            // try again  

            scheduletraversals();  

        viewroot類的成員函數performtraversals的實作是相當複雜的,這裡我們分析它的實作架構,在以後的文章中,我們再詳細分析它的實作細節。

        在分析viewroot類的成員函數performtraversals的實作架構之前,我們首先了解viewroot類的以下五個成員變量:

        --mlayoutrequested:這是一個布爾變量,用來描述應用程式程序的ui線程是否需要正在被請求執行一個ui布局操作。

        --mfirst:這是一個布爾變量,用來描述應用程式程序的ui線程是否第一次處理一個應用程式視窗的ui。

        --mfullredrawneeded:這是一個布爾變量,用來描述應用程式程序的ui線程是否需要将一個應用程式視窗的全部區域都重新繪制。

        --msurface:它指向一個java層的surface對象,用來描述一個應用程式視窗的繪圖表面。

        注意,成員變量msurface所指向的surface對象在建立的時候,還沒有在c++層有一個關聯的surface對象,是以,這時候它描述的就是一個無效的繪圖表面。另外,這個surface對象在應用程式視窗運作的過程中,也會可能被銷毀,是以,這時候它描述的繪圖表面也會變得無效。在上述兩種情況中,我們都需要請求windowmanagerservice服務來為目前正在處理的應用程式視窗建立有一個有效的繪圖表面,以便可以在上面渲染ui。這個建立繪圖表面的過程正是本文所要關心的。

        了解了上述五個成員變量之後,我們就可以分析viewroot類的成員函數performtraversals的實作架構了,如下所示:

        1. 将成員變量mview和mfullredrawneeded的值分别儲存在本地變量host和fullredrawneeded中,并且将成員變量mtraversalscheduled的值設定為false,表示應用程式程序的ui線程中的do_traversal消息已經被處理了。

        2. 本地變量newsurface用來描述目前正在處理的應用程式視窗在本輪的do_traversal消息進行中是否新建立了一個繪圖表面,它的初始值為false。

        3. 如果成員變量mlayoutrequested的值等于true,那麼就表示應用程式程序的ui線程正在被請求對目前正在處理的應用程式視窗執行一個ui布局操作,是以,這時候就會調用本地變量host所描述的一個頂層視圖對象的成員函數measure來測量位于各個層次的ui控件的大小。

        4. 如果目前正在處理的應用程式視窗的ui是第一次被處理,即成員變量mfirst的值等于true,或者目前正在處理的應用程式視窗的大小發生了變化,即本地變量windowshouldresize的值等于true,或者目前正在處理的應用程式視窗的邊襯發生了變化,即本地變量insetschanged的值等于true,或者正在處理的應用程式視窗的可見性發生了變化,即本地變量viewvisibilitychanged的值等于true,或者正在處理的應用程式視窗的ui布局參數發生了變化,即本地變量params指向了一個windowmanager.layoutparams對象,那麼應用程式程序的ui線程就會調用另外一個成員函數relayoutwindow來請求windowmanagerservice服務重新布局系統中的所有視窗。windowmanagerservice服務在重新布局系統中的所有視窗的過程中,如果發現目前正在處理的應用程式視窗尚未具有一個有效的繪圖表面,那麼就會為它建立一個有效的繪圖表面,這一點是我們在本文中所要關注的。

        5. 應用程式程序的ui線程在調用viewroot類的成員函數relayoutwindow來請求windowmanagerservice服務重新布局系統中的所有視窗之前,會調用成員變量msurface所指向的一個surface對象的成員函數isvalid來判斷它描述的是否是一個有效的繪圖表面,并且将結果儲存在本地變量hadsurface中。

        6. 應用程式程序的ui線程在調用viewroot類的成員函數relayoutwindow來請求windowmanagerservice服務重新布局系統中的所有視窗之後,又會繼續調用成員變量msurface所指向的一個surface對象的成員函數isvalid來判斷它描述的是否是一個有效的繪圖表面。如果這時候成員變量msurface所指向的一個surface對象描述的是否是一個有效的繪圖表面,并且本地變量hadsurface的值等于false,那麼就說明windowmanagerservice服務為目前正在處理的應用程式視窗新建立了一個有效的繪圖表面,于是就會将本地變量newsurface和fullredrawneeded的值均修改為true。

        7. 應用程式程序的ui線程再次判斷mlayoutrequested的值是否等于true。如果等于的話,那麼就說明需要對目前正在處理的應用程式視窗的ui進行重新布局,這是通過調用本地變量host所描述的一個頂層視圖對象的成員函數layout來實作的。在對目前正在處理的應用程式視窗的ui進行重新布局之前,應用程式程序的ui線程會将成員變量mlayoutrequested的值設定為false,表示之前所請求的一個ui布局操作已經得到處理了。

        8. 應用程式程序的ui線程接下來就要開始對目前正在處理的應用程式視窗的ui進行重新繪制了,不過在重繪之前,會先詢問一下那些注冊到目前正在處理的應用程式視窗中的tree observer,即調用它們的成員函數dispatchonpredraw,看看它們是否需要取消接下來的重繪操作,這個詢問結果儲存在本地變量canceldraw中。

        9. 如果本地變量canceldraw的值等于false,并且本地變量newsurface的值也等于false,那麼就說明注冊到目前正在處理的應用程式視窗中的tree observer不要求取消目前的這次重繪操作,并且目前正在處理的應用程式視窗也沒有獲得一個新的繪圖表面。在這種情況下,應用程式程序的ui線程就會調用viewroot類的成員函數draw來對目前正在處理的應用程式視窗的ui進行重繪。在重繪之前,還會将viewroot類的成員變量mfullredrawneeded的值重置為false。

       10. 如果本地變量canceldraw的值等于true,或者本地變量newsurface的值等于true,那麼就說明注冊到目前正在處理的應用程式視窗中的tree observer要求取消目前的這次重繪操作,或者目前正在處理的應用程式視窗獲得了一個新的繪圖表面。在這兩種情況下,應用程式程序的ui線程就不能對目前正在處理的應用程式視窗的ui進行重繪了,而是要等到下一個do_traversal消息到來的時候,再進行重繪,以便使得目前正在處理的應用程式視窗的各項參數可以得到重新設定。下一個do_traversal消息需要馬上被排程,是以,應用程式程序的ui線程就會重新執行viewroot類的成員函數scheduletraversals。

       這樣,我們就分析完成viewroot類的成員函數performtraversals的實作架構了,接下來我們就繼續分析viewroot類的成員函數relayoutwindow的實作,以便可以看到目前正在處理的應用程式視窗的繪圖表面是如何建立的。

       step 4. viewroot.relayoutwindow

android之surfaceview畫圖

public final class viewroot extends handler implements viewparent,    

        view.attachinfo.callbacks {    

    ......    

    static iwindowsession swindowsession;    

    final w mwindow;  

    private final surface msurface = new surface();    

    private int relayoutwindow(windowmanager.layoutparams params, int viewvisibility,    

            boolean insetspending) throws remoteexception {    

        ......    

        int relayoutresult = swindowsession.relayout(    

                mwindow, params,    

                (int) (mview.mmeasuredwidth * appscale + 0.5f),    

                (int) (mview.mmeasuredheight * appscale + 0.5f),    

                viewvisibility, insetspending, mwinframe,    

                mpendingcontentinsets, mpendingvisibleinsets,    

                mpendingconfiguration, msurface);    

        return relayoutresult;    

    }    

}    

       這個函數定義在檔案frameworks/base/core/java/android/view/viewroot.java中。

       viewroot類的成員函數relayoutwindow調用靜态成員變量swindowsession所描述的一個實作了iwindowsession接口的binder代理對象的成員函數relayout來請求windowmanagerservice服務對成員變量mwindow所描述的一個應用程式視窗的ui進行重新布局,同時,還會将成員變量msurface所描述的一個surface對象傳遞給windowmanagerservice服務,以便windowmanagerservice服務可以根據需要來重新建立一個繪圖表面給成員變量mwindow所描述的一個應用程式視窗使用。

       實作了iwindowsession接口的binder代理對象是由iwindowsession.stub.proxy類來描述的,接下來我們就繼續分析它的成員函數relayout的實作。

       step 5. iwindowsession.stub.proxy.relayout

       iwindowsession接口是使用aidl語言來描述的,如下所示:

android之surfaceview畫圖

interface iwindowsession {  

    int relayout(iwindow window, in windowmanager.layoutparams attrs,  

            int requestedwidth, int requestedheight, int viewvisibility,  

            boolean insetspending, out rect outframe, out rect outcontentinsets,  

            out rect outvisibleinsets, out configuration outconfig,  

            out surface outsurface);  

        這個接口定義在frameworks/base/core/java/android/view/iwindowsession.aidl檔案中。

        使用aidl語言來描述的iwindowsession接口被編譯後,就會生成一個使用java語言來描述的iwindowsession.stub.proxy類,它的成員函數relayout的實作如下所示:

android之surfaceview畫圖

public interface iwindowsession extends android.os.iinterface  

{  

    public static abstract class stub extends android.os.binder implements android.view.iwindowsession  

    {  

         ......  

         private static class proxy implements android.view.iwindowsession  

         {  

             private android.os.ibinder mremote;  

             ......  

             public int relayout(android.view.iwindow window, android.view.windowmanager.layoutparams attrs,   

                   int requestedwidth, int requestedheight, int viewvisibility, boolean insetspending,   

                   android.graphics.rect outframe,   

                   android.graphics.rect outcontentinsets,   

                   android.graphics.rect outvisibleinsets,   

                   android.content.res.configuration outconfig,   

                   android.view.surface outsurface) throws android.os.remoteexception  

            {  

                android.os.parcel _data = android.os.parcel.obtain();  

                android.os.parcel _reply = android.os.parcel.obtain();  

                int _result;  

                try {  

                    _data.writeinterfacetoken(descriptor);  

                    _data.writestrongbinder((((window!=null))?(window.asbinder()):(null)));  

                    if ((attrs!=null)) {  

                        _data.writeint(1);  

                        attrs.writetoparcel(_data, 0);  

                    else {  

                        _data.writeint(0);  

                    _data.writeint(requestedwidth);  

                    _data.writeint(requestedheight);  

                    _data.writeint(viewvisibility);  

                    _data.writeint(((insetspending)?(1):(0)));  

                    mremote.transact(stub.transaction_relayout, _data, _reply, 0);  

                    _reply.readexception();  

                    _result = _reply.readint();  

                    if ((0!=_reply.readint())) {  

                        outframe.readfromparcel(_reply);  

                        outcontentinsets.readfromparcel(_reply);  

                        outvisibleinsets.readfromparcel(_reply);  

                        outconfig.readfromparcel(_reply);  

                        outsurface.readfromparcel(_reply);  

                } finally {  

                    _reply.recycle();  

                    _data.recycle();  

                }  

                return _result;  

        這個函數定義在檔案out/target/common/obj/java_libraries/framework_intermediates/src/core/java/android/view/iwindowsession.java中。

        iwindowsession.stub.proxy類的成員函數relayout首先将從前面傳進來的各個參數寫入到parcel對象_data中,接着再通過其成員變量mremote所描述的一個binder代理對象向運作在windowmanagerservice服務内部的一個session對象發送一個類型為transaction_relayout的程序間通信請求,其中,這個session對象是用來描述從目前應用程式程序到windowmanagerservice服務的一個連接配接的。

        當運作在windowmanagerservice服務内部的session對象處理完成目前應用程式程序發送過來的類型為transaction_relayout的程序間通信請求之後,就會将處理結果寫入到parcel對象_reply中,并且将這個parcel對象_reply傳回給目前應用程式程序處理。傳回結果包含了一系列與參數window所描述的應用程式視窗相關的參數,如下所示:

       1. 視窗的大小:最終儲存在輸出參數outframe中。

       2. 視窗的内容區域邊襯大小:最終儲存在輸出參數outcontentinsets中。

       3. 視窗的可見區域邊襯大小:最終儲存在輸出參數outvisibleinsets中。

       4. 視窗的配置資訊:最終儲存在輸出參數outconfig中。

       5. 視窗的繪圖表面:最終儲存在輸出參數outsurface中。

       這裡我們隻關注從windowmanagerservice服務傳回來的視窗繪圖表面是如何儲存到輸出參數outsurface中的,即關注surface類的成員函數readfromparcel的實作。從前面的調用過程可以知道,輸出參數outsurface描述的便是目前正在處理的應用程式視窗的繪圖表面,将windowmanagerservice服務傳回來的視窗繪圖表面儲存在它裡面,就相當于是為目前正在處理的應用程式視窗建立了一個繪圖表面。

       在分析surface類的成員函數readfromparcel的實作之前,我們先分析session類的成員函數relayout的實作,因為它是用來處理類型為transaction_relayout的程序間通信請求的。

       step 6. session.relayout

android之surfaceview畫圖

public class windowmanagerservice extends iwindowmanager.stub  

        implements watchdog.monitor {  

    private final class session extends iwindowsession.stub  

            implements ibinder.deathrecipient {  

        public int relayout(iwindow window, windowmanager.layoutparams attrs,  

                int requestedwidth, int requestedheight, int viewflags,  

                boolean insetspending, rect outframe, rect outcontentinsets,  

                rect outvisibleinsets, configuration outconfig, surface outsurface) {  

            //log.d(tag, ">>>>>> entered relayout from " + binder.getcallingpid());  

            int res = relayoutwindow(this, window, attrs,  

                    requestedwidth, requestedheight, viewflags, insetspending,  

                    outframe, outcontentinsets, outvisibleinsets, outconfig, outsurface);  

            //log.d(tag, "<<<<<< exiting relayout to " + binder.getcallingpid());  

            return res;  

        這個函數定義在檔案frameworks/base/services/java/com/android/server/windowmanagerservice.java中。

        session類的成員函數relayout調用了外部類windowmanagerservice的成員函數relayoutwindow來對參數參數window所描述的一個應用程式視窗的ui進行布局,接下來我們就繼續分析windowmanagerservice類的成員函數relayoutwindow的實作。

        step 7. windowmanagerservice.relayoutwindow

android之surfaceview畫圖

    public int relayoutwindow(session session, iwindow client,  

            windowmanager.layoutparams attrs, int requestedwidth,  

            int requestedheight, int viewvisibility, boolean insetspending,  

            rect outframe, rect outcontentinsets, rect outvisibleinsets,  

            configuration outconfig, surface outsurface) {  

        synchronized(mwindowmap) {  

            windowstate win = windowforclientlocked(session, client, false);  

            if (win == null) {  

                return 0;  

            if (viewvisibility == view.visible &&  

                    (win.mapptoken == null || !win.mapptoken.clienthidden)) {  

                    surface surface = win.createsurfacelocked();  

                    if (surface != null) {  

                        outsurface.copyfrom(surface);  

                    } else {  

                        // for some reason there isn't a surface.  clear the  

                        // caller's object so they see the same state.  

                        outsurface.release();  

                } catch (exception e) {  

                    ......  

                    return 0;  

        return (intouchmode ? windowmanagerimpl.relayout_in_touch_mode : 0)  

                | (displayed ? windowmanagerimpl.relayout_first_time : 0);  

        windowmanagerservice類的成員函數relayoutwindow的實作是相當複雜的,這裡我們隻關注與建立應用程式視窗的繪圖表面相關的代碼,在後面的文章中,我們再詳細分析它的實作。

        簡單來說,windowmanagerservice類的成員函數relayoutwindow根據應用程式程序傳遞過來的一系列資料來重新設定由參數client所描述的一個應用程式視窗的大小和可見性等資訊,而當一個應用程式視窗的大小或者可見性發生變化之後,系統中目前獲得焦點的視窗,以及輸入法視窗和桌面視窗等都可能會發生變化,而且也會對其它視窗産生影響,是以,這時候windowmanagerservice類的成員函數relayoutwindow就會對系統中的視窗的布局進行重新調整。對系統中的視窗的布局進行重新調整的過程是整個windowmanagerservice服務最為複雜和核心的内容,我們同樣是在後面的文章中再詳細分析。

        現在,我們就主要分析參數client所描述的一個應用程式視窗的繪圖表面的建立過程。

        windowmanagerservice類的成員函數relayoutwindow首先獲得與參數client所對應的一個windowstate對象win,這是通過調用windowmanagerservice類的成員函數windowforclientlocked來實作的。如果這個對應的windowstate對象win不存在,那麼就說明應用程式程序所請求處理的應用程式視窗不存在,這時候windowmanagerservice類的成員函數relayoutwindow就直接傳回一個0值給應用程式程序。

        windowmanagerservice類的成員函數relayoutwindow接下來判斷參數client所描述的一個應用程式視窗是否是可見的。一個視窗隻有在可見的情況下,windowmanagerservice服務才會為它建立一個繪圖表面。 一個視窗是否可見由以下兩個條件決定:

        1. 參數viewvisibility的值等于view.visible,表示應用程式程序請求将它設定為可見的。

        注意,當windowstate對象win的成員變量mapptoken等于null時,隻要滿足條件1就可以了,因為這時候參數client所描述的視窗不是一個activity元件視窗,它的可見性不像activity元件視窗一樣受到activity元件的可見性影響。

        我們假設參數client所描述的是一個應用程式視窗,并且這個應用程式視窗是可見的,那麼windowmanagerservice類的成員函數relayoutwindow接下來就會調用windowstate對象win的成員函數createsurfacelocked來為它建立一個繪圖表面。如果這個繪圖表面能建立成功,那麼windowmanagerservice類的成員函數relayoutwindow就會将它的内容拷貝到輸出參數outsource所描述的一個surface對象去,以便可以将它傳回給應用程式程序處理。另一方面,如果這個繪圖表面不能建立成功,那麼windowmanagerservice類的成員函數relayoutwindow就會将輸出參數outsource所描述的一個surface對象的内容釋放掉,以便應用程式程序知道該surface對象所描述的繪圖表面已經失效了。

        接下來,我們就繼續分析windowstate類的成員函數createsurfacelocked的實作,以便可以了解一個應用程式視窗的繪圖表面的建立過程。

        step 8. windowstate.createsurfacelocked

android之surfaceview畫圖

    private final class windowstate implements windowmanagerpolicy.windowstate {  

        surface msurface;  

        surface createsurfacelocked() {  

            if (msurface == null) {  

                mreportdestroysurface = false;  

                msurfacependingdestroy = false;  

                mdrawpending = true;  

                mcommitdrawpending = false;  

                mreadytoshow = false;  

                if (mapptoken != null) {  

                    mapptoken.alldrawn = false;  

                int flags = 0;  

                if (mattrs.memorytype == memory_type_push_buffers) {  

                    flags |= surface.push_buffers;  

                if ((mattrs.flags&windowmanager.layoutparams.flag_secure) != 0) {  

                    flags |= surface.secure;  

                int w = mframe.width();  

                int h = mframe.height();  

                if ((mattrs.flags & layoutparams.flag_scaled) != 0) {  

                    // for a scaled surface, we always want the requested  

                    // size.  

                    w = mrequestedwidth;  

                    h = mrequestedheight;  

                // something is wrong and surfaceflinger will not like this,  

                // try to revert to sane values  

                if (w <= 0) w = 1;  

                if (h <= 0) h = 1;  

                msurfaceshown = false;  

                msurfacelayer = 0;  

                msurfacealpha = 1;  

                msurfacex = 0;  

                msurfacey = 0;  

                msurfacew = w;  

                msurfaceh = h;  

                    msurface = new surface(  

                            msession.msurfacesession, msession.mpid,  

                            mattrs.gettitle().tostring(),  

                            0, w, h, mattrs.format, flags);  

                } catch (surface.outofresourcesexception e) {  

                    reclaimsomesurfacememorylocked(this, "create");  

                    return null;  

                surface.opentransaction();  

                    try {  

                        msurfacex = mframe.left + mxoffset;  

                        msurfacey = mframe.top + myoffset;  

                        msurface.setposition(msurfacex, msurfacey);  

                        msurfacelayer = manimlayer;  

                        msurface.setlayer(manimlayer);  

                        msurfaceshown = false;  

                        msurface.hide();  

                        if ((mattrs.flags&windowmanager.layoutparams.flag_dither) != 0) {  

                            ......  

                            msurface.setflags(surface.surface_dither,  

                                    surface.surface_dither);  

                        }  

                    } catch (runtimeexception e) {  

                        reclaimsomesurfacememorylocked(this, "create-init");  

                    mlasthidden = true;  

                    surface.closetransaction();  

            return msurface;  

        在建立一個應用程式視窗的繪圖表面之前,我們需要知道以下資料:

        1. 應用程式視窗它所運作的應用程式程序的pid。

        2. 與應用程式視窗它所運作的應用程式程序所關聯的一個surfacesession對象。

        3. 應用程式視窗的标題。

        4. 應用程式視窗的像素格式。

        5. 應用程式視窗的寬度。

        6. 應用程式視窗的高度。

        7. 應用程式視窗的圖形緩沖區屬性标志。

        第1個和第2個資料可以通過目前正在處理的windowstate對象的成員變量msession所描述的一個session對象的成員變量mpid和msurfacesession來獲得;第3個和第4個資料可以通過目前正在處理的windowstate對象的成員變量mattr所描述的一個windowmanager.layoutparams對象的成員函數gettitle以及成員變量format來獲得;接下來我們就分析後面3個資料是如何獲得的。

        windowstate類的成員變量mframe的類型為rect,它用來描述應用程式視窗的位置和大小,它們是由windowmanagerservice服務根據螢幕大小以及其它屬性計算出來的,是以,通過調用過它的成員函數width和height就可以得到要建立繪圖表面的應用程式視窗的寬度和高度,并且儲存在變量w和h中。但是,當目前正在處理的windowstate對象的成員變量mattr所描述的一個windowmanager.layoutparams對象的成員變量flags的layoutparams.flag_scaled位不等于0時,就說明應用程式程序指定了該應用程式視窗的大小,這時候指定的應用程式視窗的寬度和高度就儲存在windowstate類的成員變量mrequestedwidth和mrequestedheight中,是以,我們就需要将目前正在處理的windowstate對象的成員變量mrequestedwidth和mrequestedheight的值分别儲存在變量w和h中。經過上述兩步計算之後,如果得到的變量w和h等于0,那麼就說明目前正在處理的windowstate對象所描述的應用程式視窗的大小還沒有經過計算,或者還沒有被指定過,這時候就需要将它們的值設定為1,避免接下來建立一個大小為0的繪圖表面。

        如果目前正在處理的windowstate對象的成員變量mattr所描述的一個windowmanager.layoutparams對象的成員變量memorytype的值等于memory_type_push_buffers,那麼就說明正在處理的應用程式視窗不擁有專屬的圖形緩沖區,這時候就需要将用來描述正在處理的應用程式視窗的圖形緩沖區屬性标志的變量flags的surface.push_buffers位設定為1。

       此外,如果目前正在處理的windowstate對象的成員變量mattr所描述的一個windowmanager.layoutparams對象的成員變量flags的windowmanager.layoutparams.flag_secure位不等于0,那麼就說明正在處理的應用程式視窗的界面是安全的,即是受保護的,這時候就需要将用來描述正在處理的應用程式視窗的圖形緩沖區屬性标志的變量flags的surface.secure位設定為1。當一個應用程式視窗的界面是受保護時,surfaceflinger服務在執行截屏功能時,就不能把它的界面截取下來。

       上述資料準備就緒之後,就可以建立目前正在處理的windowstate對象所描述的一個應用程式視窗的繪圖表面了,不過在建立之前,還會初始化該windowstate對象的以下成員變量:

       --mreportdestroysurface的值被設定為false。當一個應用程式視窗的繪圖表面被銷毀時,windowmanagerservice服務就會将相應的windowstate對象的成員變量mreportdestroysurface的值設定為true,表示要向該應用程式視窗所運作在應用程式程序發送一個繪圖表面銷毀通知。

       --msurfacependingdestroy的值被設定為false。當一個應用程式視窗的繪圖表面正在等待銷毀時,windowmanagerservice服務就會将相應的windowstate對象的成員變量mreportdestroysurface的值設定為true。

       --mdrawpending的值被設定為true。當一個應用程式視窗的繪圖表面處于建立之後并且繪制之前時,windowmanagerservice服務就會将相應的windowstate對象的成員變量mdrawpending的值設定為true,以表示該應用程式視窗的繪圖表面正在等待繪制。

       --mcommitdrawpending的值被設定為false。當一個應用程式視窗的繪圖表面繪制完成之後并且可以顯示出來之前時,windowmanagerservice服務就會将相應的windowstate對象的成員變量mcommitdrawpending的值設定為true,以表示該應用程式視窗正在等待顯示。

       --mreadytoshow的值被設定為false。有時候當一個應用程式視窗的繪圖表面繪制完成并且可以顯示出來之後,由于與該應用程式視窗所關聯的一個activity元件的其它視窗還未準備好顯示出來,這時候windowmanagerservice服務就會将相應的windowstate對象的成員變量mreadytoshow的值設定為true,以表示該應用程式視窗需要延遲顯示出來,即需要等到與該應用程式視窗所關聯的一個activity元件的其它視窗也可以顯示出來之後再顯示。

       --msurfaceshown的值被設定為false,表示應用程式視窗還沒有顯示出來,它是用來顯示調試資訊的。

       --msurfacelayer的值被設定為0,表示應用程式視窗的z軸位置,它是用來顯示調試資訊的。

       --msurfacealpha的值被設定為1,表示應用程式視窗的透明值,它是用來顯示調試資訊的。

       --msurfacex的值被設定為0,表示應用程式程式視窗的x軸位置,它是用來顯示調試資訊的。

       --msurfacey的值被設定為0,表示應用程式程式視窗的y軸位置,它是用來顯示調試資訊的。

       --msurfacew的值被設定為w,表示應用程式程式視窗的寬度,它是用來顯示調試資訊的。

       --msurfaceh的值被設定為h,表示應用程式程式視窗的高度,它是用來顯示調試資訊的。

       一個應用程式視窗的繪圖表面在建立完成之後,函數就會将得到的一個surface對象儲存在目前正在處理的windowstate對象的成員變量msurface中。注意,如果建立繪圖表面失敗,并且從surface類的構造函數抛出來的異常的類型為surface.outofresourcesexception,那麼就說明系統目前的記憶體不足了,這時候函數就會調用windowmanagerservice類的成員函數reclaimsomesurfacememorylocked來回收記憶體。

       如果一切正常,那麼函數接下來還會設定目前正在處理的windowstate對象所描述應用程式視窗的以下屬性:

      1. x軸和y軸位置。前面提到,windowstate類的成員變量mframe是用來描述應用程式視窗的位置和大小的,其中,位置就是通過它所描述的一個rect對象的成員變量left和top來表示的,它們分别應用視窗在x軸和y軸上的位置。此外,當一個windowstate對象所描述的應用程式視窗是一個桌面視窗時,該windowstate對象的成員變量mxoffset和myoffset用來描述桌面視窗相對目前要顯示的視窗在x軸和y軸上的偏移量。是以,将windowstate類的成員變量mxoffset的值加上另外一個成員變量mframe所描述的一個rect對象的成員變量left的值,就可以得到一個應用程式視窗在x軸上的位置,同樣,将windowstate類的成員變量myoffset的值加上另外一個成員變量mframe所描述的一個rect對象的成員變量top的值,就可以得到一個應用程式視窗在y軸上的位置。最終得到的位置值就分别儲存在windowstate類的成員變量msurfacex和msurfacey,并且會調用windowstate類的成員變量msurface所描述的一個surface對象的成員函數setposition來将它們設定到surfaceflinger服務中去。

      3. 抖動标志。如果windowstate類的成員變量mattr所描述的一個windowmanager.layoutparams對象的成員變量flags的windowmanager.layoutparams.flag_dither位不等于0,那麼就說明一個應用程式視窗的圖形緩沖區在渲染時,需要進行抖動處理,這時候就會調用windowstate類的成員變量msurface所描述的一個surface對象的成員函數setlayer來将對應的應用程式視窗的圖形緩沖區的屬性标志的surface.surface_dither位設定為1。

      4. 顯示狀态。由于目前正在處理的windowstate對象所描述的一個應用程式視窗的繪圖表面剛剛建立出來,是以,我們就需要通知surfaceflinger服務将它隐藏起來,這是通過調用目前正在處理的windowstate對象的成員變量msurface所描述的一個surface對象的成員變量hide來實作的。這時候還會将目前正在處理的windowstate對象的成員變量msurfaceshown和mlasthidden的值分别設定為false和true,以表示對應的應用程式視窗是處于隐藏狀态的。

      注意,為了避免surfaceflinger服務每設定一個應用程式視窗屬性就重新渲染一次系統的ui,上述4個屬性設定需要在一個事務中進行,這樣就可以避免出現界面閃爍。我們通過調用surface類的靜态成員函數opentransaction和closetransaction就可以分别在surfaceflinger服務中打開和關閉一個事務。

      還有另外一個地方需要注意的是,在設定應用程式視窗屬性的過程中,如果抛出了一個runtimeexception異常,那麼就說明系統目前的記憶體不足了,這時候函數也會調用windowmanagerservice類的成員函數reclaimsomesurfacememorylocked來回收記憶體。

      接下來,我們就繼續分析surface類的構造函數的實作,以便可以了解一個應用程式視窗的繪圖表面的詳細建立過程。

       step 9. new surface

android之surfaceview畫圖

public class surface implements parcelable {  

    private int msurfacecontrol;  

    private canvas mcanvas;  

    private string mname;  

    public surface(surfacesession s,  

            int pid, string name, int display, int w, int h, int format, int flags)  

        throws outofresourcesexception {  

        mcanvas = new compatiblecanvas();  

        init(s,pid,name,display,w,h,format,flags);  

        mname = name;  

    private native void init(surfacesession s,  

            throws outofresourcesexception;  

        這個函數定義在檔案frameworks/base/core/java/android/view/surface.java中。

        surface類有三個成員變量msurfacecontrol、mcanvas和mname,它們的類型分别是int、canvas和mname,其中,msurfacecontrol儲存的是在c++層的一個surfacecontrol對象的位址值,mcanvas用來描述一塊類型為compatiblecanvas的畫布,mname用來描述目前正在建立的一個繪圖表面的名稱。畫布是真正用來繪制ui的地方,不過由于現在正在建立的繪圖表面是在windowmanagerservice服務這一側使用的,而windowmanagerservice服務不會去繪制應用程式視窗的ui,它隻會去設定應用程式視窗的屬性,是以,這裡建立的畫布實際上沒有什麼作用,我們主要關注與成員變量msurfacecontrol所關聯的c++層的surfacecontrol對象是如何建立的。

        surface類的構造函數是通過調用另外一個成員函數init來建立與成員變量msurfacecontrol所關聯的c++層的surfacecontrol對象的。surface類的成員函數init是一個jni方法,它是由c++層的函數surface_init來實作的,如下所示:

android之surfaceview畫圖

static void surface_init(  

        jnienv* env, jobject clazz,  

        jobject session,  

        jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jint flags)  

    if (session == null) {  

        dothrow(env, "java/lang/nullpointerexception");  

        return;  

    surfacecomposerclient* client =  

            (surfacecomposerclient*)env->getintfield(session, sso.client);  

    sp<surfacecontrol> surface;  

    if (jname == null) {  

        surface = client->createsurface(pid, dpy, w, h, format, flags);  

    } else {  

        const jchar* str = env->getstringcritical(jname, 0);  

        const string8 name(str, env->getstringlength(jname));  

        env->releasestringcritical(jname, str);  

        surface = client->createsurface(pid, name, dpy, w, h, format, flags);  

    if (surface == 0) {  

        dothrow(env, outofresourcesexception);  

    setsurfacecontrol(env, clazz, surface);  

        這個函數定義在檔案frameworks/base/core/jni/android_view_surface.cpp中。

        是以,函數首先将參數session所指向的一個java層的surfacesession對象的成員變量mclient轉換成一個surfacecomposerclient對象,然後再調用這個surfacecomposerclient對象的成員函數createsurface來請求surfaceflinger服務來為參數clazz所描述的一個java層的surface對象所關聯的應用程式視窗建立一個layer對象。surfaceflinger服務建立完成這個layer對象之後,就會将該layer對象内部的一個實作了isurface接口的surfacelayer對象傳回給函數,于是,函數就可以獲得一個實作了isurface接口的binder代理對象。這個實作了isurface接口的binder代理對象被封裝在c++層的一個surfacecontrol對象surface中。

       注意,sso是一個全局變量,它是一個類型為sso_t的結構體,它的定義如下所示:

android之surfaceview畫圖

struct sso_t {  

    jfieldid client;  

};  

static sso_t sso;  

        它的成員函數client用來描述java層中的surfacesession類的成員變量mclient在類中的偏移量,是以,函數surface_init通過這個偏移量就可以通路參數session所指向的一個surfacesession對象的成員變量mclient的值,進而獲得一個surfacecomposerclient對象。

       得到了surfacecontrol對象surface之後,函數surface_init接下來繼續調用另外一個函數setsurfacecontrol來它的位址值儲存在參數clazz所指向的一個java層的surface對象的成員變量msurfacecontrol中,如下所示:

android之surfaceview畫圖

static void setsurfacecontrol(jnienv* env, jobject clazz,  

        const sp<surfacecontrol>& surface)  

    surfacecontrol* const p =  

        (surfacecontrol*)env->getintfield(clazz, so.surfacecontrol);  

    if (surface.get()) {  

        surface->incstrong(clazz);  

    if (p) {  

        p->decstrong(clazz);  

    env->setintfield(clazz, so.surfacecontrol, (int)surface.get());  

        在分析函數setsurfacecontrol的實作之前,我們先分析全局變量so的定義,如下所示:

android之surfaceview畫圖

struct so_t {  

    jfieldid surfacecontrol;  

    jfieldid surface;  

    jfieldid savecount;  

    jfieldid canvas;  

static so_t so;  

        它是一個類型為so_t的結構體。結構體so_t有四個成員變量surfacecontrol、surface、savecount和canvas,它們分别用來描述java層的surface類的四個成員變量msurfacecontrol、mnativesurface、msavecount和mcanvas在類中的偏移量。

       回到函數setsurfacecontrol中,它首先通過結構體so的成員變量surfacecontrol來獲得參數clazz所指向的一個java層的surface對象的成員變量msurfacecontrol所關聯的一個c++層的surfacecontrol對象。如果這個surfacecontrol對象存在,那麼變量p的值就不會等于null,在這種情況下,就需要調用它的成員函數decstrong來減少它的強引用計數,因為接下來參數clazz所指向的一個java層的surface對象不再通過成員變量msurfacecontrol來引用它了。

       另一方面,函數setsurfacecontrol需要增加參數surface所指向的一個c++層的surfacecontrol對象的強引用計數,即調用參數surface所指向的一個c++層的surfacecontrol對象的成員函數incstrong,因為接下來參數clazz所指向的一個java層的surface對象要通過成員變量msurfacecontrol來引用它,即将它的位址值儲存在參數clazz所指向的一個java層的surface對象的成員變量msurfacecontrol中。

       這一步執行完成之後,沿着調用路徑層層傳回,回到前面的step 8中,即windowstate類的成員函數createsurfacelocked中,這時候一個繪圖表面就建立完成了。這個繪圖表面最終會傳回給請求建立它的應用程式程序,即前面的step 5,也就是iwindowsession.stub.proxy類的成員函數relayout。

       iwindowsession.stub.proxy類的成員函數relayout獲得了從windowmanagerservice服務傳回來的一個繪圖表面,即一個java層的surface對象之後,就會将它的内容拷貝到參數outsurface所描述的另外一個java層的surface對象中,這裡通過調用surface類的成員函數readfromparcel來實作的。注意,參數outsurface所描述的這個java層的surface對象是在應用程式程序這一側建立的,它的作用是用來繪制應用程式窗品的ui。

       接下來,我們就繼續分析surface類的成員函數readfromparcel的實作,以便可以了解在應用程式程序這一側的surface對象是如何建立的。

       step 10. surface.readfromparcel

android之surfaceview畫圖

    public native   void readfromparcel(parcel source);  

       這個函數定義在檔案frameworks/base/core/java/android/view/surface.java中。

       surface類的成員函數readfromparcel是一個jni方法,它是由c++層的函數surface_readfromparcel來實作的,如下所示:

android之surfaceview畫圖

static void surface_readfromparcel(  

        jnienv* env, jobject clazz, jobject argparcel)  

    parcel* parcel = (parcel*)env->getintfield( argparcel, no.native_parcel);  

    if (parcel == null) {  

        dothrow(env, "java/lang/nullpointerexception", null);  

    sp<surface> sur(surface::readfromparcel(*parcel));  

    setsurface(env, clazz, sur);  

       這個函數定義在檔案frameworks/base/core/jni/android_view_surface.cpp中。

       參數argparcel所指向的一個parcel對象的目前位置儲存的是一個java層的surface對象的内容,函數readfromparcel首先調用c++層的surface類的成員函數readfromparcel來将這些内容封裝成一個c++層的surface對象,如下所示:

android之surfaceview畫圖

sp<surface> surface::readfromparcel(const parcel& data) {  

    mutex::autolock _l(scachedsurfaceslock);  

    sp<ibinder> binder(data.readstrongbinder());  

    sp<surface> surface = scachedsurfaces.valuefor(binder).promote();  

       surface = new surface(data, binder);  

       scachedsurfaces.add(binder, surface);  

    if (surface->msurface == 0) {  

      surface = 0;  

    cleancachedsurfaceslocked();  

    return surface;  

        這個函數定義在檔案frameworks/base/libs/surfaceflinger_client/surface.cpp中。

        參數data所指向的一個parcel對象的目前位置儲存的是一個binder代理對象,這個binder代理對象實作了isurface接口,它所引用的binder本地對象就是在前面的step 9中windowmanagerservice服務請求surfaceflinger服務所建立的一個layer對象的内部的一個surfacelayer對象。

        獲得了一個實作了isurface接口的binder代理對象binder之後,c++層的surface類的成員函數readfromparcel就可以将它封裝在一個c++層的surface對象中了,并且将這個c++層的surface對象傳回給調用者。注意,c++層的surface類的成員函數readfromparcel在建立為binder代理對象binder建立一個c++層的surface對象之前,首先會在c++層的surface類的靜态成員變量scachedsurfaces所描述的一個defaultkeyedvectort向量中檢查是否已經為存在一個對應的c++層的surface對象了。如果已經存在,那麼就會直接将這個c++層的surface對象傳回給調用者。

        回到函數surface_readfromparcel中,接下來它就會調用另外一個函數setsurface來将前面所獲得的一個c++層的surface對象sur儲存在參數clazz所描述的一個java層的surface對象的成員變量mnativesurface中,如下所示:

android之surfaceview畫圖

static void setsurface(jnienv* env, jobject clazz, const sp<surface>& surface)  

    surface* const p = (surface*)env->getintfield(clazz, so.surface);  

    env->setintfield(clazz, so.surface, (int)surface.get());  

       全局變量so是一個類型為so_t的結構體,在前面的step 9中,我們已經分析過它的定義了,函數setsurface首先通過它的成員變量surface來将參數clazz所描述的一個java層的surface對象的成員變量mnativesurface轉換成一個surface對象p。如果這個surface對象p存在,那麼就需要調用它的成員函數decstrong來減少它的強引用計數,因為接下來參數clazz所描述的一個java層的surface對象不再通過成員變量mnativesurface來引用它了。

       函數setsurface接下來就會将參數surface所指向的一個c++層的surface對象的位址值儲存在參數clazz所描述的一個java層的surface對象的成員變量mnativesurface中。在執行這個操作之前,函數setsurface需要調用參數surface所指向的一個c++層的surface對象的成員函數incstrong來增加它的強引用計數,這是因為接下來它要被參數clazz所描述的一個java層的surface對象通過成員變量mnativesurface來引用了。

       至此,我們就分析完成android應用程式視窗的繪圖表面的建立過程了。通過這個過程我們就可以知道:

       1. 每一個應用程式視窗都對應有兩個java層的surface對象,其中一個是在windowmanagerservice服務這一側建立的,而另外一個是在應用程式程序這一側建立的。

       2. 在windowmanagerservice服務這一側建立的java層的surface對象在c++層關聯有一個surfacecontrol對象,用來設定應用視窗的屬性,例如,大小和位置等。

       3. 在應用程式程序這一側建立的ava層的surface對象在c++層關聯有一個surface對象,用來繪制應用程式窗品的ui。

       了解上述三個結論對了解android應用程式視窗的實作架構以及windowmanagerservice服務的實作都非常重要。 一個應用程式視窗的繪圖表面在建立完成之後,接下來應用程式程序就可以在上面繪制它的ui了。在接下來的一篇文章中,我們就繼續分析android應用程式窗品的繪制過程,敬請關注!