Android的setContentView()方法我們平時用很多,但是有多少人會點進setContentView()方法裡面看看它的原始碼究竟是何方神聖呢,今天我就來看看從這個方法裡面究竟涉及到多少未知的知識。
public class ViewActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
}
}
複製程式碼
懷著好奇心我點下了setContentView()這個方法去尋根索源:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
複製程式碼
getWindow().setContentView(layoutResID)這是什麼鬼,然後點進去看看:
我的天,竟然看到setContentView()是一個叫Window類的抽象方法,Window相信每個人都聽過,但是對於Android的Window相信不是所有人都瞭解,我也是一樣,然後我帶著問題翻閱了書本。Window
摘自來自《Android開發藝術探索》的解釋:
Window表示一個視窗的概念,在日常開發中直接接觸Window的機會並不多,但是在某些特殊時候我們需要在桌面上顯示一個類似懸浮窗的東西,那麼這種效果就需要用到Window來實現。Window是一個抽象類,他的具體實現是PhoneWindow。建立一個Window是很簡單的事,只需要通過WindowManager即可完成。WindowManager是外界訪問Window的入口,Window的具體實現位於WindowManagerService中,WindowManager和WindowManagerService的互動是一個IPC過程。Android所有的檢視都是通過Window來呈現的,不管是Activity、Dialog還是Toast,他們的檢視實際上都是附加在Window上的,因此Window實際上是View的直接管理者。
IPC:Inter-Process Communication的縮寫,含義為程式間通訊或者跨程式通訊,是指兩個程式之間進行資料交換的過程。
看了一大輪的文字概念,我就想睡覺了。但是看了那麼久,總算幾個關鍵詞PhoneWindow,WindowManager和WindowManagerService。上面講到PhoneWindow是Window的實現類,那麼我們先去看看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確實找到了setContentView()方法的具體實現。 mContentParent是什麼?
ViewGroup mContentParent;
複製程式碼
暫時還不知道它是什麼,那我們當它是null吧,進入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是什麼? 書本是這樣寫的: 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()叫setContentView而不叫setView呢,那是因為我們所建立的佈局其實是Activity裡面的PhoneWindow建立出來的DecorView裡面的ContentView來的而已。一開始以為你是老大,現在才發現你是個小弟大概就是這種感覺吧。等等,雖然知道setContentView()方法是怎麼來的,但是在看它的原始碼中,我們還發現了好幾個疑問:WindowManager,ViewRoot,ViewRootImpl,PhoneWindow,WindowManagerService。他們幾個的關係又是怎麼個錯綜複雜呢?拿著這些線索,我們下一篇文章再來探個究竟吧。
我的掘金: juejin.im/user/594e8e…