作为一名Android开发者,写页面是最普通不过的事情了,在编写页面的时候,系统给提供了两种形式,一种形式是通过XML的方式进行编写,还有一种形式是通过Java代码直接编写
 
我们知道Android Framework是用Java编写,那就是说,所有的交互都是通过Java来完成,那么我们编写的XML文件又是怎样被转换成Java对象的呢?接下来我便为大家解惑,带大家进入Android世界,去了解Android世界内的奥妙!
 

我们事先准备好一个XML布局,如下

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. android:orientation="vertical">
  6. <TextView />
  7. <LinearLayout />
  8. <Button />
  9. </LinearLayout />
  10. </RelativeLayout>
这个XML布局包括了几个元素,RelativeLayout、LinearLayout、TextView、Button,这些元素在我们的 android.widget 包中都有对应的Java类,那么XML是如何转换成Java类对象的呢?我们都知道Android系统给提供了一个API,唤做LayoutInflater,但是具体细节各位了解吗?Follow me!
 
我们在转换XML为Java类对象的时候,通常会这样写,如下
  1. LayoutInflater.from(this).inflate(R.layout.activity_main,null);

只有这么简单的一行代码,首先调用from函数,获取LayoutInflater对象,然后在调用该对象下的inflate方法,我们先看一下from方法,看看LayoutInflater的实现类是哪个

  1. registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
  2. new CachedServiceFetcher<LayoutInflater>() {
  3. @Override
  4. public LayoutInflater createService(ContextImpl ctx) {
  5. return new PhoneLayoutInflater(ctx.getOuterContext());
  6. }}
  7. );
我们可以看到,LayoutInflater的实例对象是PhoneLayoutInflater,属于系统提供的一个服务,可以看我先前的文章《Android 系统服务的获取与创建》

接下来我们继续看inflate方法实现,因为继承关系缘故,inflate是在LayoutInflater中实现,后边会用到PhoneLayoutInflater

  1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
  2. return inflate(resource, root, root != null);
  3. }

一个很简单的调用,把传入的参数继续向内部传入

  1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
  2. final Resources res = getContext().getResources();
  3. if (DEBUG) {
  4. Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
  5. + Integer.toHexString(resource) + ")");
  6. }
  7.  
  8. final XmlResourceParser parser = res.getLayout(resource);
  9. try {
  10. return inflate(parser, root, attachToRoot);
  11. } finally {
  12. parser.close();
  13. }
  14. }

这里边只是获取了一下XML的解析器,然后继续调用重载函数,我们继续往下看,简单看一下就行,我下面会说这个函数做了什么 操作

  1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
  2. synchronized (mConstructorArgs) {
  3. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
  4.  
  5. final Context inflaterContext = mContext;
  6. final AttributeSet attrs = Xml.asAttributeSet(parser);
  7. Context lastContext = (Context) mConstructorArgs[0];
  8. mConstructorArgs[0] = inflaterContext;
  9. View result = root;
  10.  
  11. try {
  12. // Look for the root node.
  13. int type;
  14. while ((type = parser.next()) != XmlPullParser.START_TAG &&
  15. type != XmlPullParser.END_DOCUMENT) {
  16. // Empty
  17. }
  18.  
  19. if (type != XmlPullParser.START_TAG) {
  20. throw new InflateException(parser.getPositionDescription()
  21. + ": No start tag found!");
  22. }
  23.  
  24. final String name = parser.getName();
  25.  
  26. if (DEBUG) {
  27. System.out.println("**************************");
  28. System.out.println("Creating root view: "
  29. + name);
  30. System.out.println("**************************");
  31. }
  32.  
  33. if (TAG_MERGE.equals(name)) {
  34. if (root == null || !attachToRoot) {
  35. throw new InflateException("<merge /> can be used only with a valid "
  36. + "ViewGroup root and attachToRoot=true");
  37. }
  38.  
  39. rInflate(parser, root, inflaterContext, attrs, false);
  40. } else {
  41. // Temp is the root view that was found in the xml
  42. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
  43.  
  44. ViewGroup.LayoutParams params = null;
  45.  
  46. if (root != null) {
  47. if (DEBUG) {
  48. System.out.println("Creating params from root: " +
  49. root);
  50. }
  51. // Create layout params that match root, if supplied
  52. params = root.generateLayoutParams(attrs);
  53. if (!attachToRoot) {
  54. // Set the layout params for temp if we are not
  55. // attaching. (If we are, we use addView, below)
  56. temp.setLayoutParams(params);
  57. }
  58. }
  59.  
  60. if (DEBUG) {
  61. System.out.println("-----> start inflating children");
  62. }
  63.  
  64. // Inflate all children under temp against its context.
  65. rInflateChildren(parser, temp, attrs, true);
  66.  
  67. if (DEBUG) {
  68. System.out.println("-----> done inflating children");
  69. }
  70.  
  71. // We are supposed to attach all the views we found (int temp)
  72. // to root. Do that now.
  73. if (root != null && attachToRoot) {
  74. root.addView(temp, params);
  75. }
  76.  
  77. // Decide whether to return the root that was passed in or the
  78. // top view found in xml.
  79. if (root == null || !attachToRoot) {
  80. result = temp;
  81. }
  82. }
  83.  
  84. } catch (XmlPullParserException e) {
  85. final InflateException ie = new InflateException(e.getMessage(), e);
  86. ie.setStackTrace(EMPTY_STACK_TRACE);
  87. throw ie;
  88. } catch (Exception e) {
  89. final InflateException ie = new InflateException(parser.getPositionDescription()
  90. + ": " + e.getMessage(), e);
  91. ie.setStackTrace(EMPTY_STACK_TRACE);
  92. throw ie;
  93. } finally {
  94. // Don't retain static reference on context.
  95. mConstructorArgs[0] = lastContext;
  96. mConstructorArgs[1] = null;
  97.  
  98. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  99. }
  100.  
  101. return result;
  102. }
  103. }
各位读完后,在看看我对这个函数的理解把
解析XML布局文件,获取跟节点名称(RelativeLayout)
然后判断是否是<merge>标签,如果是merge标签就基于root参数进行构建整个XML布局关系
如果不是,便会根据根节点的名称,使用createViewFromTag函数创建一个对应的Layout对象temp,然后使用temp对象作为根节点构建整个XML布局,核心功能是由rInflate,我们稍后在分析这个函数,我们先看一下createViewFromTag
  1. private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
  2. return createViewFromTag(parent, name, context, attrs, false);
  3. }

这个方法是重载的调用,我们继续往下看

  1. View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
  2. boolean ignoreThemeAttr) {
  3. if (name.equals("view")) {
  4. name = attrs.getAttributeValue(null, "class");
  5. }
  6.  
  7. // Apply a theme wrapper, if allowed and one is specified.
  8. if (!ignoreThemeAttr) {
  9. final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
  10. final int themeResId = ta.getResourceId(0, 0);
  11. if (themeResId != 0) {
  12. context = new ContextThemeWrapper(context, themeResId);
  13. }
  14. ta.recycle();
  15. }
  16.  
  17. if (name.equals(TAG_1995)) {
  18. // Let's party like it's 1995!
  19. return new BlinkLayout(context, attrs);
  20. }
  21.  
  22. try {
  23. View view;
  24. if (mFactory2 != null) {
  25. view = mFactory2.onCreateView(parent, name, context, attrs);
  26. } else if (mFactory != null) {
  27. view = mFactory.onCreateView(name, context, attrs);
  28. } else {
  29. view = null;
  30. }
  31.  
  32. if (view == null && mPrivateFactory != null) {
  33. view = mPrivateFactory.onCreateView(parent, name, context, attrs);
  34. }
  35.  
  36. if (view == null) {
  37. final Object lastContext = mConstructorArgs[0];
  38. mConstructorArgs[0] = context;
  39. try {
  40. if (-1 == name.indexOf('.')) {
  41. view = onCreateView(parent, name, attrs);
  42. } else {
  43. view = createView(name, null, attrs);
  44. }
  45. } finally {
  46. mConstructorArgs[0] = lastContext;
  47. }
  48. }
  49.  
  50. return view;
  51. } catch (InflateException e) {
  52. throw e;
  53.  
  54. } catch (ClassNotFoundException e) {
  55. final InflateException ie = new InflateException(attrs.getPositionDescription()
  56. + ": Error inflating class " + name, e);
  57. ie.setStackTrace(EMPTY_STACK_TRACE);
  58. throw ie;
  59.  
  60. } catch (Exception e) {
  61. final InflateException ie = new InflateException(attrs.getPositionDescription()
  62. + ": Error inflating class " + name, e);
  63. ie.setStackTrace(EMPTY_STACK_TRACE);
  64. throw ie;
  65. }
  66. }
先判断mFactory2和mFactory对象,这两个对象是外部设置对象,不设置不会进入判断,如果设置后并创建了view,那后边便不会执行了

咱们最普通的用法都是用系统的加载方式

  1. if (view == null) {
  2. final Object lastContext = mConstructorArgs[0];
  3. mConstructorArgs[0] = context;
  4. try {
  5. if (-1 == name.indexOf('.')) {
  6. view = onCreateView(parent, name, attrs);
  7. } else {
  8. view = createView(name, null, attrs);
  9. }
  10. } finally {
  11. mConstructorArgs[0] = lastContext;
  12. }
  13. }
就是上面这一段代码。这段代码是调用onCreateView和createView来创建
我们先看一下onCreateView
  1. private static final String[] sClassPrefixList = {
  2. "android.widget.",
  3. "android.webkit.",
  4. "android.app."
  5. };
  6.  
  7. @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
  8. for (String prefix : sClassPrefixList) {
  9. try {
  10. View view = createView(name, prefix, attrs);
  11. if (view != null) {
  12. return view;
  13. }
  14. } catch (ClassNotFoundException e) {
  15. // In this case we want to let the base class take a crack
  16. // at it.
  17. }
  18. }
  19.  
  20. return super.onCreateView(name, attrs);
  21. }
我们的LayoutInflater的实现类是PhoneLayoutInflater,PhoneLayoutInflater只实现了onCreateView方法,所以会先调用子类的onCreateView方法

这个子类的onCreateView方法主要功能是通过循环sClassPrefixList这个数组并调用createView尝试创建view,如果可以创建成功便返回,如果循环结束后仍然没有创建成功便调用LayoutInflater的ch

  1. protected View onCreateView(String name, AttributeSet attrs)
  2. throws ClassNotFoundException {
  3. return createView(name, "android.view.", attrs);
  4. }
这个方法也是调用了onCreateView,但是在前缀参数上传递了android.view.参数,代表是系统view

我们来往下看createView把

  1. public final View createView(String name, String prefix, AttributeSet attrs)
  2. throws ClassNotFoundException, InflateException {
  3. Constructor<? extends View> constructor = sConstructorMap.get(name);
  4. if (constructor != null && !verifyClassLoader(constructor)) {
  5. constructor = null;
  6. sConstructorMap.remove(name);
  7. }
  8. Class<? extends View> clazz = null;
  9.  
  10. try {
  11. Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
  12.  
  13. if (constructor == null) {
  14. // Class not found in the cache, see if it's real, and try to add it
  15. clazz = mContext.getClassLoader().loadClass(
  16. prefix != null ? (prefix + name) : name).asSubclass(View.class);
  17.  
  18. if (mFilter != null && clazz != null) {
  19. boolean allowed = mFilter.onLoadClass(clazz);
  20. if (!allowed) {
  21. failNotAllowed(name, prefix, attrs);
  22. }
  23. }
  24. constructor = clazz.getConstructor(mConstructorSignature);
  25. constructor.setAccessible(true);
  26. sConstructorMap.put(name, constructor);
  27. } else {
  28. // If we have a filter, apply it to cached constructor
  29. if (mFilter != null) {
  30. // Have we seen this name before?
  31. Boolean allowedState = mFilterMap.get(name);
  32. if (allowedState == null) {
  33. // New class -- remember whether it is allowed
  34. clazz = mContext.getClassLoader().loadClass(
  35. prefix != null ? (prefix + name) : name).asSubclass(View.class);
  36.  
  37. boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
  38. mFilterMap.put(name, allowed);
  39. if (!allowed) {
  40. failNotAllowed(name, prefix, attrs);
  41. }
  42. } else if (allowedState.equals(Boolean.FALSE)) {
  43. failNotAllowed(name, prefix, attrs);
  44. }
  45. }
  46. }
  47.  
  48. Object lastContext = mConstructorArgs[0];
  49. if (mConstructorArgs[0] == null) {
  50. // Fill in the context if not already within inflation.
  51. mConstructorArgs[0] = mContext;
  52. }
  53. Object[] args = mConstructorArgs;
  54. args[1] = attrs;
  55.  
  56. final View view = constructor.newInstance(args);
  57. if (view instanceof ViewStub) {
  58. // Use the same context when inflating ViewStub later.
  59. final ViewStub viewStub = (ViewStub) view;
  60. viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
  61. }
  62. mConstructorArgs[0] = lastContext;
  63. return view;
  64.  
  65. } catch (NoSuchMethodException e) {
  66. final InflateException ie = new InflateException(attrs.getPositionDescription()
  67. + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
  68. ie.setStackTrace(EMPTY_STACK_TRACE);
  69. throw ie;
  70.  
  71. } catch (ClassCastException e) {
  72. // If loaded class is not a View subclass
  73. final InflateException ie = new InflateException(attrs.getPositionDescription()
  74. + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
  75. ie.setStackTrace(EMPTY_STACK_TRACE);
  76. throw ie;
  77. } catch (ClassNotFoundException e) {
  78. // If loadClass fails, we should propagate the exception.
  79. throw e;
  80. } catch (Exception e) {
  81. final InflateException ie = new InflateException(
  82. attrs.getPositionDescription() + ": Error inflating class "
  83. + (clazz == null ? "<unknown>" : clazz.getName()), e);
  84. ie.setStackTrace(EMPTY_STACK_TRACE);
  85. throw ie;
  86. } finally {
  87. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  88. }
  89. }

这个这个函数便是先把前缀prefix和name进行了一个拼接,然后使用反射技术通过拼接出来的包名创建对应的view对象,最终返回创建出来的view,到这里就弄清楚了view是怎么创建出来的。这快可以告一段落了,我们往下继续看是怎么把一个庞大的XML布局转换成一个ViewGroup对象的

我们看一下rInflateChildren函数的具体实现把

  1. final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
  2. boolean finishInflate) throws XmlPullParserException, IOException {
  3. rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
  4. }
rInflateChildren函数的作用是根据一个根节点组合一个完整的View,也就是我们实例中的RelativeLayout和他下边的两个节点:TextView、LInearLayout
rInflate函数的作用是把这个ViewGroup下的所有子View都添加进来,形成一个完整的View Tree
我们分析一下rInflate把,看看逻辑是如何实现的

  1. void rInflate(XmlPullParser parser, View parent, Context context,
  2. AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
  3.  
  4. final int depth = parser.getDepth();
  5. int type;
  6. boolean pendingRequestFocus = false;
  7.  
  8. while (((type = parser.next()) != XmlPullParser.END_TAG ||
  9. parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
  10.  
  11. if (type != XmlPullParser.START_TAG) {
  12. continue;
  13. }
  14.  
  15. final String name = parser.getName();
  16.  
  17. if (TAG_REQUEST_FOCUS.equals(name)) {
  18. pendingRequestFocus = true;
  19. consumeChildElements(parser);
  20. } else if (TAG_TAG.equals(name)) {
  21. parseViewTag(parser, parent, attrs);
  22. } else if (TAG_INCLUDE.equals(name)) {
  23. if (parser.getDepth() == 0) {
  24. throw new InflateException("<include /> cannot be the root element");
  25. }
  26. parseInclude(parser, context, parent, attrs);
  27. } else if (TAG_MERGE.equals(name)) {
  28. throw new InflateException("<merge /> must be the root element");
  29. } else {
  30. final View view = createViewFromTag(parent, name, context, attrs);
  31. final ViewGroup viewGroup = (ViewGroup) parent;
  32. final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
  33. rInflateChildren(parser, view, attrs, true);
  34. viewGroup.addView(view, params);
  35. }
  36. }
  37.  
  38. if (pendingRequestFocus) {
  39. parent.restoreDefaultFocus();
  40. }
  41.  
  42. if (finishInflate) {
  43. parent.onFinishInflate();
  44. }
  45. }
这个方法用while循环的形式来解析XML布局,然后得到节点名称name字段,并使用name属性去创建对应的view对象
然后递归调用直到整个XML解析完成
 
最后执行完成后,方法退回到inflate方法内,执行以下代码,来返回解析结果,返回后,inflate方法彻底执行完成,至此,View对象也是生成完毕

  1. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
  2.  
  3. ViewGroup.LayoutParams params = null;
  4.  
  5. if (root != null) {
  6. if (DEBUG) {
  7. System.out.println("Creating params from root: " +
  8. root);
  9. }
  10. // Create layout params that match root, if supplied
  11. params = root.generateLayoutParams(attrs);
  12. if (!attachToRoot) {
  13. // Set the layout params for temp if we are not
  14. // attaching. (If we are, we use addView, below)
  15. temp.setLayoutParams(params);
  16. }
  17. }
  18.  
  19. if (DEBUG) {
  20. System.out.println("-----> start inflating children");
  21. }
  22.  
  23. // Inflate all children under temp against its context.
  24. rInflateChildren(parser, temp, attrs, true);
  25.  
  26. if (DEBUG) {
  27. System.out.println("-----> done inflating children");
  28. }
  29.  
  30. // We are supposed to attach all the views we found (int temp)
  31. // to root. Do that now.
  32. if (root != null && attachToRoot) {
  33. root.addView(temp, params);
  34. }
  35.  
  36. // Decide whether to return the root that was passed in or the
  37. // top view found in xml.
  38. if (root == null || !attachToRoot) {
  39. result = temp;
  40. }
上边这段代码里,temp对象便是通过rInflateChindren以及层层递归得到的最终对象,处理最后赋值给result对象,并返回结果

Android LayoutInflater 类分析的更多相关文章

  1. [Android FrameWork 6.0源码学习] LayoutInflater 类分析

    LayoutInflater是用来解析XML布局文件,然后生成对象的ViewTree的工具类.是这个工具类的存在,才能让我们写起Layout来那么省劲. 我们接下来进去刨析,看看里边的奥秘 //调用i ...

  2. Android LayoutInflater原理分析,带你一步步深入了解View(一)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/12921889 有不少朋友跟我反应,都希望我可以写一篇关于View的文章,讲一讲Vi ...

  3. Android LayoutInflater原理分析

    相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的.而刚接触Android的朋友可能对LayoutInflater不怎么熟悉,因为加载布局的 ...

  4. 安卓主activity引用自定义的View——Android LayoutInflater原理分析

    相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的.而刚接触Android的朋友可能对LayoutInflater不怎么熟悉,因为加载布局的 ...

  5. Android LruCache类分析

    public class LurCache<K, V> { private final LinkedHashMap<K, V> map; private int size; / ...

  6. [旧][Android] LayoutInflater 工作流程

    备注 原发表于2016.06.20,资料已过时,仅作备份,谨慎参考 前言 感觉很长时间没写文章了,这个星期因为回家和处理项目问题,还是花了很多时间的.虽然知道很多东西如果只是看一下用一次,很快就会遗忘 ...

  7. Android的消息循环机制 Looper Handler类分析

    Android的消息循环机制 Looper Handler类分析 Looper类说明   Looper 类用来为一个线程跑一个消息循环. 线程在默认情况下是没有消息循环与之关联的,Thread类在ru ...

  8. 「Android」消息驱动Looper和Handler类分析

    Android系统中的消息驱动工作原理: 1.有一个消息队列,可以往这个消息队列中投递消息; 2.有一个消息循环,不断的从消息队列中取得消息,然后处理. 工作流程: 1.事件源将待处理的消息加入到消息 ...

  9. 源码分析篇 - Android绘制流程(三)requestLayout()与invalidate()流程及Choroegrapher类分析

    本文主要探讨能够触发performTraversals()执行的invalidate().postInvalidate()和requestLayout()方法的流程.在调用这三个方法到最后执行到per ...

随机推荐

  1. 剑指Offer全解

    二维数组中的查找 描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中 ...

  2. JDK设计模式之—动态代理

    代理模式的特点 代理模式是常用的java设计模式,它的特征是代理类与委托类有同样的接口.代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类. 代理类的对象并不是真正实现服务,而是通过调用委 ...

  3. [Swift]LeetCode405. 数字转换为十六进制数 | Convert a Number to Hexadecimal

    Given an integer, write an algorithm to convert it to hexadecimal. For negative integer, two’s compl ...

  4. [Swift]LeetCode488. 祖玛游戏 | Zuma Game

    Think about Zuma Game. You have a row of balls on the table, colored red(R), yellow(Y), blue(B), gre ...

  5. [Swift]LeetCode540. 有序数组中的单一元素 | Single Element in a Sorted Array

    Given a sorted array consisting of only integers where every element appears twice except for one el ...

  6. [Swift]LeetCode670. 最大交换 | Maximum Swap

    Given a non-negative integer, you could swap two digits at most once to get the maximum valued numbe ...

  7. dpkg: 处理软件包 xxx (--configure)时出错 解决办法

    第一步:备份 $ sudo mv /var/lib/dpkg/info /var/lib/dpkg/info.bk 第二步:新建 $ sudo mkdir /var/lib/dpkg/info 第三步 ...

  8. Spring Cloud Greenwich 正式发布,Hystrix 即将寿终正寝。。

    Spring Cloud Greenwich 正式版在 01/23/2019 这天正式发布了,下面我们来看下有哪些更新内容. 生命周期终止提醒 Spring Cloud Edgware Edgware ...

  9. WebView 讲义

    http://reezy.me/p/20170515/android-webview/  (比较全面) 参考 https://developer.android.com/reference/andro ...

  10. ES 02 - 部署Elasticsearch单机服务 + 部署中的常见问题

    目录 1 准备工作 1.1 安装JDK 1.2 下载安装包 1.3 创建elastic用户 2 启动ES服务 2.1 修改配置文件 2.2 启动服务 3 验证ES服务是否可用 4 关闭与重启服务 4. ...