美文网首页
LayoutInflater.inflate()解析

LayoutInflater.inflate()解析

作者: 淡燃 | 来源:发表于2020-10-12 16:33 被阅读0次

    LayoutInflater工作原理

    简介

    LayoutInflater用来加载 xml 文件,将 xml 文件中的 View 和 ViewGoup 进行实例化,我们通常可以在 Fragment 和 RecyclerView.Adapter 中见到它的身影,本篇文章主要说说它的用法和工作原理

    说明

    LayoutInflater是一个抽象类,我们通常使用它的 from(Context context) 静态方法创建一个 LayoutInflater 实例。

    用法

    View view = LayoutInflater.from(context).inflate(R.layout.first_item, parent, false);
    

    通过 inflate() 方法来加载 View, inflate()有以下4个重载方法

    1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root){};
    2. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {}
    3. public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {};
    4. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {}
    

    inflate() 的4个重载方法,最终都是调用到第4个方法中,前两个方法会创建一个 XmlPullParser, 调用后面两个方法,那么我们主要看的就是第4个方法,这个方法有三个参数

    1. XmlPullParser,用来解析 xml 文件,
    2. ViewGroup,view 所在的 viewGroup,如果没有,可以传 null
    3. boolean 的 attachToRoot参数,标识是否要绑定到 根视图,就是第二个 root 中

    我们可以看到,该方法是通过 inflate 进行资源解析的,那么下面我们通过源码的方式解析以下,inflate 方法究竟做了什么

    原理解析

    上面说到 inflate 有4个重载方法,我们先看前两个的源码:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
      
                //1. 获取Resources, Resources是安卓的资源管理类
            final Resources res = getContext().getResources();
            if (DEBUG) {
                Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                      + Integer.toHexString(resource) + ")");
            }
            View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
            if (view != null) {
                return view;
            }
                //2. 解析xml
            XmlResourceParser parser = res.getLayout(resource);
            try {
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
        }
    

    代码分为两步:第一步获取 Resoureces 资源, 第二步通过XmlResourceParser去解析 xml,然后调用 上边后两个重载方法方法,逻辑比较简单

    下面看一下最终调用的 inflate 方法, 下面是源码:

        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 {
                    advanceToRootNode(parser);
                  //1. 获取 xml root节点名字
                    final String name = parser.getName();
                    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 {
                        // 2. 根据根节点的名字创建 View
                        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) {
                                // attachToRoot为false,view不会被添加到root中,root 决定该怎么放置视图,在recyclerview中,都是传的false,因为recyclerview会自动管理viewholder的添加和删除
                                temp.setLayoutParams(params);
                            }
                        }
    
                        if (DEBUG) {
                            System.out.println("-----> start inflating children");
                        }
    
                        // 3. 遍历子节点,生成子视图
                        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) {
                          //如果 attachToRoot 为 true,就添加到子视图中
                            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(
                            getParserStateDescription(inflaterContext, attrs)
                            + ": " + 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;
            }
        }
    

    inflate()方法就是正式解析 xml 的,第一步获取对应的根节点名称,第二步根据根节点名称通过createViewFromTag() 方法创建 View, 第三步遍历子节点,通过 rInflateChildren() 方法生成所有的子视图。

    接下来看一下上面提到的两个重要的方法 createViewFromTag() 和 rInflateChildren() :

    createViewFromTag()方法

    返回值是个 View,参数是 parent父视图,name 根节点名称,下面我们看下这个方法都做了什么

       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();
            }
    
            try {
                View view = tryCreateView(parent, name, context, attrs);
    
                if (view == null) {
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {
                          //创建视图
                            view = onCreateView(context, parent, name, attrs);
                        } else {
                            view = createView(context, name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
            } catch (InflateException e) {
                throw e;
    
            } catch (ClassNotFoundException e) {
                final InflateException ie = new InflateException(
                        getParserStateDescription(context, attrs)
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
    
            } catch (Exception e) {
                final InflateException ie = new InflateException(
                        getParserStateDescription(context, attrs)
                        + ": Error inflating class " + name, e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            }
        }
    
    

    该方法最终是调用到 createView()方法,在 createView()方法中,通过Class.forName()反射来创建 View,至此,View 就创建好了

    public final View createView(@NonNull Context viewContext, @NonNull String name,
                @Nullable String prefix, @Nullable AttributeSet attrs)
                throws ClassNotFoundException, InflateException {
            Objects.requireNonNull(viewContext);
            Objects.requireNonNull(name);
            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 = Class.forName(prefix != null ? (prefix + name) : name, false,
                            mContext.getClassLoader()).asSubclass(View.class);
    
                    if (mFilter != null && clazz != null) {
                        boolean allowed = mFilter.onLoadClass(clazz);
                        if (!allowed) {
                            failNotAllowed(name, prefix, viewContext, 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 对象
                            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                    mContext.getClassLoader()).asSubclass(View.class);
    
                            boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                            mFilterMap.put(name, allowed);
                            if (!allowed) {
                                failNotAllowed(name, prefix, viewContext, attrs);
                            }
                        } else if (allowedState.equals(Boolean.FALSE)) {
                            failNotAllowed(name, prefix, viewContext, attrs);
                        }
                    }
                }
                            //此处代码省略 .....
        }
    

    rInflateChildren() 方法

    这个方法用来遍历创建子视图, 最终调用到rInflate()方法, 方法中有个 while 循环,一步步的遍历 xml 文件,找到子节点,通过上边的 createViewFromTag 方法创建子视图, 然后将子视图加入到根节点中

      void rInflate(XmlPullParser parser, View parent, Context context,
                AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
            final int depth = parser.getDepth();
            int type;
            boolean pendingRequestFocus = false;
    
            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)) {
                    pendingRequestFocus = true;
                    consumeChildElements(parser);
                } 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 (pendingRequestFocus) {
                parent.restoreDefaultFocus();
            }
    
            if (finishInflate) {
                parent.onFinishInflate();
            }
        }
    

    总结一下

    LayoutInflater 是通过 inflate 方法来解析 xml,解析 xml 分为以下几步

    1. 解析 layout 文件xml 找到根节点的名称
    2. 通过映射 Class.forName() 创建根节点视图View,如果需要 attachToRoot,绑定到父视图, 就把根节点 View add 到父视图中
    3. 循环遍历子节点,找到子节点名称
    4. 通过映射 Class.forName()方法创建子视图,然后加入到根节点中

    可见我们 xml 中所写的布局都会经过遍历创建一个 class,所以我们的 xml 文件越复杂,节点越多,创建的类就越多,对内存消耗也越大

    相关文章

      网友评论

          本文标题:LayoutInflater.inflate()解析

          本文链接:https://www.haomeiwen.com/subject/ylcppktx.html