「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
我们在分析Activity的setContentView流程的时候,我们知道,解析layout是通过如下的代码来解析的
LayoutInflater.from(context).inflate(layoutResID, mContentParent);
今天,我们就来分析一下LayoutInflater对象的inflate函数流程
LayoutInflater的from函数
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;
}
可以看到,此处会调用Context对象的getSystemService函数,参数为Context.LAYOUT_INFLATER_SERVICE,getSystemService函数流程分析中已经对这个函数进行分析过,会调用SystemServiceRegsitry的getService函数,而这个参数的注册如下
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
亦即,最终会返回一个PhoneLayoutInflater对象
classDiagram
LayoutInflater <|-- PhoneLayoutInflater
class LayoutInflater {
<<abstract>>
+from(Context)
+cloneInContext(Context)*
+inflate(@LayoutRes int, @Nullable ViewGroup)
+inflate(XmlPullParser, @Nullable ViewGroup)
+inflate(@LayoutRes int, @Nullable ViewGroup, boolean)
-tryInflatePrecompiled(@LayoutRes int, Resources, @Nullable ViewGroup, boolean)
+inflate(XmlPullParser, @Nullable ViewGroup, boolean)
#rInflate(XmlPullParser, View, Context, AttributeSet, boolean)
}
class PhoneLayoutInflater {
#onCreateView(String, AttributeSet)
+cloneInContext(Context)
}
由上述类图可知,PhoneLayoutInflater是继承自LayoutInflater的,且LayoutInflater是一个抽象类
PhoneLayoutInflater类的inflate函数调用
从上述的类图可以看到,PhoneLayoutInflater类没有重写inflate函数,因此最终调用的是父类LayoutInflater的inflate函数
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// ...... 日志打印代码省略
// 1. 尝试预加载layout,若成功,直接返回对应的View
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 2. 获取layout的xml资源文件解析器
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
如上备注可知,此处先尝试预加载layout配置文件,若不成功则加载layout的资源文件解析器来解析layout,因此
尝试预加载layout
先看其代码
private @Nullable
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
boolean attachToRoot) {
// 此处的mUseCompiledView的值是什么?
// 追踪这个参数可知,在LayoutInflater对象的构造函数中,调用的initPrecompiledViews的无参函数,
// 会将这个参数的值设置为false,
// 也就是说,当我们使用正常的流程LayoutInflater.from(Context)来初始化PhoneLayoutInflater的时候,
// 这个参数的值一直为false,从而直接返回一个null的值
// 那么这个参数是在什么情况下为true呢?
// 查看代码可知,只有当setPrecompiledLayoutsEnabledForTesting函数被调用且参数设置为true的情况下,该参数才会为true
// 而setPrecompiledLayoutsEnabledForTesting函数只有CTS测试验证代码才能调用
if (!mUseCompiledView) {
return null;
}
// ...... trace
// ...... CTS测试使用代码,此处不做分析,暂时省略
}
如上代码所知,上述函数只有在CTS测试的情况下,才可能会返回一个非null的View对象,因此此处我们不做分析
资源文件解析器解析layout
final Resources res = getContext().getResources();
// ......
// 获取layout资源解析器
XmlResourceParser parser = res.getLayout(resource);
try {
// 解析对应的layout
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
可以看到,此处先获取一个layout资源解析器,然后在调用重载inflate函数来解析对应的View对象
获取layout资源解析器
Resources.java
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
// 调用loadXmlResourceParser函数加载一个资源解析器
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
// 确认是否能够找到对应的layout,若未找到,则抛出NotFoundException异常
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
// 最终调用loadXmlResourceParser函数
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");
} finally {
releaseTempTypedValue(value);
}
}
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie, String type) throws NotFoundException {
// 分析这个函数,会返回一个XmlBlock.Parser对象
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
如此,最终会返回一个XmlBlock.Parser对象
inflate重载函数调用
inflate(parser, root, attachToRoot);
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// ...... trace
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 前往xml文件的起始节点
advanceToRootNode(parser);
final String name = parser.getName();
// ...... debug code
// 开始解析xml文件
// 如果是merge节点,则进入if语句
if (TAG_MERGE.equals(name)) {
// 此处的if判断也即表明,在LayoutInflater的inflate函数中传入的layout中只包含merge节点对应的节点
// 则不允许传入的第二个参数root为null
// 否则会抛出异常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 当为merge节点的时候,此时直接进入解析,并最终会调用到createViewFromTag来初始化View对象
// 通过rInflateChildren函数解析上述View对象的子View,最后递归解析子View的子View,
// 并最后添加到上述的View对象中
// 最终会添加到root对象中
// 这边使用了递归的思想解析完成后添加到最终的root中
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
// 根据xml中的节点信息创建一个View对象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// ...... debug code
// 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);
}
}
// ...... debug code
// Inflate all children under temp against its context.
// 以刚刚创建的temp对象为父Container,继续解析对应的子View
// 此函数最终会调用到
// 此处会递归调用,有兴趣可以分析一下流程
rInflateChildren(parser, temp, attrs, true);
// ...... debug code
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
// 如果root不为null,则表明root是当前解析的layout的父框架,则将解析出来的View添加到该父框架中
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.
// 若root为null,表明当前解析的layout即为当前的最外层Container,因此直接返回创建的View对象即可
if (root == null || !attachToRoot) {
result = temp;
}
}
}
// ...... catch and finally code, delete
return result;
}
}
也就是说,在xml资源解析器解析layout对应的xml的时候,有两种情况
- 当根节点为merge时,则LayoutInflater的inflate函数传入的第二个参数root不能为null,否则会抛出异常,然后资源解析器解析layout文件的时候,根据其对应的节点解析对应的View对象,并添加到对应的父View中
- 当根节点为非merge的时候,直接根据对应的节点名生成对应的View,并递归添加到对应的父View中
如此,LayoutInflater类的inflate函数简要流程分析结束