前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android LayoutInflater.inflate()源码流程分析

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

作者头像
曾大稳
发布2018-09-11 10:50:56
7930
发布2018-09-11 10:50:56
举报

  我们在根据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()源码流程分析就到这里了。因为在这里读的是流程,所有很多细节的东西还是希望大家自己去源码中看看。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-03-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档