LayoutInflater是什么?
LayoutInflater是Android系统的一个服务,我们可以通过它,把布局文件动态生成View,实现View视图的动态添加。
LayoutInflater在Android日常开发工作中经常使用到,并且我们经常调用的Activity的setContentView方法,它的内部实现就用到了LayoutInflater。
LayoutInflater对象的获取方式
我们可以通过多种方式来调用LayoutInflater服务。
方法一:LayoutInflater.from(context)
我们可以通过LayoutInflater.from(context)方法来返回LayoutInflater对象,来看源码:
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确实是一个系统服务,服务名称是Context.LAYOUT_INFLATER_SERVICE,可以通过context.getSystemService来获得。
- 如果获取失败,则抛出错误。
方法二:通过context.getSystemService方法来获取
其实方法1只是方法2的简单封装而已,这里我们直接通过getSystemService来获取:
LayoutInflater layoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
方法三:在Activity中,调用getLayoutInflater方法
这个方法最简单,我们可以直接在Activity中调用getLayoutInflater方法来获取。
Activity的getLayoutInflater方法源码:
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
逻辑解析:
- 这里直接返回的是getWindow()的getLayoutInflater()方法。
- getWindow()返回的是Activity所对应的PhoneWindow对象。
- PhoneWindow对象,在初始化时,通过LayoutInflater.from(context)获取了LayoutInflater对象。
关于Activity显示以及内部视图的创建等逻辑,我们会在今后的文章中进行源码分析。
LayoutInflater的使用
我们获取LayoutInflater实例之后,通过它的inflate方法来使布局生效。
layoutInflater.inflate(R.layout.activity_main, viewGroup);
示例中,使用layoutInflater.inflate将布局R.layout.activity_main作为子视图添加到viewGroup视图容器中。
接下来我们来分析inflate方法的源码实现。
LayoutInflater布局解析分析
LayoutInflater的inflate方法负责解析布局并生成View对象,我们接下来分析它的源码实现。
来看inflate方法:
frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, 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) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
逻辑解析:
- 可以看到inflate方法有多个方法重载。
- 首先尝试从预编译缓存中获取View对象,如果成功,则直接返回,这里的预编译是什么,稍后我们介绍。
- 如果预编译中获取失败,则调用res的getLayout方法获取XML的资源解析器,这里用的是PULL解析器(前一章中有介绍)。
- 最后调用inflate方法进行解析。
tryInflatePrecompiled方法(预编译选项)
tryInflatePrecompiled是Android 10(Android Q)中新增的方法,用来根据布局文件的xml预编译生成dex,然后通过反射来生成对应的View,从而减少XmlPullParser解析Xml的时间。它是一个编译优化选项,我们下面来分析。
private @Nullable
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
boolean attachToRoot) {
//如果mUseCompiledView是false,则表示预编译优化开个没有打开,直接返回
if (!mUseCompiledView) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
// 获得布局资源对应的packageName和layout名称
String pkg = res.getResourcePackageName(resource);
String layout = res.getResourceEntryName(resource);
try {
//获得 "包名" + ".CompiledView"的类
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
//获得以layout名称命名的方法
Method inflater = clazz.getMethod(layout, Context.class, int.class);
//执行该方法,返回View对象。
View view = (View) inflater.invoke(null, mContext, resource);
//最后进行View的布局设置并根据条件判断是否添加到父视图中
if (view != null && root != null) {
// We were able to use the precompiled inflater, but now we need to do some work to
// attach the view to the root correctly.
XmlResourceParser parser = res.getLayout(resource);
try {
AttributeSet attrs = Xml.asAttributeSet(parser);
advanceToRootNode(parser);
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
if (attachToRoot) {
root.addView(view, params);
} else {
view.setLayoutParams(params);
}
} finally {
parser.close();
}
}
return view;
} catch (Throwable e) {
if (DEBUG) {
Log.e(TAG, "Failed to use precompiled view", e);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return null;
}
逻辑解析:
- 首先根据mUseCompiledView判断预编译是否是开启状态,如果mUseCompiledView是false,则表示预编译优化开个没有打开,直接返回。
- 获得布局资源对应的packageName和layout名称。
- 预加载编译资源,存储在以"包名" + ".CompiledView"的类中,并且布局资源对应了一个以layout名为名称的方法。
- 使用反射调用,执行该方法,就会返回一个资源布局对应的View视图对象。
- 最后进行View的布局设置并根据条件判断是否添加到父视图中。
预编译选项开关
我们来看预编译选项的开关,mUseCompiledView变量是何时设置的。
private boolean mUseCompiledView;
private void initPrecompiledViews() {
// Precompiled layouts are not supported in this release.
boolean enabled = false;
initPrecompiledViews(enabled);
}
private void initPrecompiledViews(boolean enablePrecompiledViews) {
mUseCompiledView = enablePrecompiledViews;
if (!mUseCompiledView) {
mPrecompiledClassLoader = null;
return;
}
」
它是在initPrecompiledViews方法中进行设置的,其中无参的默认为false,表示不开启。
我们来看initPrecompiledViews调用的地方:
protected LayoutInflater(Context context) {
mContext = context;
initPrecompiledViews();
}
/**
* @hide for use by CTS tests
*/
@TestApi
public void setPrecompiledLayoutsEnabledForTesting(boolean enablePrecompiledLayouts) {
initPrecompiledViews(enablePrecompiledLayouts);
}
可以看到,系统只调用了initPrecompiledViews()无参的方法,表示默认关闭预编译优化。带参数的initPrecompiledViews方法只在内部测试时使用。
inflate方法
我们继续来看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);
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//如果根标签是merge标签
if (TAG_MERGE.equals(name)) {
//如果根节点是merge,并且root是null或者attachToRoot == false,则抛出异常。
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//遍历布局并生成View
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建根标签的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) {
// 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");
}
//遍历布局文件,生成所有子View
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);
}
//如果父容器root是空,或者attachToRoot==false,则直接返回布局文件的根View。
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;
}
}
通过分析我们可以看到,该方法实现了布局文件生成View的过程。createViewFromTag()方法实现了布局文件根View的创建,rInflateChildren(parser, temp, attrs, true)会调用rInflate()方法,递归实例化子View,过程中也会使用createViewFromTag()方法创建具体的子View。
rInflate和rInflateChildren方法都是通过递归解析xml中的布局,创建View,并添加到parent中,逻辑比较简单我们就不做具体分析了。
参数含义及注意事项
我们从以上分析中可以总结出该方法及参数使用的几个特点:
- 参数root为默认父视图,如果为null,表示布局文件生成的View不会添加到默认视图中。attachToRoot属性将没有意义。
- 参数root如果不为null,参数attachToRoot表示是否添加到父视图中
- 当 attachToRoot为true时,则会把布局View添加到root中,作为root的子视图。
- 当 attachToRoot为false时,不会把布局View添加到默认父视图root中,但是会把layout参数设置给布局View,当布局View被添加到父view当中时,这些layout属性会自动生效。
- 当布局文件根节点是merge标签时,root必须不为null,并且attachToRoot必须为true,否则就会抛出InflateException异常。
- attachToRoot默认值,当root != null时为true,root == null时为false。
总结
- LayoutInflater是Android系统的一个服务,我们可以通过它,把布局文件动态生成View,实现View视图的动态添加。
- Activity的setContentView方法,它的内部实现就用到了LayoutInflater。
- 可以通过多种方式来调用LayoutInflater:LayoutInflater.from(context)、context.getSystemService方法、在Activity中,调用getLayoutInflater方法。其实本质上都是获取LayoutInflater系统服务。
- LayoutInflater的inflate方法负责解析布局并生成View对象。首先尝试从预编译缓存中获取View对象,如果成功,则直接返回;如果预编译中获取失败,则会使用PULL解析器进行解析。
- 预编译优化是Android Q的新增逻辑,默认是关闭的。
- rInflate和rInflateChildren方法都是通过递归解析xml中的布局,创建View,并添加到parent中。
LayoutInflater生成View的参数含义如下:
- 参数root为默认父视图,如果为null,表示布局文件生成的View不会添加到默认视图中。attachToRoot属性将没有意义。
- 参数root如果不为null,参数attachToRoot表示是否添加到父视图中
- 当 attachToRoot为true时,则会把布局View添加到root中,作为root的子视图。
- 当 attachToRoot为false时,不会把布局View添加到默认父视图root中,但是会把layout参数设置给布局View,当布局View被添加到父view当中时,这些layout属性会自动生效。
- 当布局文件根节点是merge标签时,root必须不为null,并且attachToRoot必须为true,否则就会抛出InflateException异常。
- attachToRoot默认值,当root != null时为true,root == null时为false。
转载:https://blog.csdn.net/u011578734/article/details/106420419