美文网首页我眼中的AndroidAndroid知识Android开发
从setContentView开始分析view的加载过程 二

从setContentView开始分析view的加载过程 二

作者: grr1314 | 来源:发表于2017-05-11 06:05 被阅读83次

    疑问5:LayoutInflater 到底怎么把xml添加到decorview?
    疑问6:include 为什么不能xml资源布局的根节点?
    疑问7: merge 为什么作为xml资源布局的根节点?

    在上篇文章中我们说到setContentView最终还是要靠LayoutInflater类来加载布局文件,那么LayoutInflater是如何加载解析xml文件的呢?下面我们带着以上3个问题去分析一下这个过程。
    首先,LayoutInflater对象如何创建,相信大家对这个很熟悉了

    LayoutInflater layoutInflater=LayoutInflater.from(this);
    layoutInflater.inflate(.....);
    
    

    from方法又是怎么实现的呢?

    /**
    * Obtains the LayoutInflater from the given context.
    */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
    
    

    好了到了这我们就知道LayoutInflater对象是通过getSystemService()方法获取的,这里我们也没有必要继续深究。LayoutInflater的inflate()的重载形式有4中但是另外三个方法最终还是要调用参数最多的这个,代码如下

    /**
    * Inflate a new view hierarchy from the specified XML node. Throws
    * {@link InflateException} if there is an error.
    * <p>
    * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;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) {
                InflateException ex = new InflateException(e.getMessage());
                ex.initCause(e);
                throw ex;
            } catch (Exception e) {
                InflateException ex = new InflateException(
                        parser.getPositionDescription()
                                + ": " + e.getMessage());
                ex.initCause(e);
                throw ex;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
            }
    
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    
            return result;
        }
    }
    
    

    为了方便大家理解xml的解析过程,我先放一张图然后再来对代码进行分析。


    LayoutInflater.png

    我们跟着流程图再配合源码一步一步的分析:
    (1)循环查找根结点
    inflate()方法一上来做的一件事情就是循环查找根节点代码中通过一个While循环来循环查找

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

    (2)是否找到根节点
    紧接着判断是否找到根节点,如果找到了就进行下一步没找到就直接抛出异常

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

    (3)当前节点是merge
    先拿到当前节点的名称,然后再判断是不是merge标签,如果是则进入下一个判断条件如果不是则继续。这一步我们先来考虑是merge的情况,如果·不满足root == null || !attachToRoot这个条件就直接抛出异常,满足的话就执行(5)

    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 {
        //代码省略
    }
    
    
    

    (4)当前节点不是merge

      // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
    

    (5)遍历子节点
    通过调用rInflateChildren()方法或者是rInflate()方法
    rInflate()

    /**
    * 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();
        }
    }
    
    

    rInflateChildren()

    /**
    * Recursive method used to inflate internal (non-root) children. This
    * method calls through to {@link #rInflate} using the parent context as
    * the inflation context.
    * <strong>Note:</strong> Default visibility so the BridgeInflater can
    * call it.
    */
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
    
    

    从代码中可以看出rInflateChildren()方法本质上还是调用了rInflate()方法,在rInflate()方法中我们看到依然是先找根节点然后再对根节点做判断,从代中可以看到分别对include和merge标签进行判断,然后跑出相应的异常,那么到目前为止疑问6和7得到了解答。
    (6)最后通过root.addView()方法将布局添加到DectorView中的id为content的View当中

    // 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);
    }
    
    

    疑问5得到了解答

    相关文章

      网友评论

        本文标题:从setContentView开始分析view的加载过程 二

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