[关闭]
@linux1s1s 2019-02-14T02:29:27.000000Z 字数 5203 阅读 2705

Android View 解析初步

AndroidView 2015-04


setContentView

Android遵循MVC框架,Android的项目中UI是通过XML开发的,业务逻辑通过Java实现,所以项目中才会有java目录用于存放业务逻辑代码,而res目录用于存放XML文件,而这部分XML是如何转换成UI的?这个问题比较大,这里仅仅简单说一下:先看一下我们熟知的 setContentView(...)

  1. public class MainActivity extends ActionBarActivity{
  2. @Override
  3. public void onActivityCreated(Bundle savedInstanceState)
  4. {
  5. super.onActivityCreated(savedInstanceState);
  6. setContentView(R.layout.activity_main);
  7. }
  8. }

我们继续追踪下去:(注意此处的ActionBarActivity引用的是v7包)

  1. public class ActionBarActivity extends FragmentActivity implements Callback, SupportParentable, DelegateProvider {
  2. ActionBarActivityDelegate mImpl;
  3. public ActionBarActivity() {
  4. }
  5. public void setContentView(int layoutResID) {
  6. this.mImpl.setContentView(layoutResID);
  7. }
  8. }

为了兼容API level 7以上即Android2.1 而设计的,很明显这里ActionBarActivityDelegate是个兼容代理类,进入到这个类里面看一下setContentView实现,发现是个抽象方法,那么找一下实现类即可,这里一共有两个实现类,我们列举一下:
ActionBarActivityDelegateICS 实现类

  1. class ActionBarActivityDelegateICS extends ActionBarActivityDelegate {
  2. public void setContentView(int resId) {
  3. this.mActivity.superSetContentView(resId);
  4. }
  5. }

ActionBarActivityDelegateBase 实现类

  1. class ActionBarActivityDelegateBase extends ActionBarActivityDelegate implements Callback, android.support.v7.internal.view.menu.MenuBuilder.Callback {
  2. public void setContentView(int resId) {
  3. this.ensureSubDecor();
  4. ViewGroup contentParent = (ViewGroup)this.mActivity.findViewById(16908290);
  5. contentParent.removeAllViews();
  6. this.mActivity.getLayoutInflater().inflate(resId, contentParent);
  7. this.mActivity.onSupportContentChanged();
  8. }
  9. }

可以发现上面这个类最终走向:inflate(...) 方法。
接着我们来继续追踪一下ActionBarActivityDelegateICS这类的实现

  1. public class PhoneWindow extends Window implements MenuBuilder.Callback {
  2. @Override
  3. public void setContentView(int layoutResID) {
  4. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
  5. // decor, when theme attributes and the like are crystalized. Do not check the feature
  6. // before this happens.
  7. if (mContentParent == null) {
  8. installDecor();
  9. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  10. mContentParent.removeAllViews();
  11. }
  12. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  13. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  14. getContext());
  15. transitionTo(newScene);
  16. } else {
  17. mLayoutInflater.inflate(layoutResID, mContentParent);
  18. }
  19. final Callback cb = getCallback();
  20. if (cb != null && !isDestroyed()) {
  21. cb.onContentChanged();
  22. }
  23. }
  24. }

代码比较长,我们重点关注一下Line 18 最终走到 inflate方法。
所以 这个 setContentView(...) 最终都是要调用 LayoutInflate.inflate()这个方法,接下来我们看看这个方法吧

inflate

  1. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
  4. final AttributeSet attrs = Xml.asAttributeSet(parser);
  5. Context lastContext = (Context)mConstructorArgs[0];
  6. mConstructorArgs[0] = mContext;
  7. View result = root;
  8. try {
  9. // Look for the root node.
  10. int type;
  11. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  12. type != XmlPullParser.END_DOCUMENT) {
  13. // Empty
  14. }
  15. if (type != XmlPullParser.START_TAG) {
  16. throw new InflateException(parser.getPositionDescription()
  17. + ": No start tag found!");
  18. }
  19. final String name = parser.getName();
  20. if (DEBUG) {
  21. System.out.println("**************************");
  22. System.out.println("Creating root view: "
  23. + name);
  24. System.out.println("**************************");
  25. }
  26. if (TAG_MERGE.equals(name)) {
  27. if (root == null || !attachToRoot) {
  28. throw new InflateException("<merge /> can be used only with a valid "
  29. + "ViewGroup root and attachToRoot=true");
  30. }
  31. rInflate(parser, root, attrs, false, false);
  32. } else {
  33. // Temp is the root view that was found in the xml
  34. final View temp = createViewFromTag(root, name, attrs, false);
  35. ViewGroup.LayoutParams params = null;
  36. if (root != null) {
  37. if (DEBUG) {
  38. System.out.println("Creating params from root: " +
  39. root);
  40. }
  41. // Create layout params that match root, if supplied
  42. params = root.generateLayoutParams(attrs);
  43. if (!attachToRoot) {
  44. // Set the layout params for temp if we are not
  45. // attaching. (If we are, we use addView, below)
  46. temp.setLayoutParams(params);
  47. }
  48. }
  49. if (DEBUG) {
  50. System.out.println("-----> start inflating children");
  51. }
  52. // Inflate all children under temp
  53. rInflate(parser, temp, attrs, true, true);
  54. if (DEBUG) {
  55. System.out.println("-----> done inflating children");
  56. }
  57. // We are supposed to attach all the views we found (int temp)
  58. // to root. Do that now.
  59. if (root != null && attachToRoot) {
  60. root.addView(temp, params);
  61. }
  62. // Decide whether to return the root that was passed in or the
  63. // top view found in xml.
  64. if (root == null || !attachToRoot) {
  65. result = temp;
  66. }
  67. }
  68. } catch (XmlPullParserException e) {
  69. InflateException ex = new InflateException(e.getMessage());
  70. ex.initCause(e);
  71. throw ex;
  72. } catch (IOException e) {
  73. InflateException ex = new InflateException(
  74. parser.getPositionDescription()
  75. + ": " + e.getMessage());
  76. ex.initCause(e);
  77. throw ex;
  78. } finally {
  79. // Don't retain static reference on context.
  80. mConstructorArgs[0] = lastContext;
  81. mConstructorArgs[1] = null;
  82. }
  83. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  84. return result;
  85. }
  86. }

从这里我们就可以清楚地看出,LayoutInflater 其实就是使用Android提供的pull解析方式来解析布局文件的。不熟悉pull解析方式的朋友可以网上搜一下,教程很多,我就不细讲了,这里我们注意看下第41行,调用了 createViewFromTag() 这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在 createViewFromTag() 方法的内部又会去调用 createView()方法,然后使用反射的方式创建出View的实例并返回。
当然上面只是创建了一个View,其他的View会通过第63行的 rInflate(parser, temp, attrs, true, true) 方法循环遍历这个根布局下的所有子View。

这里额外提高了setContentView(...)这个方法,多说一句:
Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。来一张图大概说明一下:
此处输入图片的描述

接下来就是View的三部曲了:onMeasure()、onLayout()、onDraw()过程,然后才出现在屏幕上面。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注