天天看點

Android應用程式視窗(Activity)的視圖對象(View)的建立過程分析

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

圖1 DecorView類的實作

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

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

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

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

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

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

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

       Step 1. ActivityThread.handleLaunchActivity

1

2

3

4

5

6

7

8

9

10

11

12

13

14

<code>public</code> <code>final</code> <code>class</code> <code>ActivityThread {</code>

<code>    </code><code>......</code>

<code>    </code><code>private</code> <code>final</code> <code>void</code> <code>handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {</code>

<code>        </code><code>......</code>

<code>        </code><code>Activity a = performLaunchActivity(r, customIntent);</code>

<code>        </code><code>if</code> <code>(a != </code><code>null</code><code>) {</code>

<code>            </code><code>......</code>

<code>            </code><code>handleResumeActivity(r.token, </code><code>false</code><code>, r.isForward);</code>

<code>        </code><code>}</code>

<code>    </code><code>}</code>

<code>}</code>

       這個函數定義在檔案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

       Step 3. Activity.onCreate

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

<code>public</code> <code>class</code> <code>Hello </code><code>extends</code> <code>Activity </code><code>implements</code> <code>OnClickListener {</code>

<code>                                                                </code> 

<code>    </code><code>/** Called when the activity is first created. */</code>

<code>    </code><code>@Override</code>

<code>    </code><code>public</code> <code>void</code> <code>onCreate(Bundle savedInstanceState) {</code>

<code>        </code><code>super</code><code>.onCreate(savedInstanceState);</code>

<code>        </code><code>setContentView(R.layout.main);</code>

<code>                                                            </code> 

      其中,調用從父類Activity繼承下來的成員函數setContentView就是用來建立應用程式視窗視圖對象的。

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

      Step 4. Activity.setContentView

15

16

<code>public</code> <code>class</code> <code>Activity </code><code>extends</code> <code>ContextThemeWrapper</code>

<code>        </code><code>implements</code> <code>LayoutInflater.Factory,</code>

<code>        </code><code>Window.Callback, KeyEvent.Callback,</code>

<code>        </code><code>OnCreateContextMenuListener, ComponentCallbacks {</code>

<code>    </code><code>private</code> <code>Window mWindow;</code>

<code>    </code><code>public</code> <code>Window getWindow() {</code>

<code>        </code><code>return</code> <code>mWindow;</code>

<code>    </code><code>public</code> <code>void</code> <code>setContentView(</code><code>int</code> <code>layoutResID) {</code>

<code>        </code><code>getWindow().setContentView(layoutResID);</code>

       Activity類的成員函數setContentView首先調用另外一個成員函數getWindow來獲得成員變量mWindow所描述的一個視窗對象,接着再調用這個視窗對象的成員函數setContentView來執行建立應用程式視窗視圖對象的工作。

       Step 5. PhoneWindow.setContentView

17

18

19

20

21

<code>public</code> <code>class</code> <code>PhoneWindow </code><code>extends</code> <code>Window </code><code>implements</code> <code>MenuBuilder.Callback {</code>

<code>    </code><code>// This is the view in which the window contents are placed. It is either</code>

<code>    </code><code>// mDecor itself, or a child of mDecor where the contents go.</code>

<code>    </code><code>private</code> <code>ViewGroup mContentParent;</code>

<code>        </code><code>if</code> <code>(mContentParent == </code><code>null</code><code>) {</code>

<code>            </code><code>installDecor();</code>

<code>        </code><code>} </code><code>else</code> <code>{</code>

<code>            </code><code>mContentParent.removeAllViews();</code>

<code>        </code><code>mLayoutInflater.inflate(layoutResID, mContentParent);</code>

<code>        </code><code>final</code> <code>Callback cb = getCallback();</code>

<code>        </code><code>if</code> <code>(cb != </code><code>null</code><code>) {</code>

<code>            </code><code>cb.onContentChanged();</code>

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

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

     接下來,我們就繼續分析PhoneWindow類的成員函數installDecor的實作,以便可以繼續了解應用程式視窗視圖對象的建立過程。

     Step 6. PhoneWindow.installDecor

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

<code>    </code><code>// This is the top-level view of the window, containing the window decor.</code>

<code>    </code><code>private</code> <code>DecorView mDecor;</code>

<code>    </code><code>private</code> <code>TextView mTitleView;</code>

<code>    </code><code>private</code> <code>CharSequence mTitle = </code><code>null</code><code>;</code>

<code>    </code><code>private</code> <code>void</code> <code>installDecor() {</code>

<code>        </code><code>if</code> <code>(mDecor == </code><code>null</code><code>) {</code>

<code>            </code><code>mDecor = generateDecor();</code>

<code>            </code><code>mContentParent = generateLayout(mDecor);</code>

<code>            </code><code>mTitleView = (TextView)findViewById(com.android.internal.R.id.title);</code>

<code>            </code><code>if</code> <code>(mTitleView != </code><code>null</code><code>) {</code>

<code>                </code><code>if</code> <code>((getLocalFeatures() &amp; (</code><code>1</code> <code>&lt;&lt; FEATURE_NO_TITLE)) != </code><code>0</code><code>) {</code>

<code>                    </code><code>View titleContainer = findViewById(com.android.internal.R.id.title_container);</code>

<code>                    </code><code>if</code> <code>(titleContainer != </code><code>null</code><code>) {</code>

<code>                        </code><code>titleContainer.setVisibility(View.GONE);</code>

<code>                    </code><code>} </code><code>else</code> <code>{</code>

<code>                        </code><code>mTitleView.setVisibility(View.GONE);</code>

<code>                    </code><code>}</code>

<code>                    </code><code>if</code> <code>(mContentParent </code><code>instanceof</code> <code>FrameLayout) {</code>

<code>                        </code><code>((FrameLayout)mContentParent).setForeground(</code><code>null</code><code>);</code>

<code>                </code><code>} </code><code>else</code> <code>{</code>

<code>                    </code><code>mTitleView.setText(mTitle);</code>

<code>                </code><code>}</code>

<code>            </code><code>}</code>

       由于我們是在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

<code>    </code><code>final</code> <code>void</code> <code>handleResumeActivity(IBinder token, </code><code>boolean</code> <code>clearHide, </code><code>boolean</code> <code>isForward) {</code>

<code>        </code><code>ActivityClientRecord r = performResumeActivity(token, clearHide);</code>

<code>        </code><code>if</code> <code>(r != </code><code>null</code><code>) {</code>

<code>            </code><code>final</code> <code>Activity a = r.activity;</code>

<code>            </code><code>// If the window hasn't yet been added to the window manager,</code>

<code>            </code><code>// and this guy didn't finish itself or start another activity,</code>

<code>            </code><code>// then go ahead and add the window.</code>

<code>            </code><code>boolean</code> <code>willBeVisible = !a.mStartedActivity;</code>

<code>            </code><code>if</code> <code>(!willBeVisible) {</code>

<code>                </code><code>try</code> <code>{</code>

<code>                    </code><code>willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(</code>

<code>                            </code><code>a.getActivityToken());</code>

<code>                </code><code>} </code><code>catch</code> <code>(RemoteException e) {</code>

<code>            </code><code>if</code> <code>(r.window == </code><code>null</code> <code>&amp;&amp; !a.mFinished &amp;&amp; willBeVisible) {</code>

<code>                </code><code>r.window = r.activity.getWindow();</code>

<code>                </code><code>View decor = r.window.getDecorView();</code>

<code>                </code><code>decor.setVisibility(View.INVISIBLE);</code>

<code>                </code><code>ViewManager wm = a.getWindowManager();</code>

<code>                </code><code>WindowManager.LayoutParams l = r.window.getAttributes();</code>

<code>                </code><code>a.mDecor = decor;</code>

<code>                </code><code>l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;</code>

<code>                </code><code>......</code>

<code>                </code><code>if</code> <code>(a.mVisibleFromClient) {</code>

<code>                    </code><code>a.mWindowAdded = </code><code>true</code><code>;</code>

<code>                    </code><code>wm.addView(decor, l);</code>

<code>                                        </code> 

       這個函數定義在檔案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元件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

       這一步執完成之後,傳回到前面的Step 7中,即ActivityThread類的成員函數handleResumeActivity中,接下來就會繼續調用前面所獲得的一個PhoneWindow對象的成員函數getDecorView來獲得目前正在激活的Activity元件所關聯的一個應用程式視窗視圖對象。

       Step 9. PhoneWindow.getDecorView

<code>    </code><code>public</code> <code>final</code> <code>View getDecorView() {</code>

<code>        </code><code>return</code> <code>mDecor;</code>

       PhoneWindow類的成員函數getDecorView首先判斷成員變量mDecor的值是否等于null。如果是的話,那麼就說明目前正在處理的應用程式視窗還沒有建立視圖對象。這時候就會調用另外一個成員函數installDecor來建立這個視圖對象。從前面的調用過程可以知道,目前正在處理的應用程式視窗已經建立過視圖對象,是以,這裡的成員變量mDecor的值不等于null,PhoneWindow類的成員函數getDecorView直接将它傳回給調用者。

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

       Step 10. Activity.getWindowManager

<code>    </code><code>private</code> <code>WindowManager mWindowManager;</code>

<code>    </code><code>public</code> <code>WindowManager getWindowManager() {</code>

<code>        </code><code>return</code> <code>mWindowManager;</code>

       這一步執完成之後,傳回到前面的Step 7中,即ActivityThread類的成員函數handleResumeActivity中,接下來就會繼續調用前面所獲得的一個LocalWindowManager對象的成員函數addView來為目前正在激活的Activity元件的應用程式視窗視圖對象關聯一個ViewRoot對象。

       Step 11. LocalWindowManager.addView

<code>public</code> <code>abstract</code> <code>class</code> <code>Window {</code>

<code>    </code><code>private</code> <code>class</code> <code>LocalWindowManager </code><code>implements</code> <code>WindowManager {</code>

<code>        </code><code>public</code> <code>final</code> <code>void</code> <code>addView(View view, ViewGroup.LayoutParams params) {</code>

<code>            </code><code>mWindowManager.addView(view, params);</code>

<code>        </code><code>private</code> <code>final</code> <code>WindowManager mWindowManager;</code>

<code>               </code> 

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

       Step 12. WindowManagerImpl.addView

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

<code>public</code> <code>class</code> <code>WindowManagerImpl </code><code>implements</code> <code>WindowManager {</code>

<code>    </code><code>public</code> <code>void</code> <code>addView(View view, ViewGroup.LayoutParams params)</code>

<code>    </code><code>{</code>

<code>        </code><code>addView(view, params, </code><code>false</code><code>);</code>

<code>    </code><code>private</code> <code>void</code> <code>addView(View view, ViewGroup.LayoutParams params, </code><code>boolean</code> <code>nest)</code>

<code>        </code><code>final</code> <code>WindowManager.LayoutParams wparams</code>

<code>                </code><code>= (WindowManager.LayoutParams)params;</code>

<code>        </code><code>ViewRoot root;</code>

<code>        </code><code>View panelParentView = </code><code>null</code><code>;</code>

<code>        </code><code>synchronized</code> <code>(</code><code>this</code><code>) {</code>

<code>            </code><code>// Here's an odd/questionable case: if someone tries to add a</code>

<code>            </code><code>// view multiple times, then we simply bump up a nesting count</code>

<code>            </code><code>// and they need to remove the view the corresponding number of</code>

<code>            </code><code>// times to have it actually removed from the window manager.</code>

<code>            </code><code>// This is useful specifically for the notification manager,</code>

<code>            </code><code>// which can continually add/remove the same view as a</code>

<code>            </code><code>// notification gets updated.</code>

<code>            </code><code>int</code> <code>index = findViewLocked(view, </code><code>false</code><code>);</code>

<code>            </code><code>if</code> <code>(index &gt;= </code><code>0</code><code>) {</code>

<code>                </code><code>if</code> <code>(!nest) {</code>

<code>                    </code><code>throw</code> <code>new</code> <code>IllegalStateException(</code><code>"View "</code> <code>+ view</code>

<code>                            </code><code>+ </code><code>" has already been added to the window manager."</code><code>);</code>

<code>                </code><code>root = mRoots[index];</code>

<code>                </code><code>root.mAddNesting++;</code>

<code>                </code><code>// Update layout parameters.</code>

<code>                </code><code>view.setLayoutParams(wparams);</code>

<code>                </code><code>root.setLayoutParams(wparams, </code><code>true</code><code>);</code>

<code>                </code><code>return</code><code>;</code>

<code>            </code><code>// If this is a panel window, then find the window it is being</code>

<code>            </code><code>// attached to for future reference.</code>

<code>            </code><code>if</code> <code>(wparams.type &gt;= WindowManager.LayoutParams.FIRST_SUB_WINDOW &amp;&amp;</code>

<code>                    </code><code>wparams.type &lt;= WindowManager.LayoutParams.LAST_SUB_WINDOW) {</code>

<code>                </code><code>final</code> <code>int</code> <code>count = mViews != </code><code>null</code> <code>? mViews.length : </code><code>0</code><code>;</code>

<code>                </code><code>for</code> <code>(</code><code>int</code> <code>i=</code><code>0</code><code>; i&lt;count; i++) {</code>

<code>                    </code><code>if</code> <code>(mRoots[i].mWindow.asBinder() == wparams.token) {</code>

<code>                        </code><code>panelParentView = mViews[i];</code>

<code>            </code><code>root = </code><code>new</code> <code>ViewRoot(view.getContext());</code>

<code>            </code><code>root.mAddNesting = </code><code>1</code><code>;</code>

<code>            </code><code>view.setLayoutParams(wparams);</code>

<code>            </code><code>if</code> <code>(mViews == </code><code>null</code><code>) {</code>

<code>                </code><code>index = </code><code>1</code><code>;</code>

<code>                </code><code>mViews = </code><code>new</code> <code>View[</code><code>1</code><code>];</code>

<code>                </code><code>mRoots = </code><code>new</code> <code>ViewRoot[</code><code>1</code><code>];</code>

<code>                </code><code>mParams = </code><code>new</code> <code>WindowManager.LayoutParams[</code><code>1</code><code>];</code>

<code>            </code><code>} </code><code>else</code> <code>{</code>

<code>                </code><code>index = mViews.length + </code><code>1</code><code>;</code>

<code>                </code><code>Object[] old = mViews;</code>

<code>                </code><code>mViews = </code><code>new</code> <code>View[index];</code>

<code>                </code><code>System.arraycopy(old, </code><code>0</code><code>, mViews, </code><code>0</code><code>, index-</code><code>1</code><code>);</code>

<code>                </code><code>old = mRoots;</code>

<code>                </code><code>mRoots = </code><code>new</code> <code>ViewRoot[index];</code>

<code>                </code><code>System.arraycopy(old, </code><code>0</code><code>, mRoots, </code><code>0</code><code>, index-</code><code>1</code><code>);</code>

<code>                </code><code>old = mParams;</code>

<code>                </code><code>mParams = </code><code>new</code> <code>WindowManager.LayoutParams[index];</code>

<code>                </code><code>System.arraycopy(old, </code><code>0</code><code>, mParams, </code><code>0</code><code>, index-</code><code>1</code><code>);</code>

<code>            </code><code>index--;</code>

<code>            </code><code>mViews[index] = view;</code>

<code>            </code><code>mRoots[index] = root;</code>

<code>            </code><code>mParams[index] = wparams;</code>

<code>        </code><code>// do this last because it fires off messages to start doing things</code>

<code>        </code><code>root.setView(view, wparams, panelParentView);</code>

<code>    </code><code>private</code> <code>View[] mViews;</code>

<code>    </code><code>private</code> <code>ViewRoot[] mRoots;</code>

<code>    </code><code>private</code> <code>WindowManager.LayoutParams[] mParams;</code>

      這個函數定義在檔案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

<code>public</code> <code>final</code> <code>class</code> <code>ViewRoot </code><code>extends</code> <code>Handler </code><code>implements</code> <code>ViewParent,</code>

<code>        </code><code>View.AttachInfo.Callbacks {</code>

<code>    </code><code>final</code> <code>WindowManager.LayoutParams mWindowAttributes = </code><code>new</code> <code>WindowManager.LayoutParams();</code>

<code>    </code><code>View mView;</code>

<code>    </code><code>final</code> <code>View.AttachInfo mAttachInfo;</code>

<code>    </code><code>boolean</code> <code>mAdded;</code>

<code>    </code><code>public</code> <code>void</code> <code>setView(View view, WindowManager.LayoutParams attrs,</code>

<code>            </code><code>View panelParentView) {</code>

<code>            </code><code>if</code> <code>(mView == </code><code>null</code><code>) {</code>

<code>                </code><code>mView = view;</code>

<code>                </code><code>mWindowAttributes.copyFrom(attrs);</code>

<code>                </code><code>mAttachInfo.mRootView = view;</code>

<code>                </code><code>.......</code>

<code>                </code><code>if</code> <code>(panelParentView != </code><code>null</code><code>) {</code>

<code>                    </code><code>mAttachInfo.mPanelParentWindowToken</code>

<code>                            </code><code>= panelParentView.getApplicationWindowToken();</code>

<code>                </code><code>mAdded = </code><code>true</code><code>;</code>

<code>                </code><code>requestLayout();</code>

<code>                    </code><code>res = sWindowSession.add(mWindow, mWindowAttributes,</code>

<code>                            </code><code>getHostVisibility(), mAttachInfo.mContentInsets,</code>

<code>                            </code><code>mInputChannel);</code>

<code>                    </code><code>mAdded = </code><code>false</code><code>;</code>

<code>                    </code><code>mView = </code><code>null</code><code>;</code>

<code>                    </code><code>......</code>

<code>                    </code><code>throw</code> <code>new</code> <code>RuntimeException(</code><code>"Adding window failed"</code><code>, e);</code>

<code>                </code><code>} </code><code>finally</code> <code>{</code>

<code>                    </code><code>if</code> <code>(restore) {</code>

<code>                        </code><code>attrs.restore();</code>

       這個函數定義在檔案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點提到的兩個函數的執行過程,敬請期待!

本文轉自 Luoshengyang 51CTO部落格,原文連結:http://blog.51cto.com/shyluo/1242881,如需轉載請自行聯系原作者

繼續閱讀