流程图
LayoutInflater用来把一个xml文件实例化成对应的View对象。
我们从Activity的onCreate方法开始:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//注释1处
setContentView(R.layout.activity_layout_inflate)
}
AppCompatActivity的setContentView方法
@Override
public void setContentView(@LayoutRes int layoutResID) {
//注释1处,调用代理对象的setContentView方法
getDelegate().setContentView(layoutResID);
}
注释1处,调用AppCompatDelegateImpl的setContentView方法。
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//注释1处
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
注释1处,进入正题,LayoutInflater的from方法内部就是使用context.getSystemService来获取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实例以后,就可以调用它的inflate方法来加载布局文件了。
这是Activity的布局文件activity_layout_inflate.xml
。
接下来看看LayoutInflater的inflate方法究竟是怎么把布局文件转换成view对象的。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//调用3个参数的重载函数
return inflate(resource, root, root != null);
}
/**
* 根据指定的xml布局文件中生成一个view层级。有错误抛出InflateException。
* @param resource xml布局文件ID
* @param root 可选的view,如果attachToRoot是true,使用root作为生成的view层级的父布局;
* 如果attachToRoot是false,为返回的view层级的根view提供一系列的LayoutParams值。
* @param attachToRoot 用来标志生成的view层级是否应该被添加到root中去。如果是false,
* root只是用来为返回的view层级的根view创建正确的LayoutParams。
* @return 返回生成的view层级的根view。如果root不为null并且attachToRoot 是true,就返回root;
* 否则返回view层级的根view。
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot){
final Resources res = getContext().getResources();
//...
//注释1处,根据资源id生成xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
//注释2处
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
注释1处,调用Resources的getLayout方法
@NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
这个方法返回了一个XmlResourceParser对象,我们可以通过这个对象读取布局文件中的XML数据。
注释2处,调用LayoutInflater的inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot)方法开始读取解析数据。
public View inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//...
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 查找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//...
//注释0处,布局文件中第一个标签名字
final String name = parser.getName();
//处理merge标签,标签的情况暂时不去看
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 {
//注释1处,temp是xml文件中的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//注释2处
//创建与root匹配的布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//注释3处
//将布局参数设置给temp
temp.setLayoutParams(params);
}
}
//注释4处,填充temp下面的所有子view
rInflateChildren(parser, temp, attrs, true);
// 注释5处,如果条件满足,应该把temp添加到root中去。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 注释6处,判断是返回root还是返回result。
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//...
return result;
}
}
注释0处,布局文件中第一个标签名字,在这个例子中就是布局中的RelativeLayout
。
final String name = parser.getName();
在注释1处,调用createViewFromTag方法获取xml文件中的根view。
private View createViewFromTag(View parent, String name, Context context,
AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
/**
* 从一个标签名和提供的属性集合创建一个view。
*
* 这个方法的可见性是default,所以BridgeInflater可以覆盖这个方法。
*
* @param parent 父view,用来生成layout params
* @param name XML标签名,用来定义一个view。
* @param context 用来生成view的上下文。
* @param attrs 布局文件中view的属性集
* @param ignoreThemeAttr 用来标志要生成的view是否忽略 {@code android:theme}中定义的属性。
*/
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// 如果允许并指定了一个主题属性集,则使用。
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;
if (mFactory2 != null) {//注释1处
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {//注释2处
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
//注释3处
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {//注释4处
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//注释5处
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
//返回创建的View
return view;
} catch (InflateException e) {
throw e;
}
}
注释1处,调用了mFactory2
的onCreateView
方法。并且发现mFactory2
是一个AppCompatDelegateImpl对象。mFactory2
是何时赋值的我们暂且不管。
AppCompatDelegateImpl的onCreateView方法。
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return this.createView(parent, name, context, attrs);
}
AppCompatDelegateImpl的createView方法。
public View createView(View parent, String name, Context context, AttributeSet attrs) {
//内部调用了AppCompatViewInflater的createView方法
return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}
AppCompatViewInflater的createView方法,我们从中可以看到一些端倪。
final View createView(View parent, final String name, Context context, AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
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:
//默认是返回null
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
//我们在布局中有时候会设置`android:onClick="click"` 这样一个属性,难道是在这里处理的?
checkOnClickListener(view, attrs);
}
return view;
}
最后发现返回的view为null。
我们回到createViewFromTag
方法的注释2处、注释3处、返回的都是null。
注释5处,调用LayoutInflater的onCreateView方法。
protected View onCreateView(View parent, String name, AttributeSet attrs) throws ClassNotFoundException {
return onCreateView(name, attrs);
}
注意,这里调用的是PhoneLayoutInflater
类里面的onCreateView(String name, AttributeSet attrs)
方法。
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
//注释1处
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
sClassPrefixList对象
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
注释1处,调用LayoutInflater
的createView(String name, String prefix, AttributeSet attrs)
方法。传入的name是RelativeLayout
,prefix是android.widget
。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//根据要创建的View的名称获取构造函数
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) {
//如果构造函数为null,就通过类加载器加载类对象并获取构造函数,然后将构造函数缓存在sConstructorMap中
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 {
// 通过debug,发现mFilter为null
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 = 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) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//创建View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
//返回View
return view;
} catch (Exception e) {
//抛出各种异常
}
}
方法内部逻辑就是根据控件名称和前缀prefix加载对应的类对象,然后通过反射创建View对象并返回。到这里我们布局文件中最外层的RelativeLayout
就创建好了。
LayoutInflater的inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot)方法的注释1处
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
在这个例子中,temp就是我们创建好的RelativeLayout对象。
然后我们回到LayoutInflater的inflate(XmlPullParser parser,ViewGroup root, boolean attachToRoot)方法的注释4处,填充temp下面的所有子view。
//注释4处,填充temp下面的所有子view
rInflateChildren(parser, temp, attrs, true);
LayoutInflater的rInflateChildren方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
LayoutInflater的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)) {
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 {
//注释1处
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//注释2处递归创建子View。
rInflateChildren(parser, view, attrs, true);
//注释3处,添加创建的View
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
//结束填充
parent.onFinishInflate();
}
}
注释1处:创建View。
注释2处:递归创建创建子View。
注释3处:添加创建的View。
创建完所有的View以后,我们回到LayoutInflater的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
方法注释2处,创建temp的布局参数。
ViewGroup.LayoutParams params = null;
if (root != null) {
//注释2处
//创建与root匹配的布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//注释3处
//将布局参数设置给temp
temp.setLayoutParams(params);
}
}
在这个例子中,root就是 android:id/content
,是一个FrameLayout。
FrameLayout的generateLayoutParams方法。
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new FrameLayout.LayoutParams(getContext(), attrs);
}
FrameLayout.LayoutParams,是ViewGroup.MarginLayoutParams的子类。
public static class LayoutParams extends MarginLayoutParams {
//...
}
注意:这里想说一点,我们平时用的LinearLayout.LayoutParams
、RelativeLayout.LayoutParams
、FrameLayout.LayoutParams
都是ViewGroup.MarginLayoutParams
的子类。
创建完所有的View以后,我们回到LayoutInflater的inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
方法注释5处,如果条件满足,应该把temp添加到root中去。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
到这里,LayoutInflater的整个inflate流程结束。
参考链接
- Android LayoutInflater原理分析,带你一步步深入了解View(一)