我眼中的AndroidAndroid知识Android开发

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

2017-05-11  本文已影响83人  grr1314

疑问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得到了解答

上一篇下一篇

猜你喜欢

热点阅读