LayoutInflater背后隐藏的一些东西
本来是要继上一篇来写CoordinatorLayout
源码分析的,由于在项目中遇到LayoutInflater
一些问题,所以今天把LayoutInflater
一些相关问题给分析下,还是和往常一样,先来demo事例:
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout layout = findViewById(R.id.main);
View inflate = getLayoutInflater().inflate(R.layout.inflate1, null);
ViewGroup.LayoutParams lp = inflate.getLayoutParams();
Log.d(TAG, "lp is null:" + (lp == null));
layout.addView(inflate);
Log.d(TAG, "add view========");
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) inflate.getLayoutParams();
Log.d(TAG, "layoutParams_width:" + layoutParams.width);
Log.d(TAG, "layoutParams_height:" + layoutParams.height);
}
}
在addView
之前获取下inflate
的LayoutParams
是否为空,并且在addView
之后又获取下LayoutParams的宽高
activity_main.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0000"
android:orientation="vertical"
tools:context=".MainActivity" />
demo很简单,通过获取到LayoutInflater
将inflate1布局中的view添加进来,下面看看inflate1布局长啥样:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#cccccc"
android:gravity="center"
android:text="我是inflate进来的">
</TextView>
啥也没有,只是一个文本。为了演示事例,这里设置了背景颜色,以及layout_width
和layout_height
都是wrap_parent
,心想肯定是个宽高匹配textview
,然而我们看下效果,以及运行出来的日志:
从日志上看,效果图和后面获取到的
layoutParams_width
和layoutParams_height
是对应上的,宽是match_parent
,高是wrap_content
。我们再看一种情况:image.png
没想到在addView的时候,直接抛了异常:
image.png
相信这个异常大家比较熟悉吧,意思是child已经有parent了,此时又去添加到别的viewgroup身上了。说明在
inflate
过程中已经被添加过了,那咱们去掉addView过程看下结果:image.png
此处我把外面的
LinearLayout
的id打印出来了,并且把inflate之后的结果inflate的id打印出来了:image.png
image.png
从日志上看,inflate返回的结果是传进去的layout,并且子view的宽高都是wrap_content。那此时如果改下inflate1.xml文件呢:
image.png
结果如图所示:
image.png
说明这个时候inflate1.xml中的
layoutparams
起作用了。所以说第二个参数中的root参数不为空,说明此时生成的layoutparams
以传进去的layout文件为主,并且inflate方法返回就是当前root。
还有种情况调用带第三个参数的方法,最后一个参数是一个布尔值,我们直接传false:
image.png
此时效果图和日志如下:
image.png
image.png
说明此时在inflate的时候没有操作过addView方法,而且此时返回的结果也不是刚才的root了。那么下面带着这些效果图和日志咋们一起分析下源码是怎么回事:
源码分析
-
activity.getLayoutInflater()
image.png
此处调用了window中的getLayoutInflater方法,window中该方法是抽象的,说明需要到相应的子类中看下该方法,大家都知道activity中实例化window是一个phoneWindow对象,因此看下phoneWindow
对象的getLayoutInflater
方法:
image.png
直接返回的成员变量mLayoutInflater
,该变量在构造器的时候初始化的:
image.png
这个代码很熟悉吧,其实最终都是通过LayoutInflater.from(context)
来获取LayoutInflater
的,只不过在activity中方便我们直接用罢了:
image.png
这里最终通过context.getSystemService获取LayoutInflater
的,其实跟我们获取各种服务一样的获取,如果大家看过android源码设计模式这本书,相信看工厂模式的时候,有讲过android中获取各种服务的由来。在ActivityThread
中有涉及到context的由来,其实它是ContextImpl
类来实例化的。那咱们直接看ContextImpl
的getSystemService
方法:
image.png
所以最终锁定在了SystemServiceRegistry
类中获取到各种服务的:
image.png
最终我们定位到SYSTEM_SERVICE_FETCHERS
变量,其实它就是个hashMap
,用来存储android中要用到的各种服务,它是在什么时候初始化这些服务的呢:
image.png
看到了没,它是在SystemServiceRegistry
静态代码块中获取各种服务的,那咱们直接看Context.LAYOUT_INFLATER_SERVICE
是什么服务:
image.png
看到了没,真面目终于出来了,获取的是一个PhoneLayoutInflater
对象,它是LayoutInflater
的子类。
我们再来屡屡Activity.getLayoutInflater
由来:
Activity.getLayoutInflater
调用window.getLayoutInflater
,而Activity
的window
对象是phoneWindow
对象,在phoneWindow
对象中的getLayoutInflater
方法,返回的是mLayoutInflater
变量,mLayoutInflater
变量是在phoneWindow
构造器中通过LayoutInflater.from(context)
方法获取的,在该方法中通过context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
获取的,因此LayoutInflater
的获取其实跟android其他的服务也是一样获取的,而大家知道context的实现类是ContextImpl
,因此是调用了ContextImpl.getSystemService
方法,而在该方法中是通过SystemServiceRegistry.getSystemService
方法获取到的,因此可以知道android中所有的服务都是通过SystemServiceRegistry.getSystemService
获取的,而在SystemServiceRegistry
类中,可以发现所有的服务都是通过静态代码块放到了SYSTEM_SERVICE_FETCHERS中
,在用的时候直接从SYSTEM_SERVICE_FETCHERS
中取,所以最终发现LayoutInflater
其实是PhoneLayoutInflater
对象。
-
LayoutInflater.inflate
上面分析了getLayoutInflater
方法其实获取的是PhoneLayoutInflater
对象,因此咱们看下它的inflate
方法,打开一看原来inflate方法还是在LayoutInflater
中:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
上面可以看到是调用了三个参数的inflate
方法,如果传进来的root
是空的,那个第三个参数就是false
,否则为true
。那咱们看下带三个参数的inflate
方法:
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) + ")");
}
//获取到xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
//调用了另外一个重构的inflate方法
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
首先是获取到XmlResourceParser
解析器,该处解析器是一个XmlPullParser
类型的解析器,从字面意思看xml解析是遵从pull解析的规则,最后还是调用了inflate重构的方法,把刚刚获取的XmlResourceParser
传了重构的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;
//默认返回的是root
View result = root;
try {
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//如果布局的根标签是merge标签,那么通过inflate传过来的root必须不为空,并且attachToRoot=true,否则抛出异常,
//其实想想也是那么回事,咋们的merge标签的布局是要被添加到外层的viewgroup上的
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类型的标签,直接通过该方法创建view
final View temp = createViewFromTag(root, name, inflaterContext, attires
ViewGroup.LayoutParams params = null;
if (root != null) {
//如果root不为空,那么子view的layoutParames是通过root.generateLayoutParams获取到的
params = root.generateLayoutParams(attires
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
//看到了没,如果传过来的root不为空,并且attachToRoot=true,直接将创建的view添加到root上,并且params是通过root.generateLayoutParams获取到的
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果root为空或者attachToRoot=false,那么此时返回的result直接是获取到的xml中的根布局
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw i.e
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw i.e
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
通过注释可以看到,默认inflate
方法返回的view是root
。首先判断根标签是不是merge
标签,大家都知道merge标签其实是减少布局层次的一个标签,如果外面的布局和merge标签要用的布局是同一个布局的话,那么此时里面可以用merge标签来代替,所以如果根布局是merge
标签,如果root
为空或者attachToRoot=false
,直接抛了异常,大家可以写个merge
的布局试试。接着调用了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循环不断地取里面的标签,如果没有了下一个标签或者到了END_DOCUMENT才停止
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, attires
} else if (TAG_INCLUDE.equals(name)) {//include标签
//如果include标签的层次为0,直接抛出异常,意思是include标签在顶层
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
//解析include标签
parseInclude(parser, context, parent, attires
} else if (TAG_MERGE.equals(name)) {//如果里面的还是meger标签直接抛出异常,因为merge标签必须在xml的根标签上
throw new InflateException("<merge /> must be the root element");
} else {
//这里才是根据标签来创建不同的view,很关键
final View view = createViewFromTag(parent, name, context, attires
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attires
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
咋们直接看如果第二层的标签是include
,如果include标签是在顶层,那么直接抛出异常,下面看下parseInclude
方法:
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
final boolean hasThemeOverride = themeResId != 0;
if (hasThemeOverride) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
// If the layout is pointing to a theme attribute, we have to
// massage the value to get a resource identifier out of it.
//获取layout属性的xml的id
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {//如果layout属性不是resource类型的
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
}
// Attempt to resolve the "?attr/name" string to an attribute
// within the default (e.g. application) package.
//为了解决字符串类型的layout属性
layout = context.getResources().getIdentifier(
value.substring(1), "attr", context.getPackageName());
}
//如果上面的layout的xml的id还是为0,直接抛异常
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
throw new InflateException("You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
} else {
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(childParser.getPositionDescription() +
": No start tag found!");
}
final String childName = childParser.getName();
//merge标签还是继续跟上面merge标签一样
if (TAG_MERGE.equals(childName)) {
//递归调用
rInflate(childParser, parent, context, childAttrs, false);
} else {
//看到了没,所有的解析跟view相关的标签都走createViewFromTag方法
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
//获取include标签的id和visibility属性
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
ViewGroup.LayoutParams params = null;
try {
//获取当前include的layoutParams
params = group.generateLayoutParams(attires
} catch (RuntimeException e) {
// Ignore, just fail over to child attire
}
//如果当前的include的layoutParams为空,才会用自己的layoutParams
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// 继续递归调用
rInflateChildren(childParser, view, childAttrs, true);
//如果include标签有id,那么layout中的xml的根布局id以这个为主了
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
//最后将include中的view添加到传过来的parent里面,可以看出来include标签是直接添加到当前的parent
group.addView(view);
}
} finally {
childParser.close();
}
}
} else {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
LayoutInflater.consumeChildElements(parser);
}
在parseInclude
方法中, 获取到layout标签的xml的资源文件id,如果id=0,则获取layout属性的string类型的值,如果string类型不为空,则获取字符串类型的layout属性值。如果获取到的layout属性是某个xml文件的id,然后和上面inflate过程一样,判断标签是merge标签还是普通的view标签,如果是merge标签,继续递归调用rInflate方法,如果是view标签,则调用createViewFromTag方法,如果include标签上有id和visible属性,那么include的layout中的xml文件的id就是include的id,visible属性也是如此,如果include标签的layoutparams属性不为空,那么layout中的xml文件的layoutparams就以include的layoutparams为主,否则以自己的layoutparams为主。所以这就是为什么Include标签的layoutparams会覆盖layout的layoutparams。然后继续去rInflateChildren
里面的子view,形成一个递归。最后将include里面的子view添加到当前root身上。
说完了上面的parseInclude,下面来说说createViewFromTag
过程,这个过程是咱们平常写自定义view和android自带的view,比如TextView、Button等控件的生成,咱们回到rInflate
方法的调用createViewFromTag
处:
上面可以看到
createViewFromTag
完了之后,进行了rInflateChildren
操作,最后将获取到的view添加到parent身上。并且此时的params是parent身上的LayoutParams。已经好几次看到了createViewFromTag
影子和rInflateChildren
的影子,后面再分析这两个方法,说完了rInflate
方法,我们再回到inflate
方法,还记得刚才在inflate
方法中,我们一直在顺着标签为merge的标签不,下面再看看,非merge标签的部分,也就是我们经常写各种view的部分:
//首先还是和rInflate中一样,如果遇到view的标签,去创建view
final View temp = createViewFromTag(root, name, inflaterContext, attires
ViewGroup.LayoutParams params = null;
//如果传进来的root不为空,并且attachToRoot=false,才会给我们的xml布局设置LayoutParams,所以这就是为什么在root为空的情况下,获取到的LayoutParams是空的
if (root != null) {
params = root.generateLayoutParams(attires
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
//还是和rInflate过程一样,createViewFromTag完之后继续rInflateChildren
rInflateChildren(parser, temp, attrs, true);
//这里能解释为什么在root不为空,直接能看到xml布局在根布局上显示
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果root为空或者attachToRoot为false,那么result返回的是xml布局中的根布局,否则为root
if (root == null || !attachToRoot) {
result = temp;
}
这里只贴上了inflate中标签为view的部分,上面已经分析了inflate方法的merge标签部分,可以看到首先也是调用了createViewFromTag
方法,紧接着通过传过来的root
是否为空以及attachToRoot
是否为false的情况下来确定要不要给xml中的布局设置LayoutParams,如果root不为空,并且attachToRoot=false
情况下,才会给xml布局设置LayoutParams,如果root不为空,attachToRoot=true
,是直接将xml布局直接添加到root上,并且LayoutParams也不为空,此时两种情况的LayoutParams都是通过ViewGroup的generateLayoutParams
获取到的,关于generateLayoutParams
其实就是获取当前要生成的view的LayoutParams
。看此处也是调用了rInflateChildren
方法进行字view的递归调用。最后我们通过root=null或者attachToRoot=false来确定inflate返回xml中的根布局view。
我们再来总结下此处的逻辑:如果root不为空,(attachToRoot=true,此时是直接将inflate出来的view添加到root上,并且此时inflate出来view的LayoutParams是以自己在xml布局文件中设置的保持一致,如果attachToRoot=false,此时就不会将inflate出来的view添加到root上,并且此时返回的结果是inflate出来的view。)如果root为空,(不管attachToRoot是为true还是false,inflate出来的结果就不会被添加到任何view上,并且此时返回的结果就是inflate出来的view),为了方便记住,写了个表格如下:
root!=null | root=null | |
---|---|---|
attachToRoot=true | addView、LayoutParams、返回root | 返回xml中根布局 |
attachToRoot=false | LayoutParams、返回xml中的根布局 | 返回xml中根布局 |
其实到现在我们可以分析开篇那几个例子的现象:
LinearLayout layout = findViewById(R.id.main);
View inflate = getLayoutInflater().inflate(R.layout.inflate1, null);
ViewGroup.LayoutParams lp = inflate.getLayoutParams();
Log.d(TAG, "lp is null:" + (lp == null));
layout.addView(inflate);
Log.d(TAG, "add view========");
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) inflate.getLayoutParams();
Log.d(TAG, "layoutParams_width:" + layoutParams.width);
Log.d(TAG, "layoutParams_height:" + layoutParams.height);
main是一个LinearLayout,inflate1布局就一个textview,textview的宽高都是wrap_content,在日志上显示addView之前lp=null,最后得到的inflate里面的textview宽是match_parent、高是wrap_content。在这个例子中,root=null,attachToRoot没传,其实在源码中默认调用两个参数的inflate方法中,如果root为空,attachToRoot=false,root不为空,attachToRoot=true。
也就是在上面事例中inflate出来的结果直接返回了,没有设置LayoutParams和addView。所以印证了在addView之前获取不到LayoutParams,而在addView之后又能获取,咋们直接看ViewGroup的addView方法:
image.png
可以看出来如果在addView时获取不到child的LayoutParams,通过
generateDefaultLayoutParams
方法设置:image.png
在viewgroup中默认是wrap_content,而在上面例子中是LinearLayout,所以看下LinearLayout重写该方法没:
image.png
果真如此,在LinearLayout中默认的LayoutParams宽是match_parent,高是wrap_content。因此验证了上面的例子。如果你把外层的LinearLayout换成FrameLayout肯定是个全屏的TextView,大家可以自己去验证下。
在第二个例子中,在inflate方法传入了layout参数,因此直接可以得出结论,被addView过了,设置了LayoutParams,并且返回的结果就是当前的root,也就是我们传过去的layout参数,所以在后面再addView的时候直接抛了该异常:
image.png
并且从运行出来的结果看宽高都是wrap_content,因为此时LayoutParams是来自xml布局的。
分析完了事例后,我们接着看在inflate方法中有个比较重要的方法一直没有说,rInflateChildren
、createViewFromTag
:
每次createViewFromTag
方法调用了,紧接着就是调用rInflateChildren
方法:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attire
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
该方法只是简单地调用了下rInflate
,不断地找里面的下一个子view标签。
createViewFromTag
该方法就是我们在xml布局中写的view标签来创建view的方法:
View createViewFromTag(View parent, String name, Context context, AttributeSet attire
boolean ignoreThemeAttr) {
//如果标签是view的,直接通过class属性来获取name
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
View view;
//默认都是空
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attires
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attires
} else {
view = null;
}
//默认mPrivateFactory是空的
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attires
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//该处是创建系统的view
view = onCreateView(parent, name, attires
} else {
//创建自定义的view
view = createView(name, null, attires
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw i.e
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw i.e
}
}
首先如果是view标签,那么获取view的class属性来获取name,如果name中有点的话,说明该view是自定义的view,如果没有点,说明是系统自带的view,比如TextView、Button等,首先看下创建系统的view:
看到了没此处传了个android.view的前缀,如果是自定义的view就不会有前缀:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//看有没有缓存的构造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
//验证如果不是view类型的,直接将进行清除缓存操作
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// 如果有前缀加上前缀操作
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//将class类字节码放到mFilter中,方便下次有缓存的时候用
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//这里就是为什么在自定义view中,如果是通过xml生成view要重写两个参数的原因
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//放到缓存map中
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// 首次该map是空的
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// 直接加载class字节码文件
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) {
//viewstub类型的直接调用setLayoutInflater方法
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
attrs.getPositionDescription() + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
该方法看似很长,实际后面的代码都是多余抛的异常,首先是获取到map中存储的构造器,如果有,再去验证是不是继承自view类型的,如果是就不进行移除操作,然后通过反射调用了两个参数的构造器,这就是为什么我们在xml中用的view,需要重写两个参数的构造器。
陆陆续续,终于把inflate整个过程屡了一遍,其实在activity的setContentView方法中最终也是调用了LayoutInflater中的inflate方法,大家有兴趣也可以去看看这个流程。
思考
在RecyclerView中重写adapter中,item的布局明明是match_parent,可最终显示是wrap_content,解决办法是动态设置holder.itemView.setLayoutParams,还可以设置LayoutManager
总结
所有的获取LayoutInflater都是最终通过context.getSystemService获取到的,该服务是
PhoneLayoutInflater
inflate操作逻辑:如果root不为空,(attachToRoot=true,此时是直接将inflate出来的view添加到root上,并且此时inflate出来view的LayoutParams是以自己在xml布局文件中设置的保持一致,如果attachToRoot=false,此时就不会将inflate出来的view添加到root上,并且此时返回的结果是inflate出来的view。)如果root为空,(不管attachToRoot是为true还是false,inflate出来的结果就不会被添加到任何view上,并且此时返回的结果就是inflate出来的view)
addView操作的时候,如果没有设置LayoutParams,默认会用ViewGroup的generateDefaultLayoutParams
生成的,每个ViewGroup是不一样的,大家可以看看LinearLayout和FrameLayout重写的该方法
通过在xml中生成的view,需要重写两个参数的构造方法