1. setContentView
初步分析之继承自Activity
我们创建的MainActivity
继承自Activity
,在代码中使用setContentView(R.layout.activity_main)
,查看他在Activity
中的源码如下:
public void setContentView(@LayoutRes int layoutResID) {
//这里的getWindow方法获取到一个PhoneWindow实例,然后再分析PhoneWindow中的setContentView方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里的getWindow
方法,获取到的其实是一个PhoneWindow
的实例,通过Activity.java
中的attach()
方法中创建的mWindow
对象,代码如下:
mWindow = new PhoneWindow(this, window, activityConfigCallback);
然后我们再从PhoneWindow.java
中分析setContentView
方法,而setContentView
方法主要有两个目的(作用)
:
创建DecorView
/FrameLayout,然后创建mContentParent
/ViewGroup(Root对象
)installDecor()
->generateDecor()->new DecorView()->mDecor->generateLayout(mDecor)->mContentParent
解析我们的布局文件,利用反射创建View对象
mLayoutInflater.inflate(layoutResID, mContentParent);
- 创建
rootView
- ->创建布局的
rootView
(比如一个xml
布局文件是以ConstrantLayout
作为根布局)
crateViewFromTag()
->onCreateView()/createView()
->PhoneLayoutInflater.onCreateView()
->PhoneLayoutInflater.createView()
->LayoutInflater.createView()
->通过反射创建View并返回
clazz = Class.forName(解析到的控件全路径,classLoader);
然后通过clazz获取到构造器,最后根据构造器创建出view对象
- ->创建布局的
- 创建
子View
rInflateChildren
(parser, temp, attrs, true);
->rInflate
(parser, parent, parent.getContext(), attrs, finishInflate);迭代遍历while循环
->createViewFromTag
(parent, name, context, attrs);
后面的流程同上面创建rootView流程中接createViewFromTag
后面的流程
面试题:
LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot)中参数的作用是什么?
resource
子控件的布局文件root
父容器attachToRoot
逻辑判断,是否可以将子控件resource添加到父容器root中去
root
非空,并且attachToRoot
为true
,则可以将第一个参数中的布局文件resource
添加到父容器root
中去root
为空,或者attachToRoot
为false
,则可以将第一个参数中的布局文件resource
创建的View
返回
流程图如下:
哪些地方可以创建PhoneWindow?
- Activity
- Dialog
- PopupWindow
- Toast
如何布局?使用 mLayoutInflater.inflate(layoutResID, mContentParent);
使用XMLResourceParser
解析,传入的参数mContentParent
就是root
对象,然后调用rInflate
方法或者是调用rInflateChildren
方法,正常情况下是调用rInflateChildren
方法,但是rInflateChildren
方法又会去调用rInflate
方法,最终返回一个View
2. setContentView
初步分析之继承自AppCompatActivity(继承自FragmentActivity)
我们创建的MainActivity
继承自AppCompatActivity
,在代码中使用setContentView(R.layout.activity_main)
,查看他在AppCompatActivity.java
中的源码如下:
public void setContentView(@LayoutRes int layoutResID) {
//delegate是代理的意思,为了实现版本兼容
//这里的getDelegate返回的是一个AppCompatDelegateImpl类对象,然后在AppCompatDelegateImpl.java类中分析setContentView的方法
getDelegate().setContentView(layoutResID);
}
这里的getDelegate()
方法返回的是AppCompatDelegateImpl
对象的实例,然后在AppCompatDelegateImpl.java
类中分析setContentView
的代码,源码如下:
public void setContentView(int resId) {
//通过这个方法创建mSubDecor/ViewGroup;创建DecorView,创建mContentParent;
//迁移旧的布局文件中的控件到新的布局文件中,替换content为新的布局文件中的ID
ensureSubDecor();
//找到新的布局文件中的控件ID
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
首先调用ensureSubDecor()
方法创建mSubDecor
对象实例(他是一个ViewGroup
对象),代码如下:
mSubDecor = createSubDecor();
然后在createSubDecor()
方法中,会调用如下两行代码:
ensureWindow();
mWindow.getDecorView();
第二行代码中就会执行创建DecorView
和mContentParent
对象的操作;
然后创建subDecor
局部变量,并把他作为这个方法的返回值返回给mSubDecor
对象;
subDecor
引入的布局问文件是R.layout.abc_screen_simple
,
为了版本兼容,将screen_simple.xml
中content
下面的控件迁移过来,同时移除screen_simple.xml
中content
下面的控件;
然后将sceen_simple.xml
文件中id
值为content
置为空,同时将abc_screen_simple.xml
文件中的id
更改为content
(这个content
原本是sceen_simple.xml
文件中的id
);
达到将旧版本中的布局控件迁移至新版布局文件和content
id兼容的目的;
然后将subDeocr
赋值给PhoneWindow
,代码如下:
mWindow.setContentView(subDecor);
然后就会执行PhoneWindow.java
中的setContentView
方法,同Activity
中逻辑类似,这里就会执行创建decorView
,接着会返回mContentParent
对象
流程图如下: