1. 程式人生 > >Android應用程式視窗(Activity)的檢視物件(View)的建立過程分析

Android應用程式視窗(Activity)的檢視物件(View)的建立過程分析

       從前文可知道,每一個Activity元件都有一個關聯的Window物件,用來描述一個應用程式視窗。每一個應用程式視窗內部又包含有一個View物件,用來描述應用程式視窗的檢視。應用程式視窗檢視是真正用來實現UI內容和佈局的,也就是說,每一個Activity元件的UI內容和佈局都是通過與其所關聯的一個Window物件的內部的一個View物件來實現的。在本文中,我們就詳細分析應用程式視窗檢視的建立過程。

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

       在前面Android應用程式視窗(Activity)實現框架簡要介紹和學習計劃

一文中提到,應用程式視窗內部所包含的檢視物件的實際型別為DecorView。DecorView類繼承了View類,是作為容器(ViewGroup)來使用的,它的實現如圖1所示:


圖1 DecorView類的實現

        從前面Android應用程式視窗(Activity)實現框架簡要介紹和學習計劃一文還可以知道,每一個應用程式視窗的檢視物件都有一個關聯的ViewRoot物件,這些關聯關係是由視窗管理器來維護的,如圖2所示:


圖2 應用程式視窗檢視與ViewRoot的關係圖

        簡單來說,ViewRoot相當於是MVC模型中的Controller,它有以下職責:

        1. 負責為應用程式視窗檢視建立Surface。

        2. 配合WindowManagerService來管理系統的應用程式視窗。

        3. 負責管理、佈局和渲染應用程式視窗檢視的UI。

        那麼,應用程式視窗的檢視物件及其所關聯的ViewRoot物件是什麼時候開始建立的呢? 從前面Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析一文可以知道,Activity元件在啟動的時候,系統會為它建立視窗物件(Window),同時,系統也會為這個視窗物件建立檢視物件。另一方面,當Activity元件被啟用的時候,系統如果發現與它的應用程式視窗檢視物件所關聯的ViewRoot物件還沒有建立,那麼就會先建立這個ViewRoot物件,以便接下來可以將它的UI渲染出來。

       從前面Android應用程式啟動過程原始碼分析一文可以知道,Activity元件在啟動的過程中,會呼叫ActivityThread類的成員函式handleLaunchActivity,用來建立以及首次啟用Activity元件,因此,接下來我們就從這個函式開始,具體分析應用程式視窗的檢視物件及其所關聯的ViewRoot物件的建立過程,如圖3所示:


圖3 應用程式視窗檢視的建立過程

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

        Step 1. ActivityThread.handleLaunchActivity

public final class ActivityThread {
    ......

    private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ......

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            ......

            handleResumeActivity(r.token, false, r.isForward);

            ......
        }

        ......
    }

    ......
}
        這個函式定義在檔案frameworks/base/core/java/android/app/ActivityThread.java檔案中。

        函式首先呼叫ActivityThread類的成員函式performLaunchActivity來建立要啟動的Activity元件。在建立Activity元件的過程中,還會為該Activity元件建立視窗物件和檢視物件。Activity元件建立完成之後,就可以將它啟用起來了,這是通過呼叫ActivityThread類的成員函式handleResumeActivity來執行的。

        接下來,我們首先分析ActivityThread類的成員函式performLaunchActivity的實現,以便可以瞭解應用程式視窗檢視物件的建立過程,接著再回過頭來繼續分析ActivityThread類的成員函式handleResumeActivity的實現,以便可以瞭解與應用程式視窗檢視物件所關聯的ViewRoot物件的建立過程。

        Step 2. ActivityThread.performLaunchActivity

        這個函式定義在檔案frameworks/base/core/java/android/app/ActivityThread.java檔案中。

        這一步可以參考Android應用程式視窗(Activity)的執行上下文環境(Context)的建立過程分析一文的Step 1,它主要就是建立一個Activity元件例項,並且呼叫這個Activity元件例項的成員函式onCreate來讓其執行一些自定義的初始化工作。

        Step 3. Activity.onCreate

        這個函式定義在檔案frameworks/base/core/java/android/app/Activity.java中。

        這一步可以參考Android應用程式視窗(Activity)的執行上下文環境(Context)的建立過程分析一文的Step 10。我們在實現一個Activity元件的時候,也就是在實現一個Activity子類的時候,一般都會重寫成員函式onCreate,以便可以執行一些自定義的初始化工作,其中就包含初始化UI的工作。例如,在前面在Ubuntu上為Android系統內建Java應用程式測試Application Frameworks層的硬體服務一文中,我們實現了一個名稱為Hello的Activity元件,用來測試硬體服務,它的成員函式onCreate的樣子長得大概如下所示:

public class Hello extends Activity implements OnClickListener {  
    ......  
      
    /** Called when the activity is first created. */  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
  
        ......  
    }  

    ......
}
       其中,呼叫從父類Activity繼承下來的成員函式setContentView就是用來建立應用程式視窗檢視物件的。

       接下來,我們就繼續分析Activity類的成員函式setContentView的實現。

       Step 4. Activity.setContentView

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks {
    ......

    private Window mWindow;
    ......

    public Window getWindow() {
        return mWindow;
    }
    ......

    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
    }

    ......
}
        這個函式定義在檔案frameworks/base/core/java/android/app/Activity.java中。

        Activity類的成員函式setContentView首先呼叫另外一個成員函式getWindow來獲得成員變數mWindow所描述的一個視窗物件,接著再呼叫這個視窗物件的成員函式setContentView來執行建立應用程式視窗檢視物件的工作。

        從前面Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析一文可以知道,Activity類的成員變數mWindow指向的是一個PhoneWindow物件,因此,接下來我們就繼續分析PhoneWindow類的成員函式setContentView的實現。

        Step 5. PhoneWindow.setContentView

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;
    ......

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }

    ......
}
        這個函式定義在檔案frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。

        PhoneWindow類的成員變數mContentParent用來描述一個型別為DecorView的檢視物件,或者這個型別為DecorView的檢視物件的一個子檢視物件,用作UI容器。當它的值等於null的時候,就說明正在處理的應用程式視窗的檢視物件還沒有建立。在這種情況下,就會呼叫成員函式installDecor來建立應用程式視窗檢視物件。否則的話,就說明是要重新設定應用程式視窗的檢視。在重新設定之前,首先呼叫成員變數mContentParent所描述的一個ViewGroup物件來移除原來的UI內空。

        由於我們是在Activity元件啟動的過程中建立應用程式視窗檢視的,因此,我們就假設此時PhoneWindow類的成員變數mContentParent的值等於null。接下來,函式就會呼叫成員函式installDecor來建立應用程式視窗檢視物件,接著再通過呼叫PhoneWindow類的成員變數mLayoutInflater所描述的一個LayoutInflater物件的成員函式inflate來將引數layoutResID所描述的一個UI佈局設定到前面所建立的應用程式視窗檢視中去,最後還會呼叫一個Callback介面的成員函式onContentChanged來通知對應的Activity元件,它的檢視內容發生改變了。從前面Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析一文可以知道,Activity元件自己實現了這個Callback介面,並且將這個Callback介面設定到了與它所關聯的應用程式視窗物件的內部去,因此,前面實際呼叫的是Activity類的成員函式onContentChanged來發出一個檢視內容變化通知。

      接下來,我們就繼續分析PhoneWindow類的成員函式installDecor的實現,以便可以繼續瞭解應用程式視窗檢視物件的建立過程。

      Step 6. PhoneWindow.installDecor

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ......

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    private ViewGroup mContentParent;
    ......

    private TextView mTitleView;
    ......

    private CharSequence mTitle = null;
    ......

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            ......
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }
    }

    ......
}
        這個函式定義在檔案frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。

        由於我們是在Activity元件啟動的過程中建立應用程式視窗檢視的,因此,我們同時假設此時PhoneWindow類的成員變數mDecor的值等於null。這時候PhoneWindow類的成員函式installDecor就會呼叫另外一個成員函式generateDecor來建立一個DecorView物件,並且儲存在PhoneWindow類的成員變數mDecor中。

        PhoneWindow類的成員函式installDecor接著再呼叫另外一個成員函式generateLayout來根據當前應用程式視窗的Feature來載入對應的窗口布局檔案。這些佈局檔案儲存在frameworks/base/core/res/res/layout目錄下,它們必須包含有一個id值為“content”的佈局控制元件。這個佈局控制元件必須要從ViewGroup類繼承下來,用來作為視窗的UI容器。PhoneWindow類的成員函式generateLayout執行完成之後,就會這個id值為“content”的ViewGroup控制元件來給PhoneWindow類的成員函式installDecor,後者再將其儲存在成員變數mContentParent中。

       PhoneWindow類的成員函式installDecor還會檢查前面載入的窗口布局檔案是否包含有一個id值為“title”的TextView控制元件。如果包含有的話,就會將它儲存在PhoneWindow類的成員變數mTitleView中,用來描述當前應用程式視窗的標題欄。但是,如果當前應用程式視窗是沒有標題欄的,即它的Feature位FEATURE_NO_TITLE的值等於1,那麼PhoneWindow類的成員函式installDecor就需要將前面得到的標題欄隱藏起來。注意,PhoneWindow類的成員變數mTitleView所描述的標題欄有可能是包含在一個id值為“title_container”的容器裡面的,在這種情況下,就需要隱藏該標題欄容器。另一方面,如果當前應用程式視窗是設定有標題欄的,那麼PhoneWindow類的成員函式installDecor就會設定它的標題欄文字。應用程式視窗的標題欄文字儲存在PhoneWindow類的成員變數mTitle中,我們可以呼叫PhoneWindow類的成員函式setTitle來設定。

       這一步執行完成之後,應用程式視窗檢視就建立完成了,回到前面的Step 1中,即ActivityThread類的成員函式handleLaunchActivity中,接下來就會呼叫ActivityThread類的另外一個成員函式handleResumeActivity來啟用正在啟動的Activity元件。由於在是第一次啟用該Activity元件,因此,在啟用之前,還會為該Activity元件建立一個ViewRoot物件,並且與前面所建立的應用程式視窗檢視關聯起來,以便後面可以通過該ViewRoot物件來控制應用程式視窗檢視的UI展現。

       接下來,我們就繼續分析ActivityThread類的成員函式handleResumeActivity的實現。

       Step 7. ActivityThread.handleResumeActivity

public final class ActivityThread {
    ......

    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
        ......

        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;
            ......

            // If the window hasn't yet been added to the window manager,
            // and this guy didn't finish itself or start another activity,
            // then go ahead and add the window.
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                }
            }
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                ......
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
            } 

            ......
        }

        ......
    }
  
    ......
}

        這個函式定義在檔案frameworks/base/core/java/android/app/ActivityThread.java中。

        ActivityThread類的成員函式handleResumeActivity首先呼叫另外一個成員函式performResumeActivity來通知Activity元件,它要被激活了,即會導致Activity元件的成員函式onResume被呼叫。ActivityThread類的成員函式performResumeActivity的返回值是一個ActivityClientRecord物件r,這個ActivityClientRecord物件的成員變數activity描述的就是正在啟用的Activity元件a。

        ActivityThread類的成員函式handleResumeActivity接下來判斷正在啟用的Activity元件接下來是否是可見的。如果是可見的,那麼變數willBeVisible的值就會等於true。Activity類的成員變數mStartedActivity用來描述一個Activity元件是否正在啟動一個新的Activity元件,並且等待這個新的Activity元件的執行結果。如果是的話,那麼這個Activity元件的成員變數mStartedActivity的值就會等於true,表示在新的Activity元件的執行結果返回來之前,當前Activity元件要保持不可見的狀態。因此,當Activity元件a的成員變數mStartedActivity的值等於true的時候,它接下來就是不可見的,否則的話,就是可見的。

        雖然說在Activity元件a的成員變數mStartedActivity的值等於true的情況下,它接下來的狀態要保持不可見的,但是有可能它所啟動的Activity元件的UI不是全屏的。在這種情況下,Activity元件a的UI仍然是有部分可見的,這時候也要將變數willBeVisible的值設定為true。因此,如果前面得到變數willBeVisible的值等於false,那麼ActivityThread類的成員函式handleResumeActivity接下來就會通過Binder程序間通訊機制來呼叫ActivityManagerService服務的成員函式willActivityBeVisible來檢查位於Activity元件a上面的其它Activity元件(包含了Activity元件a正在等待其執行結果的Activity元件)是否是全屏的。如果不是,那麼ActivityManagerService服務的成員函式willActivityBeVisible的返回值就會等於true,表示接下來需要顯示Activity元件a。

       前面得到的ActivityClientRecord物件r的成員變數window用來描述當前正在啟用的Activity元件a所關聯的應用程式視窗物件。當它的值等於null的時候,就表示當前正在啟用的Activity元件a所關聯的應用程式視窗物件還沒有關聯一個ViewRoot物件。進一步地,如果這個正在啟用的Activity元件a還活著,並且接下來是可見的,即ActivityClientRecord物件r的成員變數mFinished的值等於false,並且前面得到的變數willBeVisible的值等於true,那麼這時候就說明需要為與Activity元件a所關聯的一個應用程式視窗檢視物件關聯的一個ViewRoot物件。

       將一個Activity元件的應用程式視窗檢視物件與一個ViewRoot物件關聯是通過該Activity元件所使用的視窗管理器來執行的。從前面Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析一文可以知道,一個Activity元件所使用的本地視窗管理器儲存它的成員變數mWindowManager中,這可以通過Activity類的成員函式getWindowManager來獲得。在接下來的Step 10中,我們再分析Activity類的成員函式getWindowManager的實現。

       由於我們現在要給Activity元件a的應用程式視窗檢視物件關聯一個ViewRoot物件,因此,我們就需要首先獲得這個應用程式視窗檢視物件。從前面的Step 6可以知道,一個Activity元件的應用程式視窗檢視物件儲存在與其所關聯的一個應用程式視窗物件的內部,因此,我們又要首先獲得這個應用程式視窗物件。與一個Activity元件所關聯的應用程式視窗物件可以通過呼叫該Activity元件的成員函式getWindow來獲得。一旦獲得了這個應用程式視窗物件(型別為PhoneWindow)之後,我們就可以呼叫它的成員函式getDecorView來獲得它內部的檢視物件。在接下來的Step 8和Step 9中,我們再分別分析Activity類的成員函式Activity類的成員函式getWindow和PhoneWindow類的成員函式getDecorView的實現。

      在關聯應用程式視窗檢視物件和ViewRoot物件的時候,還需要第三個引數,即應用程式視窗的佈局引數,這是一個型別為WindowManager.LayoutParams的物件,可以通過呼叫應用程式視窗的成員函式getAttributes來獲得。一切準備就緒之後,還要判斷最後一個條件是否成立,即當前正在啟用的Activity元件a在本地程序中是否是可見的,即它的成員變數mVisibleFromClient的值是否等於true。如果是可見的,那麼最後就可以呼叫前面所獲得的一個本地視窗管理器wm(型別為LocalWindowManager)的成員函式addView來執行關聯應用程式視窗檢視物件和ViewRoot物件的操作。

     接下來,我們就分別分析Activity類的成員函式getWindow、PhoneWindow類的成員函式getDecorView、ctivity類的成員函式getWindowManager以及LocalWindowManager類的成員函式addView的實現。

     Step 8. Activity.getWindow

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks {
    ......

    private Window mWindow;
    ......

    public Window getWindow() {
        return mWindow;
    }

    ......
}
        這個函式定義在檔案frameworks/base/core/java/android/app/Activity.java中。

        從前面Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析一文可以知道,Activity類的成員變數mWindow指向的是一個型別為PhoneWindow的視窗物件,因此,Activity類的成員函式getWindow返回給呼叫者的是一個PhoneWindow物件。

        這一步執完成之後,返回到前面的Step 7中,即ActivityThread類的成員函式handleResumeActivity中,接下來就會繼續呼叫前面所獲得的一個PhoneWindow物件的成員函式getDecorView來獲得當前正在啟用的Activity元件所關聯的一個應用程式視窗檢視物件。

        Step 9. PhoneWindow.getDecorView

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    ......

    private DecorView mDecor;
    ......

    @Override
    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }

    ......
}
        這個函式定義在檔案frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。

        PhoneWindow類的成員函式getDecorView首先判斷成員變數mDecor的值是否等於null。如果是的話,那麼就說明當前正在處理的應用程式視窗還沒有建立檢視物件。這時候就會呼叫另外一個成員函式installDecor來建立這個檢視物件。從前面的呼叫過程可以知道,當前正在處理的應用程式視窗已經建立過檢視物件,因此,這裡的成員變數mDecor的值不等於null,PhoneWindow類的成員函式getDecorView直接將它返回給呼叫者。

        這一步執完成之後,返回到前面的Step 7中,即ActivityThread類的成員函式handleResumeActivity中,接下來就會繼續呼叫當前正在啟用的Activity元件的成員函式getWindowManager來獲得一個本地視窗管理器。

        Step 10. Activity.getWindowManager

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks {
    ......

    private WindowManager mWindowManager;
    ......

    public WindowManager getWindowManager() {
        return mWindowManager;
    }

    ......
}
        這個函式定義在檔案frameworks/base/core/java/android/app/Activity.java中。

        從前面Android應用程式視窗(Activity)的執行上下文環境(Context)的建立過程分析一文可以知道,Activity類的成員變數mWindowManager指向的一是型別為LocalWindowManager的本地視窗管理器,Activity類的成員函式getWindowManager直接將它返回給呼叫者。

        這一步執完成之後,返回到前面的Step 7中,即ActivityThread類的成員函式handleResumeActivity中,接下來就會繼續呼叫前面所獲得的一個LocalWindowManager物件的成員函式addView來為當前正在啟用的Activity元件的應用程式視窗檢視物件關聯一個ViewRoot物件。

        Step 11. LocalWindowManager.addView

public abstract class Window {
    ......

    private class LocalWindowManager implements WindowManager {
        ......

        public final void addView(View view, ViewGroup.LayoutParams params) {
            ......

            mWindowManager.addView(view, params);
        }

        ......

        private final WindowManager mWindowManager;
 
        ......
    }

    ......
}
        這個函式定義在檔案frameworks/base/core/java/android/view/Window.java中。

        從前面Android應用程式視窗(Activity)的視窗物件(Window)的建立過程分析一文可以知道,LocalWindowManager類的成員變數mWindowManager指向的是一個WindowManagerImpl物件,因此,LocalWindowManager類的成員函式addView接下來呼叫WindowManagerImpl類的成員函式addView來給引數view所描述的一個應用程式視窗檢視物件關聯一個ViewRoot物件。

        Step 12. WindowManagerImpl.addView

public class WindowManagerImpl implements WindowManager {
    ......

    public void addView(View view, ViewGroup.LayoutParams params)
    {
        addView(view, params, false);
    }

    ......

    private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
    {
        ......

        final WindowManager.LayoutParams wparams
                = (WindowManager.LayoutParams)params;

        ViewRoot root;
        View panelParentView = null;

        synchronized (this) {
            // Here's an odd/questionable case: if someone tries to add a
            // view multiple times, then we simply bump up a nesting count
            // and they need to remove the view the corresponding number of
            // times to have it actually removed from the window manager.
            // This is useful specifically for the notification manager,
            // which can continually add/remove the same view as a
            // notification gets updated.
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (!nest) {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                root = mRoots[index];
                root.mAddNesting++;
                // Update layout parameters.
                view.setLayoutParams(wparams);
                root.setLayoutParams(wparams, true);
                return;
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews != null ? mViews.length : 0;
                for (int i=0; i<count; i++) {
                    if (mRoots[i].mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews[i];
                    }
                }
            }

            root = new ViewRoot(view.getContext());
            root.mAddNesting = 1;

            view.setLayoutParams(wparams);

            if (mViews == null) {
                index = 1;
                mViews = new View[1];
                mRoots = new ViewRoot[1];
                mParams = new WindowManager.LayoutParams[1];
            } else {
                index = mViews.length + 1;
                Object[] old = mViews;
                mViews = new View[index];
                System.arraycopy(old, 0, mViews, 0, index-1);
                old = mRoots;
                mRoots = new ViewRoot[index];
                System.arraycopy(old, 0, mRoots, 0, index-1);
                old = mParams;
                mParams = new WindowManager.LayoutParams[index];
                System.arraycopy(old, 0, mParams, 0, index-1);
            }
            index--;

            mViews[index] = view;
            mRoots[index] = root;
            mParams[index] = wparams;
        }
        // do this last because it fires off messages to start doing things
        root.setView(view, wparams, panelParentView);
    }

    ......

    private View[] mViews;
    private ViewRoot[] mRoots;
    private WindowManager.LayoutParams[] mParams;

    ......
}

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

       在WindowManagerImpl類中,兩個引數版本的成員函式addView是通過呼叫三個引數版本的成同函式addView來實現的,因此,我們接下來就主要分析三個引數版本的成員函式addView的實現。

       在分析WindowManagerImpl類的三個引數版本的成員函式addView的實現之前,我們首先了解一下WindowManagerImpl類是如何關聯一個應用程式視窗檢視物件(View物件)和一個ViewRoot物件的。一個View物件在與一個ViewRoot物件關聯的同時,還會關聯一個WindowManager.LayoutParams物件,這個WindowManager.LayoutParams物件是用來描述應用程式視窗檢視的佈局屬性的。

       WindowManagerImpl類有三個成員變數mViews、mRoots和mParams,它們分別是型別為View、ViewRoot和WindowManager.LayoutParams的陣列。這三個陣列的大小是始終保持相等的。這樣, 有關聯關係的View物件、ViewRoot物件和WindowManager.LayoutParams物件就會分別儲存在陣列mViews、mRoots和mParams的相同位置上,也就是說,mViews[i]、mRoots[i]和mParams[i]所描述的View物件、ViewRoot物件和WindowManager.LayoutParams物件是具有關聯關係的。因此,WindowManagerImpl類的三個引數版本的成員函式addView在關聯一個View物件、一個ViewRoot物件和一個WindowManager.LayoutParams物件的時候,只要分別將它們放在陣列mViews、mRoots和mParams的相同位置上就可以了。

       理解了一個View物件、一個ViewRoot物件和一個WindowManager.LayoutParams物件是如何關聯之後,WindowManagerImpl類的三個引數版本的成員函式addView的實現就容易理解了。

       引數view和引數params描述的就是要關聯的View物件和WindowManager.LayoutParams物件。成員函式addView首先呼叫另外一個成員函式findViewLocked來檢查引數view所描述的一個View物件是否已經存在於陣列中mViews中了。如果已經存在的話,那麼就說明該View物件已經關聯過ViewRoot物件以及WindowManager.LayoutParams物件了。在這種情況下,如果引數nest的值等於false,那麼成員函式addView是不允許重複對引數view所描述的一個View物件進行重新關聯的。另一方面,如果引數nest的值等於true,那麼成員函式addView只是重新修改引數view所描述的一個View物件及其所關聯的一個ViewRoot物件內部使用的一個WindowManager.LayoutParams物件,即更新為引數params所描述的一個WindowManager.LayoutParams物件,這是通過呼叫它們的成員函式setLayoutParams來實現的。

       如果引數view所描述的一個View物件還沒有被關聯過一個ViewRoot物件,那麼成員函式addView就會建立一個ViewRoot物件,並且將它與引數view和params分別描述的一個View物件和一個WindowManager.LayoutParams物件儲存在陣列mViews、mRoots和mParams的相同位置上。注意,如果陣列mViews、mRoots和mParams尚未建立,那麼成員函式addView就會首先分別為它們建立一個大小為1的陣列,以便可以用來分別儲存所要關聯的View物件、ViewRoot物件和WindowManager.LayoutParams物件。另一方面,如果陣列mViews、mRoots和mParams已經建立,那麼成員函式addView就需要分別將它們的大小增加1,以便可以在它們的末尾位置上分別儲存所要關聯的View物件、ViewRoot物件和WindowManager.LayoutParams物件。

       還有另外一個需要注意的地方是當引數view描述的是一個子應用程式視窗的檢視物件時,即WindowManager.LayoutParams物件wparams的成員變數type的值大於等於WindowManager.LayoutParams.FIRST_SUB_WINDOW並且小於等於WindowManager.LayoutParams.LAST_SUB_WINDOW時,那麼成員函式addView還需要找到這個子檢視物件的父檢視物件panelParentView,這是通過遍歷陣列mRoots來查詢的。首先,WindowManager.LayoutParams物件wparams的成員變數token指向了一個型別為W的Binder本地物件的一個IBinder介面,用來描述引數view所描述的一個子應用程式視窗檢視物件所屬的父應用程式視窗檢視物件。其次,每一個ViewRoot物件都通過其成員變數mWindow來儲存一個型別為W的Binder本地物件,因此,如果在陣列mRoots中,存在一個ViewRoot物件,它的成員變數mWindow所描述的一個W物件的一個IBinder介面等於WindowManager.LayoutParams物件wparams的成員變數token所描述的一個IBinder介面時,那麼就說明與該ViewRoot物件所關聯的View物件即為引數view的父應用程式視窗檢視物件。

        成員函式addView為引數view所描述的一個View物件和引數params所描述的一個WindowManager.LayoutParams物件關聯好一個ViewRoot物件root之後,最後還會將這個View對view象和這個WindowManager.LayoutParams物件,以及變數panelParentView所描述的一個父應用程式窗檢視物件,儲存在這個ViewRoot物件root的內部去,這是通過呼叫這個ViewRoot物件root的成員函式setView來實現的,因此,接下來我們就繼續分析ViewRoot類的成員函式setView的實現。

        Step 13. ViewRoot.setView

public final class ViewRoot extends Handler implements ViewParent,
        View.AttachInfo.Callbacks {
    ......

    final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
    ......

    View mView;
    ......

    final View.AttachInfo mAttachInfo;
    ......

    boolean mAdded;
    ......

    public void setView(View view, WindowManager.LayoutParams attrs,
            View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs);
                ......

                mAttachInfo.mRootView = view;
                .......

                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                ......

                requestLayout();
                ......
                try {
                    res = sWindowSession.add(mWindow, mWindowAttributes,
                            getHostVisibility(), mAttachInfo.mContentInsets,
                            mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    ......
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                ......
            }

            ......
        }
    }

    ......
}

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

        引數view所描述的一個View物件會分別被儲存在ViewRoot類的成員變數mView以及成員變數mAttachInfo所描述的一個AttachInfo的成員變數mRootView中,而引數attrs所描述的一個WindowManager.LayoutParams物件的內容會被拷貝到ViewRoot類的成員變數mWindowAttributes中去。

       當引數panelParentView的值不等於null的時候,就表示引數view描述的是一個子應用程式視窗檢視物件。在這種情況下,引數panelParentView描述的就是一個父應用程式視窗檢視物件。這時候我們就需要獲得用來描述這個父應用程式視窗檢視物件的一個型別為W的Binder本地物件的IBinder介面,以便可以儲存在ViewRoot類的成員變數mAttachInfo所描述的一個AttachInfo的成員變數mPanelParentWindowToken中去。這樣以後就可以知道ViewRoot類的成員變數mView所描述的一個子應用程式視窗檢視所屬的父應用程式視窗檢視是什麼了。注意,通過呼叫引數panelParentView的所描述的一個View物件的成員函式getApplicationWindowToken即可以獲得一個對應的W物件的IBinder介面。

       上述操作執行完成之後,ViewRoot類的成員函式setView就可以將成員變數mAdded的值設定為true了,表示當前正在處理的一個ViewRoot物件已經關聯好一個View物件了。接下來,ViewRoot類的成員函式setView還需要執行兩個操作:

       1. 呼叫ViewRoot類的另外一個成員函式requestLayout來請求對應用程式視窗檢視的UI作第一次佈局。

       2. 呼叫ViewRoot類的靜態成員變數sWindowSession所描述的一個型別為Session的Binder代理物件的成員函式add來請求WindowManagerService增加一個WindowState物件,以便可以用來描述當前正在處理的一個ViewRoot所關聯的一個應用程式視窗。

       至此,我們就分析完成Android應用程式視窗檢視物件的建立過程了。在接下來的一篇文章中,我們將會繼續分析Android應用程式視窗與WindowManagerService服務的連線過程,即Android應用程式視窗請求WindowManagerService為其增加一個WindowState物件的過程,而在接下來的兩篇文章中,我們還會分析用來渲染Android應用程式視窗的Surface的建立過程,以及Android應用程式視窗的渲染過程。通過這三個過程的分析,我們就可以看到上述第1點和第2點提到的兩個函式的執行過程,敬請期待!