在Android開發(fā)中馏艾,最常見的代碼就是setContentView
,然后傳入你寫的布局ID歼捐,那么布局就被加載到界面中了氓拼,系統(tǒng)究竟是怎么被加到界面中的你画,就需要通過源碼來查看了。
點(diǎn)擊setContentView
方法桃漾,進(jìn)去會(huì)發(fā)現(xiàn)調(diào)用了以下的代碼
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看到是通過getWindow
方法來設(shè)置布局文件的坏匪,那我們在看一下這個(gè)getWindow
做了什么事情,在點(diǎn)擊進(jìn)去看一下
/**
* 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;
}
其實(shí)這個(gè)getWindow
方法就是返回了當(dāng)前的window窗口對(duì)象呈队,而且通過注釋我們還可以知道剥槐,如果當(dāng)前的Activity不可見的時(shí)候唱歧,這個(gè)window對(duì)象是為空的宪摧,那么這個(gè)window到底是什么,我們在進(jìn)去看一下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;
關(guān)于window類的源碼截取了一部分几于,可以看到這是一個(gè)抽象類,通過注釋沿后,我們獲取信息沿彭,這個(gè)類有一個(gè)唯一的實(shí)現(xiàn)類PhoneWindow
,所以接下來我們就需要主要去分析一下這個(gè)PhoneWindow
類了尖滚,去看看這個(gè)類的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
這個(gè)類中setContentView
方法中喉刘,我們重要關(guān)注兩個(gè)方法瞧柔,一個(gè)是installDecor
和mLayoutInflater.inflate(layoutResId,mContentParent)
;而且在調(diào)用installDecor
方法的時(shí)候睦裳,還對(duì)mContentParent
進(jìn)行了判斷造锅,那這個(gè)mContentParent
是什么呢,我們通過installDector
方法點(diǎn)進(jìn)去看一下
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
的話蛛蒙,我們對(duì)mDecor進(jìn)行了一個(gè)初始化糙箍,初始化的方法是generateDecor(-1)
,我們點(diǎn)擊去看一下
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());
}
可以看到牵祟,其實(shí)這個(gè)方法就是對(duì)DecorView做了一個(gè)初始化深夯,最后也是返回了一個(gè)DecorView
DecorView是window的一個(gè)頂層容器,繼承自FrameLayout
看完這個(gè)generateDecor
之后课舍,我們在回到installDecor
方法塌西,接著往下看,
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
我們發(fā)現(xiàn)筝尾,在初始化mDecor之后捡需,又對(duì)mContentParent
進(jìn)行了初始化,那么這個(gè)mContentParent是什么筹淫,在通過generateLayour(mDecor)
方法來對(duì)里面的具體實(shí)現(xiàn)進(jìn)行探索
代碼點(diǎn)進(jìn)去有點(diǎn)多站辉,在開始的時(shí)候,是通過系統(tǒng)內(nèi)部的一些樣式來進(jìn)行一些特性樣式的設(shè)置损姜,這里可以略過饰剥,然后真正需要研究的代碼是從注釋中 Inflate the window decor
開始
可以在這里看到,源碼中初始化了一個(gè)int layoutResource
;那么我們往下研究摧阅,發(fā)現(xiàn)下面就是對(duì)這個(gè)layoutResource
字段進(jìn)行賦值操作汰蓉。簡單的理解就是通過不同的樣式來加載系統(tǒng)中一些默認(rèn)的布局文件,
在對(duì)這個(gè)layoutResource字段進(jìn)行賦值之后棒卷,我們重點(diǎn)關(guān)注兩行代碼顾孽,
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
第一行代碼中,主要是這個(gè)mDecor.onResourcesLoaded()
方法比规,傳入了之前賦值的layoutResource
字段若厚,這個(gè)方法點(diǎn)進(jìn)去之后發(fā)現(xiàn),其實(shí)就是對(duì)這個(gè)layoutResource
指定的布局進(jìn)行的繪制然后設(shè)置給了mDecor
蜒什,其實(shí)也就是相當(dāng)于理解為給頂層DecorView
設(shè)置了一個(gè)布局测秸,而這個(gè)布局是系統(tǒng)內(nèi)置的,可以通過樣式來指定加載哪些不同的布局文件。
第二行代碼中霎冯,發(fā)現(xiàn)進(jìn)行了一個(gè)findViewById
操作铃拇,那這個(gè)ID是什么,點(diǎn)擊去看一下
/**
* 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;
通過注釋可以獲取到信息就是沈撞,這個(gè)id是主要的入口布局ID锚贱,并且必須有,在獲取到這個(gè)contentParent
之后关串,這個(gè)方法就將這個(gè)對(duì)象進(jìn)行了返回拧廊,那這里就很疑問了,為什么這個(gè)id一定含有晋修,我們通過layouttResource
字段來看看之前加載的系統(tǒng)的布局文件吧碾。我們以系統(tǒng)中的R.layout.screen_simple
布局為例,發(fā)現(xiàn)他的布局是這樣寫的
<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>
可以看出墓卦,這有一個(gè)布局id為content的FrameLayout
倦春,所以其實(shí)我們通過findviewViewById
來獲取的控件就是這個(gè)FrameLayout
,通過查看系統(tǒng)中其他的布局文件落剪,我們都能發(fā)現(xiàn)有一個(gè)ID為content的FrameLayout
控件睁本。所以是shuld have
那么這里我們一層層的返回,就會(huì)發(fā)現(xiàn)忠怖,其實(shí)PhoneWindow
類中的mContentParent
就是DecorView
中的一個(gè)FrameLayout
呢堰,在回到setContentView
方法中,在對(duì)mContentParent
進(jìn)行初始化完成后凡泣,調(diào)用了mLayoutInflater.inflate(layoutResID, mContentParent)
;方法枉疼,這里的layoutResId
就是我們傳進(jìn)來的布局ID,然后將布局進(jìn)行填充添加到界面中鞋拟,這樣我們的setContentView
的整個(gè)工作就完成了骂维。
總結(jié)
看一張示意圖
我們以R.layout.screen_simple.xml
為例來進(jìn)行講解,當(dāng)調(diào)用setContentView
的時(shí)候贺纲,系統(tǒng)會(huì)先對(duì)DecorView
進(jìn)行判斷航闺,如果為空的話就初始化,初始化完DecorView
之后猴誊,在對(duì)其布局進(jìn)行一個(gè)初始化潦刃,這個(gè)布局會(huì)根據(jù)開發(fā)者指定的樣式來指定不同的布局,但是每一個(gè)布局文件中都會(huì)有一個(gè)id為content
的FrameLayout
控件稠肘,初始化完DevorView的布局的時(shí)候福铅,也會(huì)初始化這個(gè)FrameLayout
萝毛,源碼中的字段就是mContentParent
项阴,在拿到這個(gè)mContentParent
之后,會(huì)將我們傳入的布局文件加載到這個(gè)FrameLayout
中,這樣我們就能看見自己寫的布局文件了