Android的setContentView()方法我們平時(shí)用很多冤寿,但是有多少人會(huì)點(diǎn)進(jìn)setContentView()方法里面看看它的源碼究竟是何方神圣呢馏慨,今天我就來(lái)看看從這個(gè)方法里面究竟涉及到多少未知的知識(shí)夷陋。
public class ViewActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
}
}
懷著好奇心我點(diǎn)下了setContentView()這個(gè)方法去尋根索源:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow().setContentView(layoutResID)這是什么鬼饥臂,然后點(diǎn)進(jìn)去看看:
我的天夺刑,竟然看到setContentView()是一個(gè)叫Window類的抽象方法愉棱,Window相信每個(gè)人都聽(tīng)過(guò)魂莫,但是對(duì)于Android的Window相信不是所有人都了解还蹲,我也是一樣,然后我?guī)е鴨?wèn)題翻閱了書(shū)本耙考。
Window
摘自來(lái)自《Android開(kāi)發(fā)藝術(shù)探索》的解釋:
Window表示一個(gè)窗口的概念谜喊,在日常開(kāi)發(fā)中直接接觸Window的機(jī)會(huì)并不多,但是在某些特殊時(shí)候我們需要在桌面上顯示一個(gè)類似懸浮窗的東西倦始,那么這種效果就需要用到Window來(lái)實(shí)現(xiàn)斗遏。Window是一個(gè)抽象類,他的具體實(shí)現(xiàn)是PhoneWindow鞋邑。創(chuàng)建一個(gè)Window是很簡(jiǎn)單的事诵次,只需要通過(guò)WindowManager即可完成账蓉。WindowManager是外界訪問(wèn)Window的入口,Window的具體實(shí)現(xiàn)位于WindowManagerService中逾一,WindowManager和WindowManagerService的交互是一個(gè)IPC過(guò)程铸本。Android所有的視圖都是通過(guò)Window來(lái)呈現(xiàn)的,不管是Activity遵堵、Dialog還是Toast箱玷,他們的視圖實(shí)際上都是附加在Window上的,因此Window實(shí)際上是View的直接管理者陌宿。
IPC:Inter-Process Communication的縮寫(xiě)锡足,含義為進(jìn)程間通信或者跨進(jìn)程通信,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過(guò)程壳坪。
看了一大輪的文字概念舶得,我就想睡覺(jué)了。但是看了那么久弥虐,總算幾個(gè)關(guān)鍵詞PhoneWindow扩灯,WindowManager和WindowManagerService。上面講到PhoneWindow是Window的實(shí)現(xiàn)類霜瘪,那么我們先去看看PhoneWindow吧珠插。
@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();
}
...
}
在PhoneWindow確實(shí)找到了setContentView()方法的具體實(shí)現(xiàn)。
mContentParent是什么颖对?
ViewGroup mContentParent;
暫時(shí)還不知道它是什么捻撑,那我們當(dāng)它是null吧,進(jìn)入installDecor()方法看看:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
...
下面省略了一大堆UiOptions缤底,setIcon顾患,Transition的方法
} else {
...
}
}
mDecor是什么?
private DecorView mDecor;
DecorView是什么个唧?
書(shū)本是這樣寫(xiě)的:
ViewRoot對(duì)應(yīng)于ViewRootImpl類江解,它是連接WindowManager和DecorView的紐帶,View的三大流程(onMeasure(),onLayout(),onDraw())均是通過(guò)ViewRoot來(lái)完成的徙歼。在ActivityThread中犁河,當(dāng)Activity對(duì)象被創(chuàng)建完畢后,會(huì)將DecorView添加到Window中魄梯,同時(shí)會(huì)創(chuàng)建ViewRootImpl對(duì)象桨螺,并將ViewRootImpl對(duì)象和DecorView建立關(guān)聯(lián)。
我真的醉了酿秸,越翻越多自己不懂的概念出來(lái):ViewRoot灭翔,ViewRootImpl,現(xiàn)在暫且做個(gè)筆記吧,先不管了辣苏。先看看我們找到的線索:
當(dāng)Activity對(duì)象被創(chuàng)建完畢后肝箱,會(huì)將DecorView添加到Window中.
這就是我們要找的東西哄褒。DecorView原來(lái)是這樣用的。
回到installDecor()中狭园,當(dāng)mDecor為空時(shí)读处,調(diào)用generateDecor(-1)方法:
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
這里創(chuàng)建了一個(gè)DecorView了,我們發(fā)現(xiàn)DecorView其實(shí)是一個(gè)FrameLayout唱矛,再回到installDecor(),這時(shí)候我們知道m(xù)ContentParent仍然為null井辜,那么進(jìn)入generateLayout(mDecor)方法:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根據(jù)當(dāng)前設(shè)置的主題來(lái)加載默認(rèn)布局
TypedArray a = getWindowStyle();
...
設(shè)置各種各樣的屬性
...
//如果你在theme中設(shè)置了window_windowNoTitle绎谦,則這里會(huì)調(diào)用到,其他方法同理粥脚,
//這里是根據(jù)你在theme中的設(shè)置去設(shè)置的
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
requestFeature(FEATURE_ACTION_BAR);
}
//是否有設(shè)置全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
...
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} //省略其他判斷方法
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//選擇對(duì)應(yīng)布局創(chuàng)建添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
首先generateLayout會(huì)根據(jù)當(dāng)前用戶設(shè)置的主題去設(shè)置對(duì)應(yīng)的Feature窃肠,接著,根據(jù)對(duì)應(yīng)的Feature來(lái)選擇加載對(duì)應(yīng)的布局文件刷允,(Window.FEATURE_NO_TITLE)接下來(lái)通過(guò)getLocalFeatures來(lái)獲取你設(shè)置的feature冤留,進(jìn)而選擇加載對(duì)應(yīng)的布局,這也就是為什么我們要在setContentView之前調(diào)用requesetFeature的原因树灶。
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
我們還能看到contentParent其實(shí)是一個(gè)叫com.android.internal.R.id.content的布局纤怒,最后添加到DecorView上。generateLayout()方法最后返回的就是contentParent天通。好了泊窘,installDecor()方法走完了,創(chuàng)建了DecorView和在上面設(shè)置了一大堆屬性像寒,并創(chuàng)建帶來(lái)了一個(gè)mContentParent烘豹,再回到PhoneWindow的setContentView()中
@Override
public void setContentView(int layoutResID) {
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 {
//把mContentParent加載到mLayoutInflater中,
//而mLayoutInflater在上面generateLayout(DecorView decor)方法中
//早已加載到DecorView中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回調(diào)通知表示完成界面改變
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
此時(shí)已經(jīng)創(chuàng)建完DecorView并且獲取到mContentParent诺祸,接著就是將你setContentView的內(nèi)容添加到mContentParent中携悯,也就是
mLayoutInflater.inflate(layoutResID, mContentParent);
或者
mContentParent.addView(view, params);
來(lái)到這里該總結(jié)一下了:
真是千言萬(wàn)語(yǔ)都在這圖中了,網(wǎng)上盜的圖真是萬(wàn)能的筷笨,我的總結(jié)都在這個(gè)圖中了憔鬼。
我以前一直不懂為什么setContentView()叫setContentView而不叫setView呢,那是因?yàn)槲覀兯鶆?chuàng)建的布局其實(shí)是Activity里面的PhoneWindow創(chuàng)建出來(lái)的DecorView里面的ContentView來(lái)的而已奥秆。一開(kāi)始以為你是老大逊彭,現(xiàn)在才發(fā)現(xiàn)你是個(gè)小弟大概就是這種感覺(jué)吧。
等等构订,雖然知道setContentView()方法是怎么來(lái)的侮叮,但是在看它的源碼中,我們還發(fā)現(xiàn)了好幾個(gè)疑問(wèn):WindowManager悼瘾,ViewRoot囊榜,ViewRootImpl审胸,PhoneWindow,WindowManagerService卸勺。他們幾個(gè)的關(guān)系又是怎么個(gè)錯(cuò)綜復(fù)雜呢砂沛?拿著這些線索,我們下一篇文章再來(lái)探個(gè)究竟吧曙求。
我的掘金:
https://juejin.im/user/594e8e9a5188250d7b4cd875/posts
我的簡(jiǎn)書(shū):
http://www.reibang.com/u/b538ca57f640