注:-->及缩进表示方法调用层级关系
一、setContentView调用流程
ActivityThread.performLaunchActivity
-->activity.attach
--> new PhoneWindow()
-->mInstrumentation.callActivityOnCreate//内部调用onCreate方法
MainActivity.onCreate()
-->setContentView
二、setContentView源码分析
1. 继承 Activity 的流程
setContentView
-->getWindow().setContentView(layoutResID);//getWindow()得到的是new PhoneWindow()
PhoneWindow.setContentView --- 主要目的 创建 DecorView 拿到 Content
--> installDecor(); // 创建 DecorView 拿到 mContentParent
--> mDecor = generateDecor(-1);
--> new DecorView()
--> mContentParent = generateLayout(mDecor);
--> R.layout.screen_simple --> @android:id/content --> mContentParent
// R.layout.screen_simple --》 添加到 DecorView(FrameLayout)
--> mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
--> ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
--> mLayoutInflater.inflate(layoutResID, mContentParent); // R.layout.activity_main 渲染到 mContentParent
2. 继承 AppCompatActivity 的流程
setContentView
-->getDelegate().setContentView(view);//getDelegate()得到的是new AppCompatDelegate()
AppCompatDelegate.setContentView
--> ensureSubDecor();
--> mSubDecor = createSubDecor();
--> ensureWindow(); // 从Activity 那PhoneWindow
--> mWindow.getDecorView();
--> installDecor(); // mContentParent
-->... ...//此处与继承Activity的流程一致,省略
// R.layout.screen_simple 里面的 content ---》 把 content 的 View 复制到R.id.action_bar_activity_content
--> final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
--> windowContentView.setId(View.NO_ID); // 将原始的 content id 置为 NO_ID
--> contentView.setId(android.R.id.content); // subDecerView R.id.action_bar_activity_content --> 置为 content
--> mWindow.setContentView(subDecor); //
--> ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
// R.layout.activity_main View 创建
--> LayoutInflater.from(mContext).inflate(resId, contentParent);
// 通过反射创建View --- 布局的rootView
--> final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// 是否是sdk
-->if (-1 == name.indexOf('.')) { // LinearLayout
view = onCreateView(context, parent, name, attrs);
--> PhoneLayoutInflater.onCreateView(name, attrs);
--> View view = createView(name, prefix, attrs);
} else { // name = androidx.constraintlayout.widget.ConstraintLayout
view = createView(context, name, null, attrs);
// 通过反射创建 View 对象
--> clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
--> constructor = clazz.getConstructor(mConstructorSignature);
--> final View view = constructor.newInstance(args);
}
--> rInflateChildren(parser, temp, attrs, true); // 创建子View
--> rInflate
--> View view = createViewFromTag(parent, name, context, attrs);
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app.",
"android.view."
};
三、相关面试题
1. 为什么requestWindowFeature()要在setContentView()之前调用?
requestWindowFeature 实际调用的是 PhoneWindow.requestFeature,在这个方法里面会判断如果变量 mContentParentExplicitlySet 为true则报错,而这个变量会在 `PhoneWindow.setContentView` 调用的时候设置为true。
2. 为什么这么设计呢?
DecorView的xml布局是通过设置的窗口特征进行选择的。
3. 为什么 requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
需要用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE);,因为继承的是AppCompatActivity,这个类里面会覆盖设置。
4. LayoutInflater几个参数的作用?
/** * Inflate a new view hierarchy from the specified xml resource. Throws * {@link InflateException} if there is an error. * * @param resource ID for an XML layout resource to load (e.g., * <code>R.layout.main_page</code>) * @param root Optional view to be the parent of the generated hierarchy (if * <em>attachToRoot</em> is true), or else simply an object that * provides a set of LayoutParams values for root of the returned * hierarchy (if <em>attachToRoot</em> is false.) * @param attachToRoot Whether the inflated hierarchy should be attached to * the root parameter? If false, root is only used to create the * correct subclass of LayoutParams for the root view in the XML. * @return The root View of the inflated hierarchy. If root was supplied and * attachToRoot is true, this is root; otherwise it is the root of * the inflated XML file. */ LayoutInflater inflater = LayoutInflater.from(this); // 方式一:布局添加成功,里面执行了 ll.addView(view) View view = inflater.inflate(R.layout.inflate_layout, ll, true);
// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true);
ll.addView(view);
// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);
// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// View 没有父容器,尺寸大小参数的设置无效,此处涉及到View的绘制流程
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是处于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);
5. 描述下include标签的特点
1) 不能作为根元素,需要放在 ViewGroup中
2) findViewById查找不到目标控件,这个问题出现的前提是在使用include时设置了id,而在findViewById时却用了被include进来的布局的根元素id。
1. 为什么会报空指针呢?
如果使用include标签时设置了id,这个id就会覆盖 layout根view中设置的id,从而找不到这个id
代码:LayoutInflate.parseInclude
-->final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
-->if (id != View.NO_ID) {
view.setId(id);
}
6. 描述下merge标签的特点
1) merge标签必须使用在根布局
2) 因为merge标签并不是View,所以在通过LayoutInflate.inflate()方法渲染的时候,第二个参数必须指定一个父容器,
且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点.
3) 由于merge不是View所以对merge标签设置的所有属性都是无效的.
7. 描述下ViewStub标签的特点
ViewStub:就是一个宽高都为0的一个View,它默认是不可见的
1) 类似include,但是一个不可见的View类,用于在运行时按需懒加载资源,只有在代码中调用了viewStub.inflate()
或者viewStub.setVisible(View.visible)方法时才内容才变得可见。
2) 这里需要注意的一点是,当ViewStub被inflate到parent时,ViewStub就被remove掉了,即当前view hierarchy中不再存在ViewStub,
而是使用对应的layout视图代替。附ViewStub.java源码说明
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
// 获取 InflatedId
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 设置不显示
setVisibility(GONE);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 设置宽高为0
setMeasuredDimension(0, 0);
}
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 设置ID
final View view = inflateViewNoAdd(parent);
// 替换自己和View
replaceSelfWithView(view, parent);
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
mInflateListener.onInflate(this, view);
}
return view;
} else {
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
private View inflateViewNoAdd(ViewGroup parent) {
if (mInflatedId != NO_ID) {
view.setId(mInflatedId);
}
}
private void replaceSelfWithView(View view, ViewGroup parent) {
final int index = parent.indexOfChild(this);
// 移除自己
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 添加View
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}