在Activity中要显示界面,相信大家都知道怎么做了。在Activity中的生命周期onCreate方法中调用setContentView(int layoutResID)即可,其中layoutResID就是当前要显示的布局资源id。今天就来分析下为什么调用该方法就会显示出对应的布局呢。那么下面我们就开始发车了。。。
源码分析
在Activity中会调用setContentView(int layoutResID),那么进入该方法。
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
//这里调用了一个getWindow()方法,返回的是一个window对象,然后再调用该
window对象的setContentView(layoutResID)方法
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里调用了getWindow()方法的setContentView(layoutResID),那么就来看看getWindow()返回的是个什么对象。
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
这里看注释是返回一个window给当前activity
public Window getWindow() {
return mWindow;
}
这里返回的是一个成员变量mWindow,它的返回值类型是Window ,进入Window 类接着看。
/**
* The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
//这里可以看出Window是个抽象类,并且它的唯一子类是PhoneWindow
public abstract class Window
那么接着看看它的唯一子类PhoneWindow中的setContentView(layoutResID)。
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
***
mLayoutInflater.inflate(layoutResID, mContentParent);
***
}
可以看到PhoneWindow中的setContentView(layoutResID)中调用了对mContentParent这个变量进行了判断,若为null则会调用installDecor(),那么进入该方法一探究竟。
private void installDecor() {
***
if (mDecor == null) {
//这里创建了一个mDecor对象
mDecor = generateDecor(-1);
}
***
if (mContentParent == null) {
//这里创建了一个mContentParent 对象
mContentParent = generateLayout(mDecor);
}
***
}
这个方法主要创建了两个对象一个是mDecor ,一个是mContentParent ,我们接着看看这两个对象的数据类型。
// This is the top-level view of the window, containing the window decor.
//看到这里估计很多童鞋要激动了 ,这个不是我们常说的DecorView吗! 就那个window的顶层 View,表急,我们慢慢看。
private DecorView mDecor;
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
//这里注释说这个view是用来放置window内容的。
ViewGroup mContentParent;
从注释里可以看出,mDecor就是仅次于Window的顶层view,mContentParent是用来放置Window内容的,那么mDecor与mContentParent到底有什么联系呢?我猜测mDecor应该包含了mContentParent(即mDecor中添加了mContentParent),我们继续看generateLayout(mDecor)。
protected ViewGroup generateLayout(DecorView decor) {
***
int layoutResource;
***
//这里说明下 此处源码太多,只列出了一个条件。
大概意思就是根据主题的不同,选择不同的资源文件
if(***){
layoutResource =R.layout.screen_simple;
}
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
***
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
***
return contentParent ;
}
generateLayout()方法就是根据不同的主题选择不同的资源文件,这些资源文件都是系统自带的。这里我们看下R.layout.screen_simple这个资源文件它的布局是怎样的。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
从布局文件可以看出是个线性布局,上面是一个ViewStub ,下面是一个FrameLayout 对应id为@android:id/content。我们接着上述源码继续看,选择了资源文件后会调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource),调用上面传进来的decorView的onResourcesLoaded方法,传入获取到的资源文件id。
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
***
//这里调用了inflater.inflate方法加载资源文件
final View root = inflater.inflate(layoutResource, null);
***
//将加载的资源文件对象view添加到decorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
***
}
在上述方法里加载资源文件获取到view,并将该view添加到decorView中。回到
generateLayout(DecorView decor)方法中,在该方法中调用了mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法后,还执行了
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)这行代码,这里看下ID_ANDROID_CONTENT这个id对应的是哪个布局的id。
/**
* The ID that the main layout in the XML layout file should have.
*/
//注释说这个id是xml主布局必须有的
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
到这里我们似乎明白了点什么,之前加载的布局资源中有过这个id(即@android:id/content),这个id对应的View的数据类型应该是FrameLayout 。在上述generateLayout(mDecor)方法中执行的逻辑就是根据不同的主题加载布局资源文件,并将该资源文件视图添加到decorView中,然后将该decorView中id为com.android.internal.R.id.content的View返回,返回的View即为mContentParent。这也印证了之前的猜想,mDecor中添加了mContentParent。
至此,phoneWindow中的setContentView(int layoutResID)方法调用 installDecor()就分析完了。接下来接着分析mLayoutInflater.inflate(layoutResID, mContentParent)。
//会调用LayoutInflater的inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//这里会调用inflate(resource, root, root != null)
return inflate(resource, root, root != null);
}
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
//调用 inflate(parser, root, attachToRoot)
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
***
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
root.addView(temp, params);
}
上述代码流程相对简单,最后会调用mLayoutInflater.inflate(layoutResID, mContentParent)方法中传进来的layoutResID加载视图,并将该视图添加到mContentParent。
至此整个view的加载流程就分析完毕了。
总结
在Activity中调用setContentView(int layoutResID)会调用到PhoneWindow中的setContentView(int layoutResID)方法,而PhoneWindow中则会创建一个decorView,加载当前主题对应的布局视图,并将该视图添加到decorView中,最后加载setContentView(int layoutResID)中传入的布局资源id对应的视图,并将该视图添加到decorView中的子视图中。最后来张图解,相信童鞋们就会更加明白整个流程了。
在下水平有限,若文中有错,还请不吝赐教。