setContentView调用流程(二) -将布局添加到mContentParent
Android setContentView执行流程(一)-生成DecorView
Android setContentView执行流程(二)-将布局添加到mContentParent
上篇博客我们介绍了setContentView的第一步即生成DecorView以及获取到mContentParent的流程,同时还提到继承自Activity和AppCompatActivity生成DeocrView的流程有点区别,但是将我们自定义的xml布局添加到mContentParent的流程是一样的,因此这个流程我们就以继承自Activity为例进行阐述,另外我们开发中常见的如下问题也跟setContentView流程的源码相关,我们也会通过源码深入讲解
- merge、include、ViewStub是如何工作的,尤其是如果通过id查找布局的根View
- View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true)中inflate方法的参数有什么用呢?
在开始本篇内容学习之前一定要先把上一篇文章看完。
一、 整体时序图
同样先看下setContentView整体的时序图,及到哪些类从整体上了解下。
二、 源码分析
上一篇提到Acitivty的setContentView方法会调用getWindow.setContentView,而getWindow得到的拿到的就是PhoneWindow因此我们就要从PhoneWindow的setContentView源码开始看起,PhoneWindow#setContentView
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 初始化DecorView 并获取到mContentParent
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
。。。
} else {
// 将我们自定义Activity的布局添加到mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
。。。
// setContentView执行完的标识
mContentParentExplicitlySet = true;
}
在上一节我们讲了installDecor相关内容,今天我们要讲的就是第14行mLayoutInflater.inflate(layoutResID, mContentParent)是如何把layoutResID添加到mContentParent的。对于LayoutInflater平时开发中一般有三种方式来获取它
// 第一种
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// 第二种:
LayoutInflater inflater= LayoutInflater.from(context);
// 第三种:
// 在Activity内部调用getLayoutInflater()方法
getLayoutInflater();
这三种方式本质上都是通过第一种方式来获得的,我们就精简下源码,看最终获得的是个什么东西,Context是抽象类,它的实现类是ContextImpl因此我们看下
ContextImpl#getSystemService的源码
@Override
public Object getSystemService(String name) {
。。。
return SystemServiceRegistry.getSystemService(this, name);
}
可以看到调用了SystemServiceRegistry.getSystemService方法,我们来看下它的相关源码
@SystemApi
public final class SystemServiceRegistry {
private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES = new ArrayMap<Class<?>, String>();
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<?>>();
static {
// 。。。省略很多注册服务的代码
// 在静态代码块中注册LAYOUT_INFLATER_SERVICE服务
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
// 返回一个PoneLayoutInflater对象
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
private static <T> void registerService(@NonNull String serviceName,
@NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
// 对LayoutInflater来讲,这里的key:LayoutInflater.class,value:Context.LAYOUT_INFLATER_SERVICE
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
// key:Context.LAYOUT_INFLATER_SERVICE,value:CachedServiceFetcher对象
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
}
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
。。。。
// 调用createService也就是上面第11行实现的createService方法
service = createService(ctx);
。。。
ret = service;
return ret;
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
public static Object getSystemService(ContextImpl ctx, String name) {
。。。
// 通过name获取CachedServiceFetcher对象
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
。。。
// 调用CachedServiceFetcher的getService方法,获取对应的对象
final Object ret = fetcher.getService(ctx);
。。。
return ret;
}
}
来理一下它的设计思路:首先在静态代码块中初始化了Context.LAYOUT_INFLATER_SERVICE服务,在源码中有大量的服务在此进行初始化,初始化的时候会调用registerService方法,传递了三个参数第一个参数Context.LAYOUT_INFLATER_SERVICE它的值为"layout_inflater",第二个参数是LayoutInflater.class,第三个参数是CachedServiceFetcher对象,在第22行它会将key:“layout_inflater”,value:CachedServiceFetcher放入SYSTEM_SERVICE_FETCHERS这个map中,所以在调用SystemServiceRegistry的getSystemService方法时,第49行会拿到我们在静态代码块中初始化的CachedServiceFetcher对象,然后调用它的getService方法,而它的getService在第37行会调用createService方法,这个方法就是我们静态代码块第11行定义的方法,可以看到它会返回一个PhoneLayoutInflater对象,这里需要强调下得到的是PhoneLayoutInflater对象。
回到我们今天的主题:LayoutInflater.from(mContext).inflate(resId, contentParent)是如何将resId添加到contentParent中的呢?
首先来看下LayoutInflater.from的源码LayoutInflater#from
public static LayoutInflater from(@UiContext 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);这个方法得到的LayoutInflater对象,刚才我们提到它其实是一个PhoneLayoutInflater对象。LayoutInflater是一个抽象类,PhoneLayoutInflater继承自LayoutInflater这个抽象类并重写了LayoutInflater中两个参数的onCreateView(String name, AttributeSet attrs)方法,这一点需要注意下后面会用到它,接着刚才的流程from之后会调用LayoutInflater的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) {
。。。
XmlResourceParser parser = res.getLayout(resource);
return inflate(parser, root, attachToRoot);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
View result = root;
。。。
if (TAG_MERGE.equals(name)) {
// merge 标签必须要有根布局,否则会抛如下异常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 获取xml文件中的根View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
。。。
// 解析子布局
rInflateChildren(parser, temp, attrs, true);
。。。
if (root == null || !attachToRoot) {
result = temp;
}
}
。。。
return result;
}
}
第26行调用了createViewFromTag方法,获取到根View,LayoutInflater#createViewFromTag
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
。。。
if (-1 == name.indexOf('.')) { // name=LinearLayout
view = onCreateView(context, parent, name, attrs);
} else {
// 比如name=androidx.constraintlayout.widget.ConstraintLayout
view = createView(context, name, null, attrs);
}
。。。
return view;
}
第7行有个if语句这个语句主要用来判断标签的名字是否有“.”,如果没有的话就调用LayoutInflater#onCreateView如果有"."就调用LayoutInflater#createView,我们来看下它们的源码LayoutInflater#onCreateView,这个方法有多个重载的方法,大家在看源码的时候要注意下
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
@NonNull String name, @Nullable AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(parent, name, attrs);
}
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
这里需要注意了,第9行调用了两个参数的onCreateView方法,刚才我们提到LayoutInflater.from(mContext)得到的是PhoneLayoutInflater并且它重写了onCreateView两个参数的方法,因此第9行调用的是PhoneLayoutInflater里的onCreateView,我们来看下它的源码
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
// prefix是view的前缀android.widget.
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);
}
}
第10行的for循环,这个for循环会遍历sClassPrefixList,sClassPrefixList这个数组里放的是View的前缀,第12行又会调用LayoutInflater#createView
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Context context = (Context) mConstructorArgs[0];
if (context == null) {
context = mContext;
}
return createView(context, name, prefix, attrs);
}
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Class<? extends View> clazz = null;
。。。
// perfix + name是类全称,比如:android.widget.LinearLayout,prefix:android.widget,name:LinearLayout
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
。。。
constructor = clazz.getConstructor(mConstructorSignature);
。。。
final View view = constructor.newInstance(args);
return view;
}
到这里createViewFromTag就执行完了,可以看到第16行通过反射得到了View的Class对象,然后第21行调用newInstance得到了对应的View,比如LinearLayout
再回到LayoutInflater的inflate方法,代码如下,现在第5行的createViewFromTag执行完了,接下来就该填充它的子View了
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
。。。
// 根据XML 解析根View对象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
。。。
// 解析子布局
rInflateChildren(parser, temp, attrs, true);
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
我们来看下是如何解析子布局的LayoutInflater#rInflateChildren
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
。。。
while (((type = parser.next()) != XmlPullParser.END_TAG ||
。。。
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("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 解析内部子View
rInflateChildren(parser, view, attrs, true);
// 将解析之后的View添加到父布局中
viewGroup.addView(view, params);
}
}
。。。
}
第26行,它也是调用createViewFromTag来生成对应的View,并且在第30行会循环调用解析它的子View,并将得到的View添加到parent中,这样最终的View就生成了。总结一下:
(1) 调用LayoutInflater.from(mContext)本质上是通过context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)得到PhoneLayoutInflater对象,它继承自LayoutInflater这个抽象类并重写了LayoutInflater中两个参数的onCreateView(String name, AttributeSet attrs)方法
(2) 调用createViewFromTag最终调用createView通过反射得到根View,比如LinearLayout
(3) 循环调用rInflateChildren最终也是调用createView通过反射得到相应的View,比如LinearLayout,TextView,Button等并将其添加到父View,这样就形成了一个View树。
这里第18~24行可以看到对merge、include进行了特殊处理,在平时的开发中merge、inlcude以及ViewStub对我们布局的优化也非常重要,因此我们就来看下它们的用法
三、merge include ViewStub详解
merge include的讲解根据上述LayoutInflater#rInflateChildren的源码进行分析
merge
merge比较简单它主要用来优化布局,使用它可以减少一层布局嵌套,可以看到第24行抛了一个异常" must be the root element"也就是说merge标签必须是根View,不能作为子View
include
第19行,如果parser.getDepth() == 0说明是根布局,则会抛异常" cannot be the root element"也就是说include不能做为根布局。
include大家应该都使用过,对于它的使用需要注意的是它的id,如果对include设置了id,并且对include布局的根View也设置了id那么它的id是找不到的,比如下的布局嵌套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:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="@+id/include_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/include_layout" />
</LinearLayout>
include_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/include_layout_root_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
可以看到在我们的activity_main中有个include并且设置了id为include_id,在我们的include_layout.xml中根布局的id为include_layout_root_id。假如我用如下代码来查找此id是否能查找到呢?
LinearLayout ll = findViewById(R.id.include_layout_root_id);
TextView tv = ll.findViewById(R.id.tv_title);
tv.setText("change text");
运行后发现报错
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.widget.LinearLayout.findViewById(int)' on a null object reference
也就是说根据include_layout_root_id查找不到对应的View。我们换一种查找方法
LinearLayout ll = findViewById(R.id.include_id);
TextView tv = findViewById(R.id.tv_title);
tv.setText("change text");
上述代码直接查找include_id发现可以正常运行,这是为什么呢?看下上方第22行的pareinclude的源码就清楚了,LayoutInflater#pareinclude
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
final View view = createViewFromTag(parent, childName,context, childAttrs, hasThemeOverride);
// 获取include_id
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
。。。
if (id != View.NO_ID) {
// 如果include的id不是View.NO_ID则将view的id设置为include的id
view.setId(id);
}
}
第6行会获取include的id,然后在第8行有个if判断,如果include的id不为空则对view的id重新赋值为include的id。因此如果include设置了id,则include的那个xml的根布局的id就失效了,也就是说在上面的例子中activity_main中include设置了id,include_layout.xml文件中的include_layout_root_id就失效了,如果去掉activity_main中include的id,则include_layout_root_id可正常使用即
LinearLayout ll = findViewById(R.id.include_layout_root_id);
TextView tv = ll.findViewById(R.id.tv_title);
tv.setText("change text");
这种方式是可以正常运行,另外需要说明的是此动作只针对include_layout中的根View的id,对于其子View则可正常查找,比如
TextView tv = findViewById(R.id.tv_title);
tv.setText("change text");
可以直接查找include_layout布局中的tv_title。
ViewStub
对于merge和include大家比较熟悉,对ViewStub大家可能比较陌生,首先ViewStub是一个View它继承自View,它的作用就是懒加载布局,本质上来说它是一个宽高都为0的一个View,且默认是不可见的,只有在需要的时候,调用setVisibility或者inflate方法才会将目标布局给加载出来,从而达到延迟加载的效果。这样就能优化页面的渲染速度。可能有的同学会有疑问,这种效果通过设置一个View的GONE和VISIBLE不也能实现吗?没错可以实现,但是即使你把View设置为GONE在inflate的时候View仍然会被inflate,也就是说还是会创建对象、实例化等在一定程度上会耗费内存资源。而ViewStub不会,它只有在调用inflate的时候才会被加载进来,那么问题来了,ViewStub是怎么做到的呢?来看下它的源码
public final class ViewStub extends View {
private int mInflatedId;
private int mLayoutResource;
。。。
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.ViewStub, defStyleAttr, defStyleRes);
// 通过自定义属性inflatedId来获取加载的视图跟节点ID,默认返回NO_ID
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 需要加载的视图资源ID
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
setVisibility(GONE);
setWillNotDraw(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 将宽高设置为0
setMeasuredDimension(0, 0);
}
}
可以看到第17行直接调用了setVisibility(GONE)将布局直接隐藏,并且在测量的时候大小也设置为了0,所以它默认是不可见的,那它是怎么做到inflate的时候才把目标布局加载出来的呢?看下它inflate的源码ViewStub#inflate
public View inflate() {
final ViewParent viewParent = getParent();
。。。
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 把ViewStub_layout渲染成view对象
final View view = inflateViewNoAdd(parent);
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
}
}
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
if (mInflater != null) {
factory = mInflater;
} else {
factory = LayoutInflater.from(mContext);
}
// 把ViewStub_layout渲染成view对象
final View view = factory.inflate(mLayoutResource, parent, false);
// 跟include类似,如果mInflatedId不为空则把view的id设置成mInflatedId
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
return view;
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
parent.removeViewInLayout(this);
// 如果viewstub布局设置了参数则使用否则直接将View添加到parent中
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
可以看到在使用inflate的时候第7行会把自定义的属性ViewStub_layout即android:layout中的布局渲染成View,接着就会把此View添加到父View中。
四、LayoutInflater.inflate的参数
在开发中我们经常会写如下代码
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
// 对应的方法的参数如下
inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
这几个参数到底什么意思呢?首先来看几个实例,然后分别分析下,分析完后你就会对这几个参数有深刻理解,这个实例就是我们往布局中LinearLayout中填充一个inflate布局
// 案例一
LinearLayout root = findViewById(R.id.root);
LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
// 案例二
LinearLayout root = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
root.addView(view)
// 案例三
LinearLayout root = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, false);
root.addView(view)
// 案例四
View view = inflater.inflate(R.layout.inflate_layout, null, false);
root.addView(view);
要想充分理解这几个案例还是得从源码的角度来分析LayoutInflater#inflate,下面就结合案例和源码来分析下
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
。。。
XmlResourceParser parser = res.getLayout(resource);
return inflate(parser, root, attachToRoot);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// 实例化根节点的View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 获取根节点的attr
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 设置根节点的params
temp.setLayoutParams(params);
}
}
。。。
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
// 将根节点添加到root上,使用的布局参数是layout中定义的。
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
return result;
}
}
首先案例一
LinearLayout root = findViewById(R.id.root);
LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
root != null, attachToRoot=true,首先第11行会解析我们的xml文件得到对应的View,然后会进入到第15行的分支,获取root的参数,因为attachToRoot=true所以第19行的分支进不去,接着第26行的if语句满足所以将刚解析xml文件得到的View添加到root中,并将其返回,这个没有问题。
案例二
LinearLayout lrootl = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, true);
root.addView(view);
案例二根案例相比,其实就是最后有一个ll.addView(view)的操作,通过案例一我们知道,这三个参数会进入到第26行的if语句将inflate布局添加到root中,如果再次执行root.addView就会报错,因为一个View只能有一个父亲,会报如下错误
The specified child already has a parent. You must call removeView() on the child's parent first.
案例三
LinearLayout root = findViewById(R.id.root);
View view = LayoutInflater.from(context).inflate(R.layout.inflate, root, false);
root.addView(view);
这个可以正常运行,案例三跟案例二相比就是第三个参数案例二为true,案例三为false,为什么为false就可以了呢?同样看源码第26行的if语句不满足,而第32行的if语句满足因此直接将创建好的View直接返回,因此执行root.addView(view)不会报错,可正常执行。
案例四
View view = inflater.inflate(R.layout.inflate_layout, null, false);
root.addView(view);
案例四,能显示但是显示界面有异常,就是inflate_layout 根View设置的宽高无效,会以其子View的宽高为准,同样也是看源码来分析,我们看下它走了哪个流程
root==null,attachToRoot=false,满足第33行的if条件将布局直接返回,此时它没有父View所以它自己设置的宽高是无效的,这一点后面我讲UI的刷新机制的时候会详细讲。到这里对inflate的几个参数大家应该有所了解了吧。
五、总结
到这里关于setContentView的执行流程以及与其相关的知识点就介绍完了,整体的流程不算复杂,我们在阅读源码的时候切记不要每一行代码都搞明白,只了解主流程即可,否则代码会看的头晕目眩,容易放弃,另外关于对源码的学习一定要自己去阅读源码,做笔记这样才能了解更加透彻且复习也快。
如果大家有疑问或者发现错误,欢迎在下方留言,我会在第一时间回答!!
如果觉得文章对你有帮助就帮忙点个赞吧。
原文地址:https://blog.csdn.net/dmk877/article/details/143654161
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!