Android的setContentView()方法咱們平時用不少,可是有多少人會點進setContentView()方法裏面看看它的源碼到底是何方神聖呢,今天我就來看看從這個方法裏面究竟涉及到多少未知的知識。android
public class ViewActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
}
}
複製代碼
懷着好奇心我點下了setContentView()這個方法去尋根索源:bash
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
複製代碼
getWindow().setContentView(layoutResID)這是什麼鬼,而後點進去看看:app
Windowide
摘自來自《Android開發藝術探索》的解釋:佈局
Window表示一個窗口的概念,在平常開發中直接接觸Window的機會並很少,可是在某些特殊時候咱們須要在桌面上顯示一個相似懸浮窗的東西,那麼這種效果就須要用到Window來實現。Window是一個抽象類,他的具體實現是PhoneWindow。建立一個Window是很簡單的事,只須要經過WindowManager便可完成。WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。Android全部的視圖都是經過Window來呈現的,不論是Activity、Dialog仍是Toast,他們的視圖實際上都是附加在Window上的,所以Window其實是View的直接管理者。post
IPC:Inter-Process Communication的縮寫,含義爲進程間通訊或者跨進程通訊,是指兩個進程之間進行數據交換的過程。ui
看了一大輪的文字概念,我就想睡覺了。可是看了那麼久,總算幾個關鍵詞PhoneWindow,WindowManager和WindowManagerService。上面講到PhoneWindow是Window的實現類,那麼咱們先去看看PhoneWindow吧。this
@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確實找到了setContentView()方法的具體實現。 mContentParent是什麼?spa
ViewGroup mContentParent;
複製代碼
暫時還不知道它是什麼,那咱們當它是null吧,進入installDecor()方法看看:code
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是什麼? 書本是這樣寫的: ViewRoot對應於ViewRootImpl類,它是鏈接WindowManager和DecorView的紐帶,View的三大流程(onMeasure(),onLayout(),onDraw())均是經過ViewRoot來完成的。在ActivityThread中,當Activity對象被建立完畢後,會將DecorView添加到Window中,同時會建立ViewRootImpl對象,並將ViewRootImpl對象和DecorView創建關聯。
我真的醉了,越翻越多本身不懂的概念出來:ViewRoot,ViewRootImpl,如今暫且作個筆記吧,先無論了。先看看咱們找到的線索:
當Activity對象被建立完畢後,會將DecorView添加到Window中.
這就是咱們要找的東西。DecorView原來是這樣用的。
回到installDecor()中,當mDecor爲空時,調用generateDecor(-1)方法:
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
這裏建立了一個DecorView了,咱們發現DecorView實際上是一個FrameLayout,再回到installDecor(),這時候咱們知道mContentParent仍然爲null,那麼進入generateLayout(mDecor)方法:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//根據當前設置的主題來加載默認佈局
TypedArray a = getWindowStyle();
...
設置各類各樣的屬性
...
//若是你在theme中設置了window_windowNoTitle,則這裏會調用到,其餘方法同理,
//這裏是根據你在theme中的設置去設置的
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);
}
//是否有設置全屏
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();
//選擇對應佈局建立添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
複製代碼
首先generateLayout會根據當前用戶設置的主題去設置對應的Feature,接着,根據對應的Feature來選擇加載對應的佈局文件,(Window.FEATURE_NO_TITLE)接下來經過getLocalFeatures來獲取你設置的feature,進而選擇加載對應的佈局,這也就是爲何咱們要在setContentView以前調用requesetFeature的緣由。
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
複製代碼
咱們還能看到contentParent實際上是一個叫com.android.internal.R.id.content的佈局,最後添加到DecorView上。generateLayout()方法最後返回的就是contentParent。好了,installDecor()方法走完了,建立了DecorView和在上面設置了一大堆屬性,並建立帶來了一個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()) {
//回調通知表示完成界面改變
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
此時已經建立完DecorView而且獲取到mContentParent,接着就是將你setContentView的內容添加到mContentParent中,也就是
mLayoutInflater.inflate(layoutResID, mContentParent);
或者
mContentParent.addView(view, params);
複製代碼
來到這裏該總結一下了:
等等,雖然知道setContentView()方法是怎麼來的,可是在看它的源碼中,咱們還發現了好幾個疑問:WindowManager,ViewRoot,ViewRootImpl,PhoneWindow,WindowManagerService。他們幾個的關係又是怎麼個錯綜複雜呢?拿着這些線索,咱們下一篇文章再來探個究竟吧。
個人掘金: juejin.im/user/594e8e…