LayoutInflater.inflater做了什么?

 本文分析了从LayoutInflater类的inflate函数开始,到涉及jni的AssetManager类结束,中间到底做了什么。
 本文的源码均是基于Android6.0(API=23)

Part 1

 首先,为了搞清楚这一切,我们从最深处往外探寻。
 首先来看位于Resources类的getvalue方法,逻辑也不复杂,方法具有三个参数。

public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
        throws NotFoundException {
    boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
    if (found) {
        return;
    }
    throw new NotFoundException("Resource ID #0x"
                                + Integer.toHexString(id));
}

 先来看一下函数签名,第一个是资源id,也就是写的R.layout.XXX,第二个是一个TypedValue类的实例,API对于这个类的描述为:一个包含动态类型数据值的容器,主要用来持有Resources类的资源值。
 这个类最熟悉的地方就是需要动态设置TextView的字体大小的时候,TextView提供了一个这样的方法public void setTextSize(int unit, float size)这个方法的第一个参数unit(int类型),实际使用的时候就需要传入TypedValue的一些静态常量来指定,例如px、dp、sp、pt等。

/** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
public static final int COMPLEX_UNIT_PX = 0;
/** {@link #TYPE_DIMENSION} complex unit: Value is Device Independent
 *  Pixels. */
public static final int COMPLEX_UNIT_DIP = 1;
/** {@link #TYPE_DIMENSION} complex unit: Value is a scaled pixel. */
public static final int COMPLEX_UNIT_SP = 2;
/** {@link #TYPE_DIMENSION} complex unit: Value is in points. */
public static final int COMPLEX_UNIT_PT = 3;
/** {@link #TYPE_DIMENSION} complex unit: Value is in inches. */
public static final int COMPLEX_UNIT_IN = 4;
/** {@link #TYPE_DIMENSION} complex unit: Value is in millimeters. */
public static final int COMPLEX_UNIT_MM = 5;

 也就是说,从资源文件里面取出来的值会放到这个TypedValue类里面。
 第三个参数,布尔值的resolveRefs,API是这么解释的,如果为true的话,当一个引用其他资源的资源值将会返回最终实际上真实的值。为false的话,则填充为自身的的引用值。getValue方法的第三行调用了AssetManager类的getResourceValue方法,这个超出本文的范围,内部有jni的逻辑,我们只需要知道的是,如果找到了资源值,outvalue将会带有资源值,同时返回为true。如果为false,则抛出异常。

 为何要说到这个getValue方法呢,因为在Resources类的另外一个方法loadXmlResourceParser(int id, String type)方法中,调用了这个getValue方法,下面我们看一下这个方法。

/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
        throws NotFoundException {
    synchronized (mAccessLock) {
        TypedValue value = mTmpValue;
        if (value == null) {
            mTmpValue = value = new TypedValue();
        }
        getValue(id, value, true);
        if (value.type == TypedValue.TYPE_STRING) {
            return loadXmlResourceParser(value.string.toString(), id,
                    value.assetCookie, type);
        }
        throw new NotFoundException(
                "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
                + Integer.toHexString(value.type) + " is not valid");
    }
}

 该方法首先取到Resources类的成员变量mTmpValue,赋给函数内的局部变量,并在为null的时候做了初始化处理,然后第8行执行了getValue方法,这个方法我们刚刚已经介绍过了,它将根据id来寻找资源值赋给TypedValue对象,然后,如果找到值的类型不为string类型的话,抛出异常。否则的话,则是执行了loadXmlResourceParser方法的重载版本,该重载版本具有四个参数,分别对应的是xml的文件名称,xml资源的id,附加信息,和值类型。

private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];

/*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
        int assetCookie, String type) throws NotFoundException {
    if (id != 0) {
        try {
            // These may be compiled...
            synchronized (mCachedXmlBlockIds) {
                // First see if this block is in our cache.
                final int num = mCachedXmlBlockIds.length;
                for (int i=0; iif (mCachedXmlBlockIds[i] == id) {
                        //System.out.println("**** REUSING XML BLOCK!  id="
                        //                   + id + ", index=" + i);
                        return mCachedXmlBlocks[i].newParser();
                    }
                }

                // Not in the cache, create a new block and put it at
                // the next slot in the cache.
                XmlBlock block = mAssets.openXmlBlockAsset(
                        assetCookie, file);
                if (block != null) {
                    int pos = mLastCachedXmlBlockIndex+1;
                    if (pos >= num) pos = 0;
                    mLastCachedXmlBlockIndex = pos;
                    XmlBlock oldBlock = mCachedXmlBlocks[pos];
                    if (oldBlock != null) {
                        oldBlock.close();
                    }
                    mCachedXmlBlockIds[pos] = id;
                    mCachedXmlBlocks[pos] = block;
                    //System.out.println("**** CACHING NEW XML BLOCK!  id="
                    //                   + id + ", index=" + pos);
                    return block.newParser();
                }
            }
        } catch (Exception e) {
            NotFoundException rnf = new NotFoundException(
                    "File " + file + " from xml type " + type + " resource ID #0x"
                    + Integer.toHexString(id));
            rnf.initCause(e);
            throw rnf;
        }
    }

 我去,这个方法有点长啊,但是内部逻辑并不复杂。
 首先,11-18行判断是否能在缓存区根据找到相应的xml块,然后如果找到的话,直接返回相应XmlBlock的解析器,否则的话,在22行,通过AssetMangeropenXmlBlockAsset方法去获得XmlBlock实例,内部原理我们也不去分析,因为有jni……从24行到36行开始的if往后都是把新获得的XmlBlock块放到缓存里面去,只是在最后一行,将该块的解析器作为返回值返回。当然,在其中任何一步出了问题,都会抛出异常。

 这样,我们就稀里糊涂的获得了一个我们需要的XmlResourceParser,因为这个loadXmlResourceParser四个参数的方法的返回值,同时也作为两个参数方法的返回值,被接下来这个方法给调用了……

public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
    return loadXmlResourceParser(id, "layout");
}

 嗯,这个方法只有一行代码……结果也就是我们刚才那个结果,那么这个getLayout方法有什么用呢?原来这个方法,真的就是被一个LayoutInflater类的inflate函数调用了。

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

LayoutInflater.inflater做了什么?_第1张图片

Part 2

至此,我们总结一下目前的发现。

inflate函数都需要一个XmlPullParser实例来解析xml文件,首先将layout的id传入,调用了Resources类的public XmlResourceParser getLayout(int resource)方法,其内部调用了/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)方法
,在其方法中通过public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)方法根据资源的id获得了一个携带xml文件信息的TypedValue对象,再根据该TypedValue对象的string字段,也就是xml文件的文件名,来调用/*package*/ XmlResourceParser loadXmlResourceParser(String file, int id,
int assetCookie, String type) throws NotFoundException
方法来获得一个Xml解析器。

LayoutInflater类的inflate函数有数个重载版本,包括
1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
2. public View inflate(XmlPullParser parser, @Nullable ViewGroup root)
3. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
4. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)

 所有方法最后都调用了上面的第四个方法。
 其中第一个参数XmlPullParser区别与我们平时指定的layout的id有所区别,所以在分析这个方法之前,我们要知道这个XmlPullParser是如何来的。

接下来,有了XmlPullParser对象,我们就可以分析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 {
                // 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;
        }
    }

 这个方法也是一如既往的长,不过这次我们不能在深入的往里面跳了……似乎无穷尽的样子。我们平时都是要么就是写inflater(R.layout.xxx,null);或者是inflater(R.layout.xxx,parent,false);但是似乎没人关系到底为何这么写,接下来我们就看一发。
 为了不造成混淆,我们把第一个参数统一称为parser,为int类型的时候成为id,第二个参数称为root,第三个参数我们姑且称为attachToRoot。
 我们已经做好了准备工作,也将R.layout.xxx换成了一个XmlPullParaser,我们先来看第9行,将你传进来的root赋给了一个变量result,顺便说一句,当你调用两个参数的inflater函数时,实际上你是调用了inflate(parser, root, root != null)。接下来第14行到第22行,是对xml结构的一次检查,这里我们暂且不讨论,因为要涉及到XmlBlock#Parser的实现。接下来24行到40行,也是一个特殊情况,涉及到标签,我们也先跳过,再往后,就是真正的重点了。

                    // Temp is the root view that was found in the xml
/*此处开始为42行*/    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);
                        }
                    }

 42行,调用了createViewFromTag方法来获取一个View,来自41行的注释表示,此时返回的View实际上就是你layout的根节点View。接下来44行到58行,表示如果你第二个参数parent不为空,而且第三个参数为false的话,我们就给你的创建View的根布局装上一个LayoutParams,而且这个params是根据Xml解析器parser解析出来的属性来创建的,参见第6行。

/*此处为第6行*/ final AttributeSet attrs = Xml.asAttributeSet(parser);

 通常我们当你使用Java代码来添加一个View至一个ViewGroup中时,View的LayoutParams类型一定和该ViewGroup的内部类ViewGroup#LayoutParams同属一个类型。52行的引用的attrs就是xml文件中的属性,至此,创建的View已经有了自己的LayoutParams。如果你第二个参数root传递的为null,inflater方法会认为你创建的View暂时不需要父布局,这样的话你View的根布局自然也没有约束,后期再使用这个View的话,就需要重新去指定一次LayoutParams

if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }

                    // Inflate all children under temp against its context.
//此处为65行         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.
//此处为73行         if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

 第65行,来自64行的注释表示这一行是实例化temp(也就是创建出来的View)的所有子View。第73行到75行,如果你的父布局parent为空且第三个参数为true,则将创建的View加入到父布局中,同时也指定了LayoutParams。这段代码实际上就是因为下面这种情况而执行的。

inflater(R.layout.xxx,parent,true);

此时,如果你inflater函数是这样调用的话,那么整个过程就已经结束了,根据102行的返回,你将直接获得的是你传入的第二个参数,也就是你期望将创建出来的View加入的那个父布局。如果这个时候你再次尝试将你创建出来的View来addView方法来加入到其他的ViewGroup中,则会抛出异常,因为这个View已经有父布局了。

如果你是这么调用的呢?

inflater(R.layout.xxx,null,false);
inflater(R.layout.xxx,null,true);
inflater(R.layout.xxx,null);
inflater(R.layout.xxx,parent,false);

 //此处为79行        if (root == null || !attachToRoot) {
                        result = temp;
                    }

 则都是执行了第79行到81行的逻辑。就是将你最后创建的View返回。但是上面这四种情况,获得的结果也不一样。前三种效果是一样的,因为第三种的实现实际上就等于第一种,然后第二种因为所有关于attachtoRoot的逻辑全部建立在root不为空上面,所以也是一样,前面三种实际上都只是执行了第42行和第65行,46行至58行的逻辑被跳过,然后返回一个没有LayoutParams的View,这样的View因为丢失了根节点的属性,所以在xml根节点设置的属性都会失效。

 上面第四种实现方式,完整的执行了46行到58行的逻辑,同时因为attachtoRoot为false的原因,没有执行73行到75行,所以返回了一个具有完整属性的View,所以就可以将这个View作为一个ViewGroup的addView方法的参数来传入,甚至这个ViewGroup不为先前提到的root。

 所以还是要根据自己的实际所需,选择对inflater函数做不同的实现方式。

你可能感兴趣的:(Android开发)