Android LayoutInflater工作原理

在Android中LayoutInflater用的地方有很多,它的作用就是将用xm编写的布局文件转换为View对象。今天我们就来分析它的实现原理。

我们知道在Activity中我们向其中添加布局是通过setContentView(@LayoutRes int layoutResID)这个方法来添加的,我们今天就从这个地方开始一步一步分析它是怎么将xml描述的view添加到我们的Activity窗口,知道显示在用户眼前的。我们先进入setContentView方法,方法定义如下

public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
我们看到它调用了Window的setContentView,但是Window类中对应的方法是抽象方法,那么它在哪里实现的呢。稍微对源码有些了解的人都知道,Window类的实现类是PhoneWindow,因此我们进入PhoneWindow类的对应的方法,看看其中的方法,实现如下:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

在方法中如果没有设置场景动画,它会走 mLayoutInflater.inflate(layoutResID, mContentParent);这个方法。而mLayoutInflater是PhoneLayoutInflater对象,它是LayoutInflater对象的子类。我们进入inflate方法,定义如下:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}

我们发现如果传入的root不为null,第三个参数就为true。由上面我们可以知道root是不为null的。我们接着往下走:

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();
    }
}
我们看到这个方法主要做了两件事情,一件是创建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 {
            // 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) {
            final InflateException ie = new InflateException(e.getMessage(), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (Exception e) {
            final InflateException ie = new InflateException(parser.getPositionDescription()
                    + ": " + 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;
    }
}

这个方法有点长,这个主要是解析XML,分为XML的名称是否为merge两种情况来解析。

下面我们就分这两种情况来分析。

第一种情况就是XML标签名称等于merge时候:

当传入的root(根View)为null和attachToRoot(是否添加到根View)为false其中就抛出异常,否则进入如下方法:

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

由上面方法我们知道它解析Xml分如下五种情况来执行。分别是Xml标签名称为requestFocus,tag,include,merge和其它标签名称这物种情况。
当为requestFocus时候:
首先将pendingRequestFocus置为true,再进入下面这个方法:

final static void consumeChildElements(XmlPullParser parser)
throws XmlPullParserException, IOException {
int type;
final int currentDepth = parser.getDepth();
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > currentDepth) && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
}

我们看到这里面就对XML进行遍历,直到遍历到requestFocus的内部xml,就跳出循环(目的就是接着遍历它的子XML)。
而pendingRequestFocus置为true有什么作用呢,我们看看如下定义,

if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
由此我们知道了,它是将当前解析的父Viwe即parent请求获取焦点。

当XML标签名称等于tag的时候:
会执行如下方法
private void parseViewTag(XmlPullParser parser, View view, AttributeSet attrs)
throws XmlPullParserException, IOException {
final Context context = view.getContext();
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewTag);
final int key = ta.getResourceId(R.styleable.ViewTag_id, 0);
final CharSequence value = ta.getText(R.styleable.ViewTag_value);
view.setTag(key, value);
ta.recycle();

    consumeChildElements(parser);
}

我们看到这个方法就是获取当前View的id和对应的tag值,将id作为键区域,将tag作为值区域将这个tag保存到当前View中。consumeChildElements这个方法就是将xml解析到末尾处。

当XML标签值是include的时候:
如果它是作为顶层View的时候,会抛出“ cannot be the root element”异常,
否则会进入到parseInclude这个方法,由于这个方法比较长,在这里我就不贴出来了。就简单介绍下这个方法主要作用:
第一,保证include标签必须是ViewGroup或者是它的子类;
第二,如果设置了主题,就初始化ContextThemeWrapper这个对象;
第三,通过已经初始化了的ContextThemeWrapper对象来获取include的XML文件;
第四,解析XML布局文件,并且为它生成对应的布局参数即LayoutParams对象,将解析的子View添加到父View(parent)中去。

如果XML标签值是merge时候抛出异常,提示“ must be the root element”;

否则通过类加载器来加载类并且调用其构造方法来进行初始化,并为其生成设置LayoutParams。再循环解析初始化被它所包围的子View。

最后如果root不为null并且attachToRoot为false,就为生成的view设置LayoutParams
如果root不为null并且attachToRoot为true的时候,将生成的LayoutParams对象设置到新生成的View中并且将新生成的View添加到根布局中,最后将添加了新生成的View的根布局返回。
如果root为null或者attachToRoot为false就将新生成View(没有设置LayoutParams)返回。

至此LayoutInflater实现原理分析完毕了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值