zoukankan      html  css  js  c++  java
  • 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启动时,就将这些内容就会显示在手机上。

  • 相关阅读:
    标准Gitlab命令行操作指导
    ssh登录巨慢加速验证
    ssh远程主机执行命令或脚本
    ssh创建与添加密钥开启免密登陆 免确认机器指纹参数
    linux免密传输文件 nc
    Linux 查看实时网卡流量的方法 网速 nload sar iftop dstat
    Mysql 常用命令
    Redis 常用命令整理
    shell sed -i 指定内容追加.
    java 复制指定目录中的所有文件和文件夹到另一个指定文件夹中
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/8394775.html
Copyright © 2011-2022 走看看