LayoutInflater工作原理
简介
LayoutInflater用来加载 xml 文件,将 xml 文件中的 View 和 ViewGoup 进行实例化,我们通常可以在 Fragment 和 RecyclerView.Adapter 中见到它的身影,本篇文章主要说说它的用法和工作原理
说明
LayoutInflater是一个抽象类,我们通常使用它的 from(Context context) 静态方法创建一个 LayoutInflater 实例。
用法
View view = LayoutInflater.from(context).inflate(R.layout.first_item, parent, false);
通过 inflate() 方法来加载 View, inflate()有以下4个重载方法
1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root){};
2. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {}
3. public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {};
4. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {}
inflate() 的4个重载方法,最终都是调用到第4个方法中,前两个方法会创建一个 XmlPullParser, 调用后面两个方法,那么我们主要看的就是第4个方法,这个方法有三个参数
- XmlPullParser,用来解析 xml 文件,
- ViewGroup,view 所在的 viewGroup,如果没有,可以传 null
- boolean 的 attachToRoot参数,标识是否要绑定到 根视图,就是第二个 root 中
我们可以看到,该方法是通过 inflate 进行资源解析的,那么下面我们通过源码的方式解析以下,inflate 方法究竟做了什么
原理解析
上面说到 inflate 有4个重载方法,我们先看前两个的源码:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//1. 获取Resources, Resources是安卓的资源管理类
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//2. 解析xml
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
代码分为两步:第一步获取 Resoureces 资源, 第二步通过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 {
advanceToRootNode(parser);
//1. 获取 xml root节点名字
final String name = parser.getName();
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 {
// 2. 根据根节点的名字创建 View
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) {
// attachToRoot为false,view不会被添加到root中,root 决定该怎么放置视图,在recyclerview中,都是传的false,因为recyclerview会自动管理viewholder的添加和删除
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// 3. 遍历子节点,生成子视图
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) {
//如果 attachToRoot 为 true,就添加到子视图中
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(
getParserStateDescription(inflaterContext, attrs)
+ ": " + 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;
}
}
inflate()方法就是正式解析 xml 的,第一步获取对应的根节点名称,第二步根据根节点名称通过createViewFromTag() 方法创建 View, 第三步遍历子节点,通过 rInflateChildren() 方法生成所有的子视图。
接下来看一下上面提到的两个重要的方法 createViewFromTag() 和 rInflateChildren() :
createViewFromTag()方法
返回值是个 View,参数是 parent父视图,name 根节点名称,下面我们看下这个方法都做了什么
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//创建视图
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(context, attrs)
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
该方法最终是调用到 createView()方法,在 createView()方法中,通过Class.forName()反射来创建 View,至此,View 就创建好了
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
Constructor extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// 通过反射 new 一个 class 对象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
//此处代码省略 .....
}
rInflateChildren() 方法
这个方法用来遍历创建子视图, 最终调用到rInflate()方法, 方法中有个 while 循环,一步步的遍历 xml 文件,找到子节点,通过上边的 createViewFromTag 方法创建子视图, 然后将子视图加入到根节点中
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(" 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 (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
总结一下
LayoutInflater 是通过 inflate 方法来解析 xml,解析 xml 分为以下几步
- 解析 layout 文件xml 找到根节点的名称
- 通过映射 Class.forName() 创建根节点视图View,如果需要 attachToRoot,绑定到父视图, 就把根节点 View add 到父视图中
- 循环遍历子节点,找到子节点名称
- 通过映射 Class.forName()方法创建子视图,然后加入到根节点中
可见我们 xml 中所写的布局都会经过遍历创建一个 class,所以我们的 xml 文件越复杂,节点越多,创建的类就越多,对内存消耗也越大