LayoutInflater中inflate函数总共有4个形态
View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
View inflate(XmlPullParser parser, @Nullable ViewGroup root)
View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
前两个是我们经常用的,且第一个也是直接调用第二个的,那我们来分析一下第二种
/frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
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();
}
}
从代码中可以看出,先使用预编译加载(Android10的release版本暂时不支持),如果失败了,再使用inflate的另一个重载的函数来实现功能,下面我们看下tryInflatePrecompiled函数的实现
/frameworks/base/core/java/android/view/LayoutInflater.java
private @Nullable
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
// 如果不使用预编译,就直接返回
boolean attachToRoot) {
if (!mUseCompiledView) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
// Try to inflate using a precompiled layout.
// 获取resource资源所在包名
String pkg = res.getResourcePackageName(resource);
// 获取resource资源的xml文件的名字
String layout = res.getResourceEntryName(resource);
try {
// 获取预编译好的类,针对每个包资源里面的layout目录都会编译成一个类,layout目录中的每一个xml都会对应成一个方法,方法名就是xml文件的名字
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
// 找到对应xml文件名的方法
Method inflater = clazz.getMethod(layout, Context.class, int.class);
// 调用找到的方法
View view = (View) inflater.invoke(null, mContext, resource);
// 根据 parent和 attachToRoot 来设置View的Layout参数
if (view != null && root != null) {
// 解析resource对应的xml,获取根对象layout_width和layout_height参数
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) {
} finally {
}
return null;
}
该函数的大体流程是,现获取xml资源所在包的包名,在获取资源对应的xml文件名,然后从mPrecompiledClassLoader中获取类名和包名一致的类对象,获取对象的的方法(方法名是前面获取的xml资源文件名),然后调用该方法生成View,在根据tryInflatePrecompiled函数传入的参数设置View的LayoutParams.
下来我们看一下mPrecompiledClassLoader对象,看它是怎么来的
/frameworks/base/core/java/android/view/LayoutInflater.java
private void initPrecompiledViews(boolean enablePrecompiledViews) {
...
mPrecompiledClassLoader = mContext.getClassLoader();
String dexFile = mContext.getCodeCacheDir() + COMPILED_VIEW_DEX_FILE_NAME;
if (new File(dexFile).exists()) {
mPrecompiledClassLoader = new PathClassLoader(dexFile, mPrecompiledClassLoader);
}
....
}
看代码得知mPrecompiledClassLoader是一个PathClassLoader对象,加载一个名为"/compiled_view.dex"的文件,这个文件所在路径是mContext.getCodeCacheDir()函数获取的(这是一个文件系统上的绝对路径,用来缓存代码的,升级或删除app都会清空这个路径下的文件),个人猜测这个compiled_view.dex文件应该是app安装的时候系统生成的,把app资源里面的layout目录下的xml都预先编译成能生成对应View的类,这样每次在inflate一个layout.xml时不用再次解析xml文件,从而加快layout的inflate过程,好了,预加载就先告一段落,下面我们看一下预加载失败的处理流程,XmlResourceParser parser = res.getLayout(resource),然后调用View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 函数,Resource.getLayout(int resourceId)的过程我们在另一篇文章中做分析,下面我们看看View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) 函数:
/frameworks/base/core/java/android/view/LayoutInflater.java
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
// 获取xml跟对象的属性组
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 (TAG_MERGE.equals(name)) {// 如果是merge标签,merge一般和include,它必须挂载在一个root下,如果传入的参数不合法,就会抛出异常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 解析merge标签
rInflate(parser, root, inflaterContext, attrs, false);
} else { // 正常View节点
// Temp is the root view that was found in the xml
// 创建XML中根节点对应的View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// 设置根节点View的LayoutParams
ViewGroup.LayoutParams params = null;
if (root != null) {
// 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);
}
}
// 创建根节点的所有子节点
rInflateChildren(parser, temp, attrs, true);
// 根据传入的参数设置XML根节点的挂载情况
if (root != null && attachToRoot) {
root.addView(temp, params);
}
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;
}
}
大致流程是,判断XML的跟标签的类型,如果是merge,调用rInflate(parser, root, inflaterContext, attrs, false); 其他则先调用 final View temp = createViewFromTag(root, name, inflaterContext, attrs);创建TopView,然后在调用rInflateChildren(parser, temp, attrs, true);创建TopView的所有跟节点,下面分析下View createViewFromTag(View parent, String name, Context context, AttributeSet attrs)
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// 如果标签名为view,则去属性的中的class对应值作为带创建View的类名
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 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(Exception e)
{
}
}
根据XML标签创建对象时先调用tryCreateView来创建对象
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
在tryCreateView中有个特殊处理,就是发现标签是blink是,直接创建一个BlinkLayout后返回,后面尝试用内部Factoty创建对象,这里是Android提供给用户的扩展,我们可以自定义Factory来接管对象创建过程
在调用tryCreateView创建不成功时,进入系统默认的创建流程,如果是XML标签不带“.”则默认是系统内置View,调用onCreateView,否则认为是用户自定义的View,调用createView,实际上onCreateView中是用prefix="android.view"参数调用createView,那么我们看下createView
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
...
// 从缓存中找类构造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
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 -- remember whether it is allowed
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);
}
}
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
// 这里是重点,调用构造器并传入属性创建View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) { // 如果是ViewStub,保存下当前的Context
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
} catch (Exception e) {
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
从上面代码可以看到,createView函数会现在缓存中找View类构造器,没找到就根据name和prefix创建类构造器,然后调用View view = constructor.newInstance(args);来创建View,View创建成功后,针对ViewStub做特殊处理,保存当前的Context到View中,方面后面inflate时用,调用该函数收,就得到了一个View对象
下来我们看下rInflateChildren创建TOPView的自VIew流程
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
它内部直接调用了rInflate(parser, parent, parent.getContext(), attrs, finishInflate);,前面我们知道,在标签名为merge的时候调用了rInflate,那么来看看这两次调用有什么不同,merge时rInflate(parser, root, inflaterContext, attrs, false); 创建TopView子View时
rInflateChildren(parser, temp, attrs, true);其实就是Parent和最后的参数不一样,我们看看rInflate的代码
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)) { // 处理requestFocus标签
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) { // 处理tag标签
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { // 处理include标签
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) { // 处理merge标签,merge只能是跟,所以不能出现在这里
throw new InflateException("<merge /> must be the root element");
} else { // 这里是真正处理创建View的过程
// 创建一个View
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);
// 添加到父View里面
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
上面过程就是用while循环把当前View的所有直接子VIew都创建出来,然后在递归调用rInflateChildren(parser, view, attrs, true);把子View的子View创建出来,到此inflate就流程就分析完成了
我们总结下inflate流程
1、首先调用tryInflatePreCompile函数通过预编译的方式创建View,不过这个逻辑在10.0.0的release版本中还没有生效,这个过程需要安装APK时配合,预先把layout资源编译成对应的类
2、如果上一步失败了,就调用Resource类的getLayout函数获取layout资源的XmlResourceParser对象,然后调用inflate函数的另一个重载形式:View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
3、获取跟标签及其属性,判断跟标签是不是"merge",如果是就调用rInflate函数,如果不是调用createViewFromTag函数创建根标签对应的TopView,然后调用rInflaterChild函数创建根标签的所以子标签对应View(这是一个递归过程);
4、根据传入的参数则是TopView的LayoutParams参数或者是直接添加到传入的root对象
5、根据传入的参数决定是返回TopView(root == null || bAttachRoot == false),还是直接返回传入的root(root != null && bAttachRoot );
6、整个流程结束