在平常开发中,我常用LayoutInflater
将一个xml布局初始化为一个View对象,可是对它内部原理的了解倒是少之又少。今天,咱们就来看看LayoutInflater
。android
本文主要内容:缓存
LayoutInflater
建立流程。咱们经过Activity
或者LayoutInflater
的from方法来建立一个对象,咱们去看看这俩方法有啥区别。- View 建立流程。主要介绍
LayoutInflater
将一个xml解析成为一个View经历的过程。setContentView
方法解析。分析了View
的建立流程,咱们再来看看是怎么初始化Activity
的ContentView
。
本文参考文章:bash
熟悉LayoutInflater
的同窗应该都知道,建立LayoutInflater
对象有两种方式:app
- 经过
Activity
的getLayoutInflater
方法。- 经过
LayoutInflater
的from方法。
但是这俩方法有啥区别呢?这是本节须要解答的地方。ide
不过在此以前,咱们先来了解Context的继承类图。 布局
可能有人会问,咱们分析LayoutInflater
,为何还要去了解Context
的结构呢?这是由于LayoutInflater
自己就是系统的一个服务,是经过Context
的getSystemService
方法来获取的。post
根据源码咱们知道,全部的系统服务都是在SystemServiceRegistry
类里面进行注册,而后统一在ContextImpl
进行获取,固然也包括LayoutInflater
。性能
咱们经过Activity
的getLayoutInflater
方法获取的其实是Window
里面的LayoutInflater
对象,而Window的LayoutInflater
对象是在构造方法里面初始初始化的:ui
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
复制代码
此时这个Context就是Activity
的对象。因此从本质上来看,Activity
的getLayoutInflater
方法和LayoutInflater
的from方法没有很大的区别,惟一区别的在于这个Context对象的不一样。咱们来看一看from方法:this
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;
}
复制代码
在from
方法里面调用的是Context
的getSystemService
方法,如今咱们必须得了解整个Context的继承体系。 假设这里的Context
是Activity
,那么这里调用的就是Context
的getSystemService
方法
@Override
public Object getSystemService(String name) {
return mBase.getSystemService(name);
}
复制代码
那这里的mBase又是什么呢?从上面的类图咱们知道是ContextImpl的对象,怎么来证实呢?
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ······
// 1. 建立ContextImpl的对象
ContextImpl appContext = createBaseContextForActivity(r);
// ······
// 2. 调用Activity的attach方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
// ······
}
//---------------Activity--------------------------
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
// 将ContextImpl传递给父类
attachBaseContext(context);
// ·······
}
//---------------ContextWapper---------------------
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
复制代码
整个调用链很是的清晰,分别是:ActivityThread#performLaunchActivity
-> Activity#attach
-> ContextWapper#attachBaseContext
。
而后,咱们再去看看ContextImpl
的getSystemService
方法:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
复制代码
最终的对象是从SystemServiceRegistry
里面获取的。
LayoutInflater
是经过inflate
方法将一个xml布局解析成为一个View。咱们都知道inflate
方法一般有三个参数,分别是:resource
、root
、attachToRoot
,表示的含义以下:
- resource:xml布局的id。
- root:解析成以后的View的父View,此参数只在
attachToRoot
为true才生效。- attachToRoot:决定解析出来的View是否添加到
root
上。
有人可能会好奇,为何须要第三个参数,这是由于咱们将xml解析成View不必定当即须要添加到一个ViewGroup
中去,这是为何呢?想想RecyclerView
,RecyclerView
在初始化ItemView
时,不是当即将ItemView
添加进去,而是当ItemView
进入屏幕可见区域时才会添加,由于RecyclerView
有预加载机制,会加载一部分屏幕外的ItemView
。
咱们先看一下inflate方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
try {
// ······
// 1. 若是是merge标签,直接绕过merge标签,
// 解析merge标签下面的View。
if (TAG_MERGE.equals(name)) {
// ······
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 2.建立View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// ······
// 3.递归解析children
rInflateChildren(parser, temp, attrs, true);
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(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
return result;
}
}
复制代码
inflate
方法里面一种作了2件事:
- 若是根View是merge,直接递归解析它的子View。
- 若是根View不是merge,先解析根View,而后在递归解析它全部的child。
咱们分为两步来看,先看一下解析根标签。
根View的解析与child的解析不同,是经过createViewFromTag
方法来完成的:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// ······
View view;
// 1. 若是mFactory2不为空,优先让mFactory2处理
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 2. 若是上面解析为空,再使用mPrivateFactory常识这解析
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 若是是系统widget包下的控件
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else { // 若是是第三方包或者自定义的控件
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
// ······
}
复制代码
inflate
方法里面主要作了三件事:
- 首先使用
mFactory2
和mFactory
来尝试着建立View对象。mFactory2
和mFactory
两者有且只能有一个有值,因此只须要调用其中一个就好了。- 若是第一步中的两个工厂都没法建立View对象,再尝试着使用
mPrivateFactory
对象建立。不过一般来讲,这个对象都是为空的。- 最后一步就是走兜底逻辑。这里的兜底有一点的特殊:若是View是
widget
的控件,会先在前面加一个android.wiget.
的前缀,再行建立View;其次,若是是其余包下的控件,好比说,androidX和自定义的控件,就直接建立View对象。
关于第一点,我还想介绍一下,Google爸爸之因此要设计两个工厂类,主要有3个方面的考虑:
- 兼容性,后面发布的版本能够兼容以前的版本,好比说,
AppCompatActivity
是新推出来的组件,因此在新版本上使用的mFactory2
,旧版本就走原来的原来逻辑,也就是默认的onCreateView
方法。- 扩展性,若是开发者须要自定义一种全局的样式或者手动建立解析View,能够直接给
LayoutInflayer
设置Factory
,用来达到本身的目的。- 提高性能,这一点能够从能够从
AppCompatActivity
里面看出。AppCompatActivity
的内部给LayoutInflayer
设置了一个Factory2
,也就是AppCompatDelegateImpl
对象。AppCompatDelegateImpl
在解析xml时,会先判断当前View是否基础控件,好比说,Button
、TextView
或者ImageView
等,若是是的话,能够经过new 的方式建立对应的AppCompatXXX
对象。之因此说它提高性能,是由于它在解析基础控件时,再也不经过反射,而是经过new的方式建立的。
上面的第三点可能有点复杂,咱们能够直接看一下AppCompatDelegateImpl
的createView
方法。因为createView
内部调用了AppCompatViewInflater
的createView
方法,因此这里咱们直接看AppCompatViewInflater
的createView
方法:
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// ······
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}
// ······
return view;
}
复制代码
在默认的状况下,建立View对象的真正操做在createView
方法里面,咱们能够来看看:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// ······
// 若是缓存中没有View的构造方法对象,
// 那么就建立一个,而且放入缓存中去。
if (constructor == null) {
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
if (mFilter != null) {
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
// ······
}
复制代码
从这里,咱们就能够知道,LayoutInflater
建立View的本质就是Java反射,因此在咱们平常开发过程当中,尽可能不要套太深的布局,毕竟反射的性能是有目共睹的。
children的解析其实是在rInflate
方法里面进行的,咱们直接来看源码:
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
// ······
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();
// requestFocus标签
if (TAG_REQUEST_FOCUS.equals(name)) {
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标签
throw new InflateException("<merge /> must be the root element");
} else { // 正常View或者ViewGroup
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);
}
}
// ······
}
复制代码
children的遍历就像是一个树的遍历,就是一种广搜的思想,这里就不过多的讨论了。
说完了上面的原理,最后咱们在来看看AppCompatActivity
的setContentView
方法。在LayoutInflater
方面,AppCompatActivity
相比于Activity
,给LayoutInflater
设置了一个Factory2
,也就是上面讨论的东西。
这里咱们再也不讨论以前谈论过的东西,而是看一个有趣的东西,我也不知道Google爸爸是怎么想的。 AppCompatActivity
的onCreate
方法内部会给LayoutInflater
设置一个Factory2
对象,整个调用链是:AppCompatActivity#onCreate
-> AppCompatDelegateImpl#installViewFactory
-> LayoutInflaterCompat#setFactory2
-> LayoutInflaterCompat#forceSetFactory2
。咱们直接来看setFactory2
方法:
private static void forceSetFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
if (!sCheckedField) {
try {
sLayoutInflaterFactory2Field = LayoutInflater.class.getDeclaredField("mFactory2");
sLayoutInflaterFactory2Field.setAccessible(true);
} catch (NoSuchFieldException e) {
Log.e(TAG, "forceSetFactory2 Could not find field 'mFactory2' on class "
+ LayoutInflater.class.getName()
+ "; inflation may have unexpected results.", e);
}
sCheckedField = true;
}
if (sLayoutInflaterFactory2Field != null) {
try {
sLayoutInflaterFactory2Field.set(inflater, factory);
} catch (IllegalAccessException e) {
Log.e(TAG, "forceSetFactory2 could not set the Factory2 on LayoutInflater "
+ inflater + "; inflation may have unexpected results.", e);
}
}
}
复制代码
看到这个神奇操做没?万万没想到Google是经过反射的方式来给mFactory2
方法。爸爸为啥要这样作呢?我猜想是setFactory2
方法的坑,咱们来看看:
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
复制代码
只要Factory
被设置过,不管是Factory
仍是Factory2
,都不容许被再次设置。因此,我猜想是,爸爸为了成功给mFactory2
设置上值,经过反射来绕开这种限制,这也是在是无奈。
设置了Factory2
工厂类以后,就是调用setContentView
方法来给Activity
设置ContentView
。咱们这里直接来看一下AppCompatDelegateImpl
的setContentView
方法:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
复制代码
从这里咱们能够看出来,contentView是经过LayoutInflater
加载出来的。具体的细节就再也不讨论了,上面已经详细的分析过了。
到此为止,本文算是为止。总的来讲,本文仍是简单的(隐约的感受到,本文有点水),在这里,咱们对本文的内容作一个简单的总结。
Activity
的getLayoutInflater
方法和LayoutInflater
在本质没有任何的区别,最终都会调用到ContextImpl
的getSystemService
方法里面去。LayoutInflater
初始化View分为三步:1.调用mFactory2
或者mFactory
方法来解析xml;2. 经过mPrivateFactory
来解析xml;3. 经过onCreateView
或者createView
方法来解析xml。除了在AppCompatDelegateImpl
在解析基础控件时使用的是new方式,其他几乎都是反射方式来建立View,因此在布局中尽量的少写View或者尽可能不要书写层次很深的布局。