Activity的setContentView的流程


最簡單的Activity中的內容大致是這樣的:

public class MainActivity extends Activity {  
  
  
    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main_activity);  
    }  
  
}  

setContentView

一般來說我們設置頁面的內容視圖是都是通過setContentView方法,那么我們就以2.3源碼為例就來看看Activity中的setContentView到底做了什么吧。

 1 /** 
 2  * Set the activity content from a layout resource.  The resource will be 
 3  * inflated, adding all top-level views to the activity. 
 4  *  
 5  * @param layoutResID Resource ID to be inflated. 
 6  */  
 7 public void setContentView(int layoutResID) {  
 8     getWindow().setContentView(layoutResID);  
 9 }  
10    
11 public Window getWindow() {  
12     return mWindow;  
13 }  
14   
15   
16 private Window mWindow;  

我們可以看到,實際上調用的mWindow的setContentView方法,在Android Touch事件分發過程這篇文章中我們已經指出Window的實現類為PhoneWindow類

 1 @Override  
 2 public void setContentView(int layoutResID) {  
 3     if (mContentParent == null) {  
 4         installDecor();         // 1、生成DecorView  
 5     } else {  
 6         mContentParent.removeAllViews();  
 7     }  
 8     mLayoutInflater.inflate(layoutResID, mContentParent);// 2、將layoutResId的布局添加到mContentParent中  
 9     final Callback cb = getCallback();  
10     if (cb != null) {  
11         cb.onContentChanged();  
12     }  
13 }  
14     // 構建mDecor對象,並且初始化標題欄和Content Parent(我們要顯示的內容區域)  
15     private void installDecor() {  
16     if (mDecor == null) {  
17         mDecor = generateDecor();          // 3、構建DecorView  
18         mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);  
19         mDecor.setIsRootNamespace(true);  
20     }  
21     if (mContentParent == null) {  
22         mContentParent = generateLayout(mDecor);              // 4、獲取ContentView容器,即顯示內容的區域  
23   
24         mTitleView = (TextView)findViewById(com.android.internal.R.id.title); 5、設置Title等  
25         if (mTitleView != null) {  
26             if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {  
27                 View titleContainer = findViewById(com.android.internal.R.id.title_container);  
28                 if (titleContainer != null) {  
29                     titleContainer.setVisibility(View.GONE);  
30                 } else {  
31                     mTitleView.setVisibility(View.GONE);  
32                 }  
33                 if (mContentParent instanceof FrameLayout) {  
34                     ((FrameLayout)mContentParent).setForeground(null);  
35                 }  
36             } else {  
37                 mTitleView.setText(mTitle);  
38             }  
39         }  
40     }  
41 }  
42   
43     protected DecorView generateDecor() {  
44     return new DecorView(getContext(), -1);    // 構建mDecor對象  
45 }  

我們可以看到,setContentView的基本流程簡單概括就是如下幾步:

 

1、構建mDecor對象。mDecor就是整個窗口的頂層視圖,它主要包含了Title和Content View兩個區域 (參考圖1中的兩個區域 ),Title區域就是我們的標題欄,Content View區域就是顯示我們xml布局內容中的區域。關於mDecor對象更多說明也請參考Android Touch事件分發過程這篇文章;

2、設置一些關於窗口的屬性,初始化標題欄區域和內容顯示區域;

這里比較復雜的就是generateLayout(mDecor)這個函數,我們一起來分析一下吧。

  1 // 返回用於顯示我們設置的頁面內容的ViewGroup容器  
  2 protected ViewGroup generateLayout(DecorView decor) {  
  3    // Apply data from current theme.  
  4    // 1、獲取窗口的Style屬性  
  5    TypedArray a = getWindowStyle();  
  6   
  7    if (false) {  
  8        System.out.println("From style:");  
  9        String s = "Attrs:";  
 10        for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {  
 11            s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="  
 12                    + a.getString(i);  
 13        }  
 14        System.out.println(s);  
 15    }  
 16    // 窗口是否是浮動的  
 17    mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);  
 18    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)  
 19            & (~getForcedWindowFlags());  
 20    if (mIsFloating) {  
 21        setLayout(WRAP_CONTENT, WRAP_CONTENT);  
 22        setFlags(0, flagsToUpdate);  
 23    } else {  
 24        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);  
 25    }  
 26    // 設置是否不顯示title區域  
 27    if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {  
 28        requestFeature(FEATURE_NO_TITLE);  
 29    }  
 30    // 設置全屏的flag  
 31    if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {  
 32        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));  
 33    }  
 34   
 35    if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {  
 36        setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));  
 37    }  
 38   
 39    WindowManager.LayoutParams params = getAttributes();  
 40    // 設置輸入法模式  
 41    if (!hasSoftInputMode()) {  
 42        params.softInputMode = a.getInt(  
 43                com.android.internal.R.styleable.Window_windowSoftInputMode,  
 44                params.softInputMode);  
 45    }  
 46   
 47    if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,  
 48            mIsFloating)) {  
 49        /* All dialogs should have the window dimmed */  
 50        if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {  
 51            params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;  
 52        }  
 53        params.dimAmount = a.getFloat(  
 54                android.R.styleable.Window_backgroundDimAmount, 0.5f);  
 55    }  
 56    // 窗口動畫  
 57    if (params.windowAnimations == 0) {  
 58        params.windowAnimations = a.getResourceId(  
 59                com.android.internal.R.styleable.Window_windowAnimationStyle, 0);  
 60    }  
 61   
 62    // The rest are only done if this window is not embedded; otherwise,  
 63    // the values are inherited from our container.  
 64    if (getContainer() == null) {  
 65        if (mBackgroundDrawable == null) {  
 66            if (mBackgroundResource == 0) {  
 67                mBackgroundResource = a.getResourceId(  
 68                        com.android.internal.R.styleable.Window_windowBackground, 0);  
 69            }  
 70            if (mFrameResource == 0) {  
 71                mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);  
 72            }  
 73            if (false) {  
 74                System.out.println("Background: "  
 75                        + Integer.toHexString(mBackgroundResource) + " Frame: "  
 76                        + Integer.toHexString(mFrameResource));  
 77            }  
 78        }  
 79        mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);  
 80    }  
 81   
 82    // Inflate the window decor.   
 83    // 2、根據一些屬性來選擇不同的頂層視圖布局,例如設置了FEATURE_NO_TITLE的屬性,那么就選擇沒有Title區域的那么布局;  
 84    // layoutResource布局就是整個Activity的布局,其中含有title區域和content區域,content區域就是用來顯示我通過  
 85    // setContentView設置進來的內容區域,也就是我們要顯示的視圖。  
 86   
 87    int layoutResource;  
 88    int features = getLocalFeatures();  
 89    // System.out.println("Features: 0x" + Integer.toHexString(features));  
 90    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
 91        if (mIsFloating) {  
 92            layoutResource = com.android.internal.R.layout.dialog_title_icons;  
 93        } else {  
 94            layoutResource = com.android.internal.R.layout.screen_title_icons;  
 95        }  
 96        // System.out.println("Title Icons!");  
 97    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {  
 98        // Special case for a window with only a progress bar (and title).  
 99        // XXX Need to have a no-title version of embedded windows.  
100        layoutResource = com.android.internal.R.layout.screen_progress;  
101        // System.out.println("Progress!");  
102    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {  
103        // Special case for a window with a custom title.  
104        // If the window is floating, we need a dialog layout  
105        if (mIsFloating) {  
106            layoutResource = com.android.internal.R.layout.dialog_custom_title;  
107        } else {  
108            layoutResource = com.android.internal.R.layout.screen_custom_title;  
109        }  
110    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {  
111        // If no other features and not embedded, only need a title.  
112        // If the window is floating, we need a dialog layout  
113        if (mIsFloating) {  
114            layoutResource = com.android.internal.R.layout.dialog_title;  
115        } else {  
116            layoutResource = com.android.internal.R.layout.screen_title;  
117        }  
118        // System.out.println("Title!");  
119    } else {  
120        // Embedded, so no decoration is needed.  
121        layoutResource = com.android.internal.R.layout.screen_simple;  
122        // System.out.println("Simple!");  
123    }  
124   
125    mDecor.startChanging();  
126    // 3、加載視圖  
127    View in = mLayoutInflater.inflate(layoutResource, null);  
128    // 4、將layoutResource的內容添加到mDecor中  
129    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  
130    // 5、獲取到我們的內容顯示區域,這是一個ViewGroup類型的,其實是FrameLayout  
131    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
132    if (contentParent == null) {  
133        throw new RuntimeException("Window couldn't find content container view");  
134    }  
135   
136    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {  
137        ProgressBar progress = getCircularProgressBar(false);  
138        if (progress != null) {  
139            progress.setIndeterminate(true);  
140        }  
141    }  
142   
143    // 6、設置一些背景、title等屬性  
144    // Remaining setup -- of background and title -- that only applies  
145    // to top-level windows.  
146    if (getContainer() == null) {  
147        Drawable drawable = mBackgroundDrawable;  
148        if (mBackgroundResource != 0) {  
149            drawable = getContext().getResources().getDrawable(mBackgroundResource);  
150        }  
151        mDecor.setWindowBackground(drawable);  
152        drawable = null;  
153        if (mFrameResource != 0) {  
154            drawable = getContext().getResources().getDrawable(mFrameResource);  
155        }  
156        mDecor.setWindowFrame(drawable);  
157   
158        // System.out.println("Text=" + Integer.toHexString(mTextColor) +  
159        // " Sel=" + Integer.toHexString(mTextSelectedColor) +  
160        // " Title=" + Integer.toHexString(mTitleColor));  
161   
162        if (mTitleColor == 0) {  
163            mTitleColor = mTextColor;  
164        }  
165   
166        if (mTitle != null) {  
167            setTitle(mTitle);  
168        }  
169        setTitleColor(mTitleColor);  
170    }  
171   
172    mDecor.finishChanging();  
173   
174    return contentParent;  

其實也就是這么幾個步驟:

 

1、獲取用戶設置的一些屬性與Flag;

2、根據一些屬性選擇不同的頂層視圖布局,例如FEATURE_NO_TITLE則選擇沒有title的布局文件等;這里我們看一個與圖1中符合的頂層布局吧,即layoutResource = c

 1 <?xml version="1.0" encoding="utf-8"?>  
 2   
 3 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
 4     android:orientation="vertical"  
 5     android:fitsSystemWindows="true">  
 6     <!-- Popout bar for action modes -->  
 7     <ViewStub android:id="@+id/action_mode_bar_stub"  
 8               android:inflatedId="@+id/action_mode_bar"  
 9               android:layout="@layout/action_mode_bar"  
10               android:layout_width="match_parent"  
11               android:layout_height="wrap_content" />  
12     <!--  title區域-->  
13     <FrameLayout  
14         android:layout_width="match_parent"   
15         android:layout_height="?android:attr/windowTitleSize"  
16         style="?android:attr/windowTitleBackgroundStyle">  
17         <TextView android:id="@android:id/title"   
18             style="?android:attr/windowTitleStyle"  
19             android:background="@null"  
20             android:fadingEdge="horizontal"  
21             android:gravity="center_vertical"  
22             android:layout_width="match_parent"  
23             android:layout_height="match_parent" />  
24     </FrameLayout>  
25     <!--內容顯示區域, 例如main_activity.xml布局就會被放到這個ViewGroup下面 -->  
26     <FrameLayout android:id="@android:id/content"  
27         android:layout_width="match_parent"   
28         android:layout_height="0dip"  
29         android:layout_weight="1"  
30         android:foregroundGravity="fill_horizontal|top"  
31         android:foreground="?android:attr/windowContentOverlay" />  
32 </LinearLayout>  

我們可以看到有兩個區域,即title區域和content區域,generateLayout函數中的

// 5、獲取到我們的內容顯示區域,這是一個ViewGroup類型的,其實是FrameLayout  
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  

獲取的就是xml中id為content的FrameLayout,這個content就是我們的內容顯示區域。整個布局對應的效果如下 :

這兩個區域就組成了mDecor視圖,我們的main_activity.xml就是放在內容視圖這個區域的。

3、加載頂層布局文件,轉換為View,將其添加到mDecor中;

4、獲取內容容器Content Parent,即用於顯示我們的內容的區域;

5、設置一些背景圖和title等。

在經過這幾步,我們就得到了mContentParent,這就是用來裝載我們的視圖的ViewGroup。再回過頭來看setContentView函數:

 1 public void setContentView(int layoutResID) {  
 2     if (mContentParent == null) {  
 3         installDecor();         // 1、生成DecorView,並且根據窗口屬性加載頂級視圖布局、獲取mContentParent、設置一些基本屬性等  
 4     } else {  
 5         mContentParent.removeAllViews();  
 6     }  
 7     mLayoutInflater.inflate(layoutResID, mContentParent);// 2、將layoutResId加載到mContentParent中,這里的layoutResId就是我們的main_activity.xml  
 8     final Callback cb = getCallback();  
 9     if (cb != null) {  
10         cb.onContentChanged();  
11     }  
12 }  

我們看看LayoutInflater的inflate函數吧 : 

 1 /** 
 2  * Inflate a new view hierarchy from the specified xml resource. Throws 
 3  * {@link InflateException} if there is an error. 
 4  *  
 5  * @param resource ID for an XML layout resource to load (e.g., 
 6  *        <code>R.layout.main_page</code>) 
 7  * @param root Optional view to be the parent of the generated hierarchy. 
 8  * @return The root View of the inflated hierarchy. If root was supplied, 
 9  *         this is the root View; otherwise it is the root of the inflated 
10  *         XML file. 
11  */  
12 public View inflate(int resource, ViewGroup root) {  
13     return inflate(resource, root, root != null);  
14 }  
15   
16 /** 
17  * Inflate a new view hierarchy from the specified xml resource. Throws 
18  * {@link InflateException} if there is an error. 
19  *  
20  * @param resource ID for an XML layout resource to load (e.g., 
21  *        <code>R.layout.main_page</code>) 
22  * @param root Optional view to be the parent of the generated hierarchy (if 
23  *        <em>attachToRoot</em> is true), or else simply an object that 
24  *        provides a set of LayoutParams values for root of the returned 
25  *        hierarchy (if <em>attachToRoot</em> is false.) 
26  * @param attachToRoot Whether the inflated hierarchy should be attached to 
27  *        the root parameter? If false, root is only used to create the 
28  *        correct subclass of LayoutParams for the root view in the XML. 
29  * @return The root View of the inflated hierarchy. If root was supplied and 
30  *         attachToRoot is true, this is root; otherwise it is the root of 
31  *         the inflated XML file. 
32  */  
33 public View inflate(int resource, ViewGroup root, boolean attachToRoot) {  
34     if (DEBUG) System.out.println("INFLATING from resource: " + resource);  
35     XmlResourceParser parser = getContext().getResources().getLayout(resource);  
36     try {  
37         return inflate(parser, root, attachToRoot);  
38     } finally {  
39         parser.close();  
40     }  
41 }  

實際上就是將layoutResId這個布局的視圖附加到mContentParent中。

DecorView

移步 : DecorView 。

ViewGroup

ViewGroup從語義上來說就是視圖組,它也繼承自View類,它其實就是視圖的容器。我們看官方的定義 : 

* A ViewGroup is a special view that can contain other views  
* (called children.) The view group is the base class for layouts and views  
* containers. This class also defines the  
* {@link android.view.ViewGroup.LayoutParams} class which serves as the base  
* class for layouts parameters.  

我們通過ViewGroup來組織、管理子視圖,例如我們常見的FrameLayout、LinearLayout、RelativeLayout、ListView等都是ViewGroup類型,總之只要能包含其他View或者ViewGroup的都是ViewGroup類型。使用ViewGroup來構建視圖樹。

 

 



View

View就是UI界面上的一個可見的組件,任何在UI上可見的都為View的子類。我們看官方定義 :

 

TextView、Button、ImageView、FrameLayout、LinearLayout、ListView等都是View的子類。

這樣,ViewGroup類型的視圖管理嵌套在里面的ViewGroup以及View控件組成了豐富多彩的用戶界面。例如我們開篇的Hello World的視圖結構是這樣的 :

總結 

整個窗口由Title區域和Content區域組成,Content區域就是我們要顯示內容的區域,在這個區域中mContentParent是根ViewGroup,由mContentParent組織、管理其子視圖,從而構建整個視圖樹。當Activity啟動時,就將這些內容就會顯示在手機上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM