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

疑问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.
* 

* Important   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 * attachToRoot is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if attachToRoot 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(" 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的解析过程,我先放一张图然后再来对代码进行分析。


从setContentView开始分析view的加载过程 二_第1张图片
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(" 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().
* 

* Note: 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(" cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException(" 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.
* Note: 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的加载过程 二)