Android LayoutInflater.inflate()源码流程分析

  我们在根据layout文件得到View的时候都会使用LayoutInflater.from(mContext).inflate().下面我们来分析这个获取View流程。   我们知道inflate有如下函数:

inflate(@LayoutRes int resource, @Nullable ViewGroup root);
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);
inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);

其实点进去查看可以知道,其实都到了这个方法:

inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot);

源码如下:

/**
    * Inflate a new view hierarchy from the specified XML node. Throws
    * {@link InflateException} if there is an error.
    * <p>
    * <em><strong>Important</strong></em>   For performance
    * reasons, view inflation relies heavily on pre-processing of XML files
    * that is done at build time. Therefore, it is not currently possible to
    * use LayoutInflater with an XmlPullParser over a plain XML file at runtime.
    * 
    * @param parser XML dom node containing the description of the view
    *        hierarchy.
    * @param root Optional view to be the parent of the generated hierarchy (if
    *        <em>attachToRoot</em> is true), or else simply an object that
    *        provides a set of LayoutParams values for root of the returned
    *        hierarchy (if <em>attachToRoot</em> is false.)
    * @param attachToRoot Whether the inflated hierarchy should be attached to
    *        the root parameter? If false, root is only used to create the
    *        correct subclass of LayoutParams for the root view in the XML.
    * @return The root View of the inflated hierarchy. If root was supplied and
    *         attachToRoot is true, this is root; otherwise it is the root of
    *         the inflated XML file.
    */
   public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
       synchronized (mConstructorArgs) {
           Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

           final Context inflaterContext = mContext;
           final AttributeSet attrs = Xml.asAttributeSet(parser);
           Context lastContext = (Context) mConstructorArgs[0];
           mConstructorArgs[0] = inflaterContext;
           View result = root;

           try {
               // Look for the root node.
               int type;
               while ((type = parser.next()) != XmlPullParser.START_TAG &&
                       type != XmlPullParser.END_DOCUMENT) {
                   // Empty
               }

               if (type != XmlPullParser.START_TAG) {
                   throw new InflateException(parser.getPositionDescription()
                           + ": No start tag found!");
               }

               final String name = parser.getName();
               
               if (DEBUG) {
                   System.out.println("**************************");
                   System.out.println("Creating root view: "
                           + name);
                   System.out.println("**************************");
               }

               if (TAG_MERGE.equals(name)) {
                   if (root == null || !attachToRoot) {
                       throw new InflateException("<merge /> can be used only with a valid "
                               + "ViewGroup root and attachToRoot=true");
                   }

                   rInflate(parser, root, inflaterContext, attrs, false);
               } else {
                   // Temp is the root view that was found in the xml
                   final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                   ViewGroup.LayoutParams params = null;

                   if (root != null) {
                       if (DEBUG) {
                           System.out.println("Creating params from root: " +
                                   root);
                       }
                       // Create layout params that match root, if supplied
                       params = root.generateLayoutParams(attrs);
                       if (!attachToRoot) {
                           // Set the layout params for temp if we are not
                           // attaching. (If we are, we use addView, below)
                           temp.setLayoutParams(params);
                       }
                   }

                   if (DEBUG) {
                       System.out.println("-----> start inflating children");
                   }

                   // Inflate all children under temp against its context.
                   rInflateChildren(parser, temp, attrs, true);

                   if (DEBUG) {
                       System.out.println("-----> done inflating children");
                   }

                   // We are supposed to attach all the views we found (int temp)
                   // to root. Do that now.
                   if (root != null && attachToRoot) {
                       root.addView(temp, params);
                   }

                   // Decide whether to return the root that was passed in or the
                   // top view found in xml.
                   if (root == null || !attachToRoot) {
                       result = temp;
                   }
               }

           } catch (XmlPullParserException e) {
               final InflateException ie = new InflateException(e.getMessage(), e);
               ie.setStackTrace(EMPTY_STACK_TRACE);
               throw ie;
           } catch (Exception e) {
               final InflateException ie = new InflateException(parser.getPositionDescription()
                       + ": " + e.getMessage(), e);
               ie.setStackTrace(EMPTY_STACK_TRACE);
               throw ie;
           } finally {
               // Don't retain static reference on context.
               mConstructorArgs[0] = lastContext;
               mConstructorArgs[1] = null;

               Trace.traceEnd(Trace.TRACE_TAG_VIEW);
           }

           return result;
       }
   }

其实就是通过XmlPullParser来解析layout.xml布局 在这里通过判断,如果是merge标签就会执行如下方法:

/**
    * Recursive method used to descend down the xml hierarchy and instantiate
    * views, instantiate their children, and then call onFinishInflate().
    * <p>
    * <strong>Note:</strong> Default visibility so the BridgeInflater can
    * override it.
    */
   void rInflate(XmlPullParser parser, View parent, Context context,
           AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

       final int depth = parser.getDepth();
       int type;

       while (((type = parser.next()) != XmlPullParser.END_TAG ||
               parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

           if (type != XmlPullParser.START_TAG) {
               continue;
           }

           final String name = parser.getName();
           
           if (TAG_REQUEST_FOCUS.equals(name)) {
               parseRequestFocus(parser, parent);
           } else if (TAG_TAG.equals(name)) {
               parseViewTag(parser, parent, attrs);
           } else if (TAG_INCLUDE.equals(name)) {
               if (parser.getDepth() == 0) {
                   throw new InflateException("<include /> cannot be the root element");
               }
               parseInclude(parser, context, parent, attrs);
           } else if (TAG_MERGE.equals(name)) {
               throw new InflateException("<merge /> must be the root element");
           } else {
               final View view = createViewFromTag(parent, name, context, attrs);
               final ViewGroup viewGroup = (ViewGroup) parent;
               final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
               rInflateChildren(parser, view, attrs, true);
               viewGroup.addView(view, params);
           }
       }

       if (finishInflate) {
           parent.onFinishInflate();
       }
   }

我这主要讲根据layout.xml获取里面的View流程,这些不同的标签不同的解析方式,在这不进行细讲,有趣的可以自行查看源码。在这我们重点看createViewFromTag函数,因为在如果layout开始标签不是merge的话也会调用这个函数创建View,可以发现最终进入:

/**
   * Creates a view from a tag name using the supplied attribute set.
   * <p>
   * <strong>Note:</strong> Default visibility so the BridgeInflater can
   * override it.
   *
   * @param parent the parent view, used to inflate layout params
   * @param name the name of the XML tag used to define the view
   * @param context the inflation context for the view, typically the
   *                {@code parent} or base layout inflater context
   * @param attrs the attribute set for the XML tag used to define the view
   * @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
   *                        attribute (if set) for the view being inflated,
   *                        {@code false} otherwise
   */
  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
          boolean ignoreThemeAttr) {
      if (name.equals("view")) {
          name = attrs.getAttributeValue(null, "class");
      }

      // Apply a theme wrapper, if allowed and one is specified.
      if (!ignoreThemeAttr) {
          final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
          final int themeResId = ta.getResourceId(0, 0);
          if (themeResId != 0) {
              context = new ContextThemeWrapper(context, themeResId);
          }
          ta.recycle();
      }

      if (name.equals(TAG_1995)) {
          // Let's party like it's 1995!
          return new BlinkLayout(context, attrs);
      }

      try {
          View view;
          if (mFactory2 != null) {
              view = mFactory2.onCreateView(parent, name, context, attrs);
          } else if (mFactory != null) {
              view = mFactory.onCreateView(name, context, attrs);
          } else {
              view = null;
          }

          if (view == null && mPrivateFactory != null) {
              view = mPrivateFactory.onCreateView(parent, name, context, attrs);
          }

          if (view == null) {
              final Object lastContext = mConstructorArgs[0];
              mConstructorArgs[0] = context;
              try {
                  if (-1 == name.indexOf('.')) {
                      view = onCreateView(parent, name, attrs);
                  } else {
                      view = createView(name, null, attrs);
                  }
              } finally {
                  mConstructorArgs[0] = lastContext;
              }
          }

          return view;
      } catch (InflateException e) {
          throw e;

      } catch (ClassNotFoundException e) {
          final InflateException ie = new InflateException(attrs.getPositionDescription()
                  + ": Error inflating class " + name, e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;

      } catch (Exception e) {
          final InflateException ie = new InflateException(attrs.getPositionDescription()
                  + ": Error inflating class " + name, e);
          ie.setStackTrace(EMPTY_STACK_TRACE);
          throw ie;
      }
  }

通过这段函数可以知道到View是在这里创建的:

View view;
if (mFactory2 != null) {
    view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs);
} else {
    view = null;
}

if (view == null && mPrivateFactory != null) {
    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}

if (view == null) {
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    } finally {
        mConstructorArgs[0] = lastContext;
    }
}

return view;

可以发现View创建经过了实际是由mFactory2–>Factory–>mPrivateFactory的判断才进入到了onCreateView方法,在这里我们就可以知道,如果我们要拦截View的创建,我们就可以给LayoutInflater设置一个我们自定义的一个Factory即可,并且创建View的规则我们在自己的Factory类中实现即可。具体的实现方式点此查看,我们继续看onCreateView(parent, name, attrs); 如果是-1 == name.indexOf('.')的,即不是自定义的View将会执行:

createView(name, "android.view.", attrs);

是自定义的则是:

view = createView(name, null, attrs);

createView(String name, String prefix, AttributeSet attrs)函数如下:

/**
     * Low-level function for instantiating a view by name. This attempts to
     * instantiate a view class of the given <var>name</var> found in this
     * LayoutInflater's ClassLoader.
     * 
     * <p>
     * There are two things that can happen in an error case: either the
     * exception describing the error will be thrown, or a null will be
     * returned. You must deal with both possibilities -- the former will happen
     * the first time createView() is called for a class of a particular name,
     * the latter every time there-after for that class name.
     * 
     * @param name The full name of the class to be instantiated.
     * @param attrs The XML attributes supplied for this instance.
     * 
     * @return View The newly instantiated view, or null.
     */
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                        
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;

        } catch (NoSuchMethodException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    attrs.getPositionDescription() + ": Error inflating class "
                            + (clazz == null ? "<unknown>" : clazz.getName()), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

可以知道其实就是通过反射来进行View的创建,先在constructor缓存集合sConstructorMap里面查找对应的Viewconstructor进行初始化,如果没有就通过反射拿到constructor,然后缓存到sConstructorMap里面,创建View的时候是通过prefix拼接的,如果xml里面是TextView则拼接出来则是android.view.TextView,所以上面不是自定的View的话,执行的是createView(name, "android.view.", attrs);,如果是自定义的话就不用拼接了。可以发现通过代码

 constructor = clazz.getConstructor(mConstructorSignature);

static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};

得知,最终创建View的构造方法是带有ContextAttributeSet参数的方法,这也就解析了我们在自定义一些View的时候,如果不重写这个带有这两个参数的方法的话将会崩溃的现象。

LayoutInflater.inflate()源码流程分析就到这里了。因为在这里读的是流程,所有很多细节的东西还是希望大家自己去源码中看看。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏编程之路

羊皮书APP(Android版)开发系列(十)Android开发常用工具类

15210
来自专栏开发之途

Android 解析RecyclerView(2)——带顶部View和底部View的RecyclerView

39250
来自专栏向治洪

Android侧滑删除另一种实现,SwipeListView补充

前不久在在做聊天删除功能的时候使用SwipeListView进行侧滑删除有一点小问题,因为SwipeListView嵌套在Fragment内的时候,会报一个转换...

21190
来自专栏Android知识点总结

O3-开源框架使用之Butterknife 8.8.1及源码浅析

cast()方法是Clazz的一个公共方法:由下可见它反会一个由传入值强转成的T类型对象

25640
来自专栏MyBlog

关于Valley网络框架

11220
来自专栏指尖下的Android

Android LayoutInflater的用法详解

相信我们在开发过程中肯定接触过LayoutInflater,比如ListView的适配器里的getView方法里通过LayoutInflater.from(Co...

22220
来自专栏向治洪

android使用LruCache对listview加载图片时候优化处理

注意:LruCache是有版本限制的,低版本的sdk需要在libs文件夹添加相应的support-4v文件。 本文改造的大部分是参考http://www.i...

30460
来自专栏向治洪

listview动态获取数据

1.主Activity 1 public class MainActivity extends Activity { 2 3 private ...

25390
来自专栏向治洪

android多线程下载2

在上一集中,我们简单介绍了如何创建多任务下载,但那种还不能拿来实用,这一集我们重点通过代码为大家展示如何创建多线程断点续传下载,这在实际项目中很常用. main...

22790
来自专栏Android知识点总结

O3-开源框架使用之Butterknife 8.8.1及源码浅析

cast()方法是Clazz的一个公共方法:由下可见它反会一个由传入值强转成的T类型对象

14530

扫码关注云+社区

领取腾讯云代金券