在Android开发中,最常见的代码就是setContentView
,然后传入你写的布局ID,那么布局就被加载到界面中了,系统究竟是怎么被加到界面中的,就需要通过源码来查看了。
点击setContentView
方法,进去会发现调用了以下的代码
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到是通过getWindow
方法来设置布局文件的,那我们在看一下这个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.
*/
public Window getWindow() {
return mWindow;
}
其实这个getWindow
方法就是返回了当前的window窗口对象,而且通过注释我们还可以知道,如果当前的Activity不可见的时候,这个window对象是为空的,那么这个window到底是什么,我们在进去看一下window类是什么样的,
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
/** Flag for the "options panel" feature. This is enabled by default. */
public static final int FEATURE_OPTIONS_PANEL = 0;
/** Flag for the "no title" feature, turning off the title at the top
* of the screen. */
public static final int FEATURE_NO_TITLE = 1;
关于window类的源码截取了一部分,可以看到这是一个抽象类,通过注释,我们获取信息,这个类有一个唯一的实现类PhoneWindow
,所以接下来我们就需要主要去分析一下这个PhoneWindow
类了,去看看这个类的setContentView
做了什么
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
在PhoneWindow
这个类中setContentView
方法中,我们重要关注两个方法,一个是installDecor
和mLayoutInflater.inflate(layoutResId,mContentParent)
;而且在调用installDecor
方法的时候,还对mContentParent
进行了判断,那这个mContentParent
是什么呢,我们通过installDector
方法点进去看一下
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
由于源码里面中的方法代码太长了,这里只做部分截取,这里看到,如果mDecor==null
的话,我们对mDecor进行了一个初始化,初始化的方法是generateDecor(-1)
,我们点击去看一下
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
可以看到,其实这个方法就是对DecorView做了一个初始化,最后也是返回了一个DecorView
DecorView是window的一个顶层容器,继承自FrameLayout
看完这个generateDecor
之后,我们在回到installDecor
方法,接着往下看,
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
我们发现,在初始化mDecor之后,又对mContentParent
进行了初始化,那么这个mContentParent是什么,在通过generateLayour(mDecor)
方法来对里面的具体实现进行探索
代码点进去有点多,在开始的时候,是通过系统内部的一些样式来进行一些特性样式的设置,这里可以略过,然后真正需要研究的代码是从注释中 Inflate the window decor
开始
可以在这里看到,源码中初始化了一个int layoutResource
;那么我们往下研究,发现下面就是对这个layoutResource
字段进行赋值操作。简单的理解就是通过不同的样式来加载系统中一些默认的布局文件,
在对这个layoutResource字段进行赋值之后,我们重点关注两行代码,
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
第一行代码中,主要是这个mDecor.onResourcesLoaded()
方法,传入了之前赋值的layoutResource
字段,这个方法点进去之后发现,其实就是对这个layoutResource
指定的布局进行的绘制然后设置给了mDecor
,其实也就是相当于理解为给顶层DecorView
设置了一个布局,而这个布局是系统内置的,可以通过样式来指定加载哪些不同的布局文件。
第二行代码中,发现进行了一个findViewById
操作,那这个ID是什么,点击去看一下
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
通过注释可以获取到信息就是,这个id是主要的入口布局ID,并且必须有,在获取到这个contentParent
之后,这个方法就将这个对象进行了返回,那这里就很疑问了,为什么这个id一定含有,我们通过layouttResource
字段来看看之前加载的系统的布局文件。我们以系统中的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>
可以看出,这有一个布局id为content的FrameLayout
,所以其实我们通过findviewViewById
来获取的控件就是这个FrameLayout
,通过查看系统中其他的布局文件,我们都能发现有一个ID为content的FrameLayout
控件。所以是shuld have
那么这里我们一层层的返回,就会发现,其实PhoneWindow
类中的mContentParent
就是DecorView
中的一个FrameLayout
,在回到setContentView
方法中,在对mContentParent
进行初始化完成后,调用了mLayoutInflater.inflate(layoutResID, mContentParent)
;方法,这里的layoutResId
就是我们传进来的布局ID,然后将布局进行填充添加到界面中,这样我们的setContentView
的整个工作就完成了。
总结
看一张示意图
WX20190807-154652@2x.png我们以R.layout.screen_simple.xml
为例来进行讲解,当调用setContentView
的时候,系统会先对DecorView
进行判断,如果为空的话就初始化,初始化完DecorView
之后,在对其布局进行一个初始化,这个布局会根据开发者指定的样式来指定不同的布局,但是每一个布局文件中都会有一个id为content
的FrameLayout
控件,初始化完DevorView的布局的时候,也会初始化这个FrameLayout
,源码中的字段就是mContentParent
,在拿到这个mContentParent
之后,会将我们传入的布局文件加载到这个FrameLayout
中,这样我们就能看见自己写的布局文件了
网友评论