Android View系统分析之从setContentView说开来(一)

今天是中秋节,先祝各位中秋快乐吧。作为北漂的人,对于过节最大的感触就是没气 氛~ 中秋是一个特别重要的节日,小的时候过中秋都是特别快乐的,有月饼吃,和家人上月,过完中秋要去亲戚家拜访等等。现在对于我们来说也就是一个节日罢了,窝 在家里看点电视、看点书、吃顿好的,虽说生活好了,但日子过得没啥滋味。废话不多说,开始今天的学习吧。

Hello World

对于学习编程的人而言,大多数人第一个项目都是著名的"Hello World",自从K&R开了这个先例,后面的人就很少有打破的。学习Android开发也是这样,我们第一次创建应用,估计也就是运行程序,然 后在模拟器上输出一个Hello World,我们看到最简单的Activity中的内容大致是这样的:

[java] view plaincopy

  1. public class MainActivity extends Activity {
  2. @Override
  3. public void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.main_activity);
  6. }
  7. }

main_activity.xml大致是这样的 :

[java] view plaincopy

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:gravity="center" >
  6. <TextView
  7. android:layout_width="match_parent"
  8. android:layout_height="wrap_content"
  9. android:text="@string/hello_world" />
  10. </RelativeLayout>

然后执行程序,我们就可以看到模拟器中的Hello World了。


  图1

我们在整个过程中做的事情很少,在我们的main_activity.xml我们只有一个显示文本的TextView,但是在上图中却还多了一个
title。我们好奇的是整个过程是怎么工作的?对于大型系统来说细节总是复杂的,在下水平有限,所以我们今天只来理一下它的基本脉络。

setContentView

一般来说我们设置页面的内容视图是都是通过setContentView方法,那么我们就以2.3源码为例就来看看Activity中的setContentView到底做了什么吧。

[java] view plaincopy

  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. public Window getWindow() {
  11. return mWindow;
  12. }
  13. private Window mWindow;

我们可以看到,实际上调用的mWindow的setContentView方法,在Android Touch事件分发过程这篇文章中我们已经指出Window的实现类为PhoneWindow类,我们就移步到PhoneWindow的setConentView吧,核心源码如下 :

[java] view plaincopy

  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. mTitleView = (TextView)findViewById(com.android.internal.R.id.title); 5、设置Title等
  24. if (mTitleView != null) {
  25. if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
  26. View titleContainer = findViewById(com.android.internal.R.id.title_container);
  27. if (titleContainer != null) {
  28. titleContainer.setVisibility(View.GONE);
  29. } else {
  30. mTitleView.setVisibility(View.GONE);
  31. }
  32. if (mContentParent instanceof FrameLayout) {
  33. ((FrameLayout)mContentParent).setForeground(null);
  34. }
  35. } else {
  36. mTitleView.setText(mTitle);
  37. }
  38. }
  39. }
  40. }
  41. protected DecorView generateDecor() {
  42. return new DecorView(getContext(), -1);    // 构建mDecor对象
  43. }

我们可以看到,setContentView的基本流程简单概括就是如下几步:

1、
构建mDecor对象。mDecor就是整个窗口的顶层视图,它主要包含了Title和Content View两个区域 (参考图1中的两个区域
),Title区域就是我们的标题栏,Content View区域就是显示我们xml布局内容中的区域。关于mDecor对象更多说明也请参考Android Touch事件分发过程这篇文章;

2、设置一些关于窗口的属性,初始化标题栏区域和内容显示区域;

这里比较复杂的就是generateLayout(mDecor)这个函数,我们一起来分析一下吧。

[java] view plaincopy

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

其实也就是这么几个步骤:

1、获取用户设置的一些属性与Flag;

2、
根据一些属性选择不同的顶层视图布局,例如FEATURE_NO_TITLE则选择没有title的布局文件等;这里我们看一个与图1中符合的顶层布局
吧,即layoutResource = com.android.internal.R.layout.screen_title的情形:

[html] view plaincopy

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

我们可以看到有两个区域,即title区域和content区域,generateLayout函数中的

[java] view plaincopy

  1. // 5、获取到我们的内容显示区域,这是一个ViewGroup类型的,其实是FrameLayout
  2. 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函数:

[java] view plaincopy

  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函数吧 :

[java] view plaincopy

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

实际上就是将layoutResId这个布局的视图附加到mContentParent中。

DecorView

移步 : DecorView 。

ViewGroup

ViewGroup从语义上来说就是视图组,它也继承自View类,它其实就是视图的容器。我们看官方的定义 :

[java] view plaincopy

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


们通过ViewGroup来组织、管理子视图,例如我们常见的FrameLayout、LinearLayout、RelativeLayout、
ListView等都是ViewGroup类型,总之只要能包含其他View或者ViewGroup的都是ViewGroup类型。使用ViewGroup来构建视图树。

View

View就是UI界面上的一个可见的组件,任何在UI上可见的都为View的子类。我们看官方定义 :

[java] view plaincopy

  1. * This class represents the basic building block for user interface components. A View
  2. * occupies a rectangular area on the screen and is responsible for drawing and
  3. * event handling. View is the base class for <em>widgets</em>, which are
  4. * used to create interactive UI components (buttons, text fields, etc.). The
  5. * {@link android.view.ViewGroup} subclass is the base class for <em>layouts</em>, which
  6. * are invisible containers that hold other Views (or other ViewGroups) and define
  7. * their layout properties.

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

总结


个窗口由Title区域和Content区域组成,Content区域就是我们要显示内容的区域,在这个区域中mContentParent是根
ViewGroup,由mContentParent组织、管理其子视图,从而构建整个视图树。当Activity启动时,就将这些内容就会显示在手机
上。

时间: 2024-08-28 16:52:32

Android View系统分析之从setContentView说开来(一)的相关文章

Android View系统分析之二View与ViewGroup

目录 在Android View系统分析之从setContentView说开来(一)一文中,我们从setContentView开始阐述了Android中的视图层次,从设置内容布局到整个视图层次的建立的过程.并且对View和ViewGroup的关系进行了简单的介绍,今天我们继续来深入的了解Android中的View和ViewGroup. ViewGroup与View的关系 我们在定义一个布局时,在它的顶层通常都是使用LinearLayout或者RelativeLayout等组件来包装一些子控件,例

Android View系统分析之三Activity的启动与显示

前言 在Android View系统分析之从setContentView说开来(一)与Android View系统分析之二View与ViewGroup中我们已经简单介绍了一个Activity的UI内容与视图树的组成关系,即View与ViewGroup组成了Activity的可视化视图树,然后将该视图树添加到Activity中的DecorView的content区域,这样整个Activity的UI就填充完成了.那么一个进程和Activity又是如何启动?Activity的UI内容又是如何显示在屏幕

Android View视图系统分析和Scroller和OverScroller分析

Android  View视图系统分析和Scroller和OverScroller分析 View  视图分析 首先,我们知道.在Android中全部的视图资源(无论是Layout还是View),终于的父类都是View类.各式各样的Layout仅仅是对ViewGroup的一中特别的实现.各种View也仅仅是View的特别实现. 而ViewGroup也是对于View的一种实现.所以说全部的View元素在根本上都是一样的.当然这并不等于说View == ViewGroup,就好比仅仅有ViewGrou

Android View 事件分发机制 源码解析 (上)

一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个MyButton继承Button,然后把跟事件传播有关的方法进行复写,然后添加上日志~ MyButton [java] view plain copy package com.example.zhy_event03; import android.content.Context; import andr

Android View 触摸事件传递机制

PS:以现在的眼光看以前写的博客感觉写的很烂,或许或一段时间再看现在的博客会有同样的感觉.所以每时每刻都去学习,去发现和理解新的东西. 引言 由于之前写的一篇关于Android事件传递顺序的博客质量太差,可能是理解不到位的原因,故最近又花了许多时间再次去看Android源码,看完之后有了新的理解,所以打算重新整理这篇博客.理解Android触摸事件传递机制有助于日后的开发以及自定义一些手势效果等.注意:这篇博客是基于Android2.0源码来分析的,不管老版本还是新版本的Android,其内部触

Android View的事件分发机制

准备了一阵子,一直想写一篇事件分发的文章总结一下.这个知识点实在是太重要了. 一个应用的布局是丰富的,有TextView,ImageView,Button等.这些子View的外层还有ViewGroup.如RelativeLayout.LinearLayout.作为一个开发人员,我们会思考.当点击一个button,Android系统是如何确定我点的就是button而不是TextView的?然后还正确的响应了button的点击事件. 内部经过了一系列什么过程呢? 先铺垫一些知识能更加清晰的理解事件分

android 细节之android.view.InflateException: Binary XML file line #95: Error inflating class(out of m)

今天的异常很有意思,叫做android.view.InflateException: Binary XML file line #95: Error inflating class(out of memory) . 其实是因为out of memory,导致 xml是不可能被充气成功,因此activity的onCreate方法中, setContentView(R.layout.***)也就不可能成功调用. 他出现在我有多个教学动画,并且播放的动画,是基于imageView,imageView的

Android view 的事件分发机制

1 事件的传递顺序是 Activity -> Window -> 顶层View touch 事件产生后,最先由 activity 的 dispatchTouchEvent 处理 /** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to

Android View的生命周期详解

View生命周期相关方法: onFinishInflate() 当View中所有的子控件均被映射成xml后触发 onMeasure( int ,  int ) 确定所有子元素的大小 onLayout( boolean ,  int ,  int ,  int ,  int ) 当View分配所有的子元素的大小和位置时触发 onSizeChanged( int ,  int ,  int ,  int ) 当view的大小发生变化时触发 onDraw(Canvas) view渲染内容的细节 onK