平時(shí)我們加載xml文件都是直接在onCreate方法中調(diào)用setContentView,在加載Activity的onCreate方法時(shí)布局就被加載出來(lái)了,但是深入一點(diǎn)看,發(fā)現(xiàn)內(nèi)容還是很多的,看了很多大神相關(guān)的博客,也寫(xiě)了個(gè)總結(jié),內(nèi)容可能不全,不足之處,還請(qǐng)多指教。
1.先從setContentView開(kāi)始說(shuō)起
Activity中有3個(gè)setContentView()方法,可以看到, 這三個(gè)方法都是先getWindow,獲得一個(gè)Window對(duì)象,然后調(diào)用它的setContentView,那么這個(gè)Window又是什么呢?
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
///
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
////
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
1.1 創(chuàng)建Window對(duì)象
getWindow返回的是mWindow,而這個(gè)mWindow是由PolicyManager創(chuàng)建的,PolicyManager提供了靜態(tài)類方法(這里用到了工廠模式,PolicyManager提供工廠方法),創(chuàng)建了一個(gè)PhoneWindow 對(duì)象。
//創(chuàng)建一個(gè)Window對(duì)象
mWindow = PolicyManager.makeNewWindow(this);
最終創(chuàng)建Window對(duì)象的方法
//創(chuàng)建具體對(duì)象的接口
public PhoneWindow makeNewWindow(Context context) {
return new PhoneWindow(context);
}
Window:是一個(gè)抽象類,提供繪制窗口的通用API。
PhoneWindow :是Window的唯一的實(shí)現(xiàn)類,每個(gè)Activity都會(huì)有一個(gè)PhoneWindow,它是Activity和整個(gè)View交互的接口。該類內(nèi)部包含了一個(gè)DecorView對(duì)象,該DectorView對(duì)象是所有應(yīng)用窗口(Activity界面)的根View。
DectorView:是PhoneWindow的內(nèi)部類,繼承自FrameLayout。
1.2 調(diào)用PhoneWindow對(duì)象的setContentView方法
一層層下來(lái),發(fā)現(xiàn)Activity的setContentView()實(shí)際上是執(zhí)行的是PhoneWindow的方法,現(xiàn)在來(lái)看一下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);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
以上代碼分析:
1 . 判斷mContentParent是否為空。從名字可以看出,這是父容器,它是一個(gè)ViewGroup類型的對(duì)象,是真正的content的Parent,如果是第一次調(diào)用,會(huì)調(diào)用installDecor(),在這個(gè)方法中會(huì)先判斷DectorView是否為空,為空就先創(chuàng)建DectorView對(duì)象mDecor,然后調(diào)用generateLayout(mDecor)得到mContentParent;如果不是第一次調(diào)用,會(huì)先清除mContentParent中的子view。
2 . mLayoutInflater.inflate(layoutResID, mContentParent);將傳入的資源文件轉(zhuǎn)換成View樹(shù),再添加到mContentParent中(mLayoutInflater 在PhoneWindow的構(gòu)造函數(shù)中通過(guò)mLayoutInflater = LayoutInflater.from(context)得到)。
再來(lái)看一下其他的兩個(gè)setContentView():
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
可以看到,其實(shí)做的事情都差不多,只不過(guò)多設(shè)置了LayoutParams,因?yàn)閭魅氲腣iew,就不需要像第一個(gè)那樣還從xml文件中解析出來(lái),可以直接將view添加到mContentParent中。
小結(jié):比較3個(gè)setContentView
從上面的代碼中,我們可以看到,第一個(gè)setContentView是通過(guò)反射解析傳入的布局文件,然后添加到mContentParent,而后兩個(gè)setContentView是直接將傳入的View添加到mContentParent,需要注意的是,每次反射拿到的View都是重新創(chuàng)建的,就算兩次setContentView加載的是同一個(gè)布局文件,控件的實(shí)例也是不一樣的,如傳入的是View/ViewGroup就能保證傳入的是同一組控件。
1.3 installDecor()實(shí)例化DectorView對(duì)象
現(xiàn)在來(lái)看一下剛剛提到的installDecor(),初始化mDecor ,創(chuàng)建mContentParent,根據(jù)窗口的風(fēng)格修飾,選擇對(duì)應(yīng)的修飾布局文件,這里內(nèi)容太多,省略了。
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根據(jù)窗口的風(fēng)格修飾,選擇對(duì)應(yīng)的修飾布局文件
mContentParent = generateLayout(mDecor);
......
}
}
1.4 generateLayout()創(chuàng)建mContentParent
接下來(lái)看generateLayout()創(chuàng)建mContentParent 的過(guò)程。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//根據(jù)當(dāng)前的主題設(shè)置窗口屬性
......
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
//根據(jù)當(dāng)前的窗口屬性選擇相對(duì)應(yīng)的布局
WindowManager.LayoutParams params = getAttributes();
......
//將相應(yīng)的布局文件轉(zhuǎn)成view添加到窗口視圖對(duì)象decor中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_Android_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
return contentParent;
}
這段代碼所做的事情:
- 根據(jù)當(dāng)前的主題設(shè)置窗口屬性;
- 根據(jù)當(dāng)前的窗口屬性選擇相對(duì)應(yīng)的布局;
- 將相應(yīng)的布局文件轉(zhuǎn)成view添加到根視圖對(duì)象decor/mDecor中
View in = mLayoutInflater.inflate(layoutResource, null); decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));,需要注意的是,這里的layoutResource并不是我們傳入的資源文件,而是系統(tǒng)定義的。 - 從根布局中找到id為
ID_Android_CONTENT的ViewGroup賦值給contentParent,也就是上文的mContentParent。
總結(jié):Activity,PhoneWindow,DectorView,mContentParent之間的關(guān)系
通過(guò)上面的分析,我們來(lái)看一下彼此之間的關(guān)系,有助于理解。
DecorView繼承于FrameLayout,然后它有一個(gè)子view即LinearLayout,方向?yàn)樨Q直方向,其內(nèi)有兩個(gè)FrameLayout,上面的FrameLayout即為T(mén)itleBar之類的,下面的FrameLayout即為我們的ContentView,所謂的setContentView就是往這個(gè)FrameLayout里面添加我們的布局View的!


2.PhoneWindow的setContentView最后的回調(diào)
上面分析了加載視圖到父容器mContentParent中,現(xiàn)在我們看一下setContentView()中的最后一步。
@Override
public void setContentView(int layoutResID) {
......
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
至此,已經(jīng)分析完了將傳入的布局文件添加到View的整個(gè)過(guò)程。接下來(lái)可以看到,它先創(chuàng)建了一個(gè)CallBack回調(diào)接口,在加載完以上的View到根布局之后,就會(huì)調(diào)用這個(gè)回調(diào)接口,順便說(shuō)一下,cb.onContentChanged()方法在Activity中是一個(gè)空方法,我們可以在自定義的Activity中覆寫(xiě)這個(gè)方法。
現(xiàn)在看getCallback()是由Window提供的,PhoneWindow并沒(méi)有實(shí)現(xiàn),繼續(xù)往下看,發(fā)現(xiàn)Window中有一個(gè)public void setCallback(Callback callback)方法,接收到外部傳入的callback,賦值給內(nèi)部的mCallback 。那么這個(gè)外部方法在哪里調(diào)用呢?這個(gè)就要說(shuō)一下Activity的啟動(dòng)了。
3.Activity的啟動(dòng)
在Activity加載時(shí)會(huì)先創(chuàng)建一個(gè)activity實(shí)例,然后調(diào)用activity的attach方法完成activity的初始化過(guò)程,我們來(lái)看一下attach()方法。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
//1.創(chuàng)建窗口對(duì)象,是一個(gè)PhoneWindow實(shí)例
mWindow = PolicyManager.makeNewWindow(this);
//2.設(shè)置回調(diào)
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
......
mToken = token;
//3.將創(chuàng)建的WindowManager注入窗口對(duì)象以便管理窗口的視圖對(duì)象
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
//4. 獲取窗口管理器
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
在這個(gè)方法中,完成了上文提到的Window對(duì)象的創(chuàng)建。
為Window對(duì)象設(shè)置回調(diào),也就是上面
setContentView()中最后獲取到cb。這里設(shè)置的回調(diào)就是Activity自己,再看Activity,它實(shí)現(xiàn)了Window.Callback, KeyEvent.Callback兩個(gè)回調(diào)接口。其中KeyEvent.Callback接口中聲明了處理手勢(shì)事件的方法(onKeyDown按下,onKeyUp抬起,onKeyLongPress長(zhǎng)按...),而Window.Callback聲明了一些事件分發(fā)的函數(shù),關(guān)于View的事件分發(fā),可以看這個(gè) View的事件分發(fā)機(jī)制 ,從這里可以看出activity本身不具備處理用戶的事件的能力。為mWindow設(shè)置
WindowManager,WindowManager主要用來(lái)管理窗口的一些狀態(tài)、屬性、view增加、刪除、更新、窗口順序、消息收集和處理等.獲取Window的WindowManager的實(shí)現(xiàn)類WindowManagerImpl保存在activity的mWindowManager中。
3.1 關(guān)于mWindow.setWindowManager()方法
從setWindowManager方法中可以發(fā)現(xiàn),這里返回的是WindowManagerImpl對(duì)象,WindowManager是一個(gè)接口,而WindowManagerImpl是它的實(shí)現(xiàn)類,這里調(diào)用createLocalWindowManager()。
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
WindowManagerImpl中的createLocalWindowManager()方法
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
3.2 將生成的窗口視圖對(duì)象是添加到手機(jī)屏幕
我們知道,Activity的視圖是在Activity的生命周期onResume()方法執(zhí)行之后才會(huì)顯示的,這是因?yàn)樵贏ctivityThread的handleResumeActivity方法中調(diào)用了Activity的onResume()方法,關(guān)于Activity的啟動(dòng)這一部分內(nèi)容我還沒(méi)有看過(guò),“老羅的Android之旅”中有這一部分內(nèi)容的詳細(xì)分析,有興趣的可以看一下。在這個(gè)函數(shù)中,會(huì)調(diào)用activity的makeVisible方法經(jīng)WindowManagerImpl將DecorView展示出來(lái)。這里的getWindowManager()就是之前設(shè)置的WindowManager。最后就Activity就可以請(qǐng)求WindowManagerService將視圖繪制到屏幕上了。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
總結(jié)
總結(jié)一下整個(gè)View的加載過(guò)程:
首先在Activity啟動(dòng)時(shí),在
attach方法中先創(chuàng)建Activity的窗口對(duì)象,它是PhoneWindow類型,每個(gè)Activity都有一個(gè)窗口對(duì)象,然后為這個(gè)窗口設(shè)置各種事件的回調(diào),還要注冊(cè)其對(duì)應(yīng)的窗口管理器,用來(lái)管理窗口的一些狀態(tài),屬性,view的更新等。當(dāng)調(diào)用Activity的
onCreat方法時(shí),會(huì)調(diào)用設(shè)置布局文件,Activity的setContentView其實(shí)調(diào)用的是Activity的窗口對(duì)象PhoneWindow的setContentView,PhoneWindow有一個(gè)內(nèi)部類DectorView(FrameLayout的子類),它是整個(gè)窗口下的根View,內(nèi)部包含兩個(gè)FrameLayout,一個(gè)根據(jù)主題樣式來(lái)進(jìn)行TitleBar之類設(shè)置,一個(gè)就是用來(lái)裝我們傳入的布局文件中的view,這個(gè)就是mContetntParent。第一次調(diào)用PhoneWindow的setcontentView方法會(huì)先創(chuàng)建DectorView,進(jìn)行一些初始化的設(shè)置,然后解析系統(tǒng)的資源文件到DectorView中,接著會(huì)找到id為ID_Android_CONTENT的FrameLayout,將其賦值給mContetntParent,用來(lái)放我們傳入的資源文件解析出來(lái)的view.這些初始化的設(shè)置完成之后,就是處理我們調(diào)用
setContentView時(shí)傳入的布局文件了,如果傳入的資源文件id,會(huì)調(diào)用反射機(jī)制解析xml文件,再把解析出來(lái)的各個(gè)view加到mContetntParent,如果傳入的是View,那么直接加到mContetntParent就可以,最后就是系統(tǒng)在調(diào)用onResume之后,經(jīng)之前設(shè)置的WindowManager將整個(gè)DecorView展示出來(lái)。