Android窗口機制之由setContentView引發(fā)的Window,PhoneWindow支示,DecorView源碼理解
由上一篇文章Activity啟動流程源碼分析,Activity啟動完成最終調(diào)用了ActivityThread.handleLaunchActivity->ActivityThread.performLaunchActivity然后回調(diào)Activity.onCreate鄙才,所以,接著由setContentView引出的Window促绵,PhoneWindow攒庵,DecorView源碼理解,最近也看了好多相關(guān)的文章,記錄自己的見解:
從上面的類圖看:
Window是個抽象類败晴,定義一些頂層窗口的行為策略浓冒,而Window的實現(xiàn)類是PhoneWindow;
DecorView其實是整個Activity窗口的裝飾類吧尖坤,繼承FrameLayout稳懒;
PhoneWindow會持有一個DecorView和mContentParent,mContentParent是DecorView的直接ViewGroup的contentView慢味,即:用來顯示的主要內(nèi)容场梆,不包括title之類的。
第一次畫圖纯路,不是很規(guī)范或油,將就著看吧,我們從Activity的setContentView方法開始:
public void setContentView(@LayoutRes int layoutResID) {
Window window = getWindow();
window.setContentView(layoutResID);
initWindowDecorActionBar();
}
Activity的setContentView方法驰唬,通過layoutResID設(shè)置Activity的主體內(nèi)容顶岸,不包括標題欄狀態(tài)欄,這個資源將被填充至activity的頂層view也就是DecorView的contentView中叫编,而這個方法會直接調(diào)用Window.setContentView方法辖佣,而Window是個抽象類,所以我們看Window的實現(xiàn)類PhoneWindow的setContentView方法:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//創(chuàng)建DecorView搓逾,并內(nèi)容布局賦值到PhoneWindow的mContentParent上
//也就是說mContentParent是DecorView的直接子View殿衰,然后通過將layoutResID的布局有填充的mContentParent上
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
/**
* 經(jīng)過上面的步驟我們已經(jīng)獲取了DecorView中contectView控硼,那么接下來就是就要將傳進來的layoutResID填充contentView的布局了
*/
//是否有過度動畫
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//mContentParent是DecorView的中contentView,就是DecorView的孫子税课,將layoutResID填充到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回調(diào)通知表示完成界面加載
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
整個流程就是先判斷mContentParent是否為null,就調(diào)用installDecor方法去初始化DecorView宋渔,mContentParent是DecorView的內(nèi)容ViewGroup,如果mContentParent填充完就把layoutResID調(diào)用inflate方法將layoutResID的布局填充至mContentParent,剛剛說了installDecor初始化DecorView裳食,我們來看看installDecor方法。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//調(diào)用該方法創(chuàng)建new一個DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//DecorView持有PhoneWindow
mDecor.setWindow(this);
}
//一開始DecorView未加載到mContentParent芙沥,所以此時mContentParent=null
if (mContentParent == null) {
//該方法將mDecorView添加到Window上綁定布局
/**
* Decorview需要填充自己的直接布局有可能是LinearLayout或者FrameLayout,
* 然后通過ID_ANDROID_CONTENT獲取contentView诲祸,最后返回,將contentview復(fù)制給phonewindow的mContentParent
*/
mContentParent = generateLayout(mDecor);
// 省略一萬行代碼................
}
有installDecor方法內(nèi)容較多而昨,省略了一些救氯,在installDecor方法中,如果mDecor 為null的話就會調(diào)用generateDecor方法創(chuàng)建一個DecorView并返回歌憨,上面說到mContentParent 是DecorView的內(nèi)容ViewGroup着憨,所以在如果mContentParent 為null的話將調(diào)用generateLayout方法生成mContentParent ,接下來就重點看看generateLayout方法务嫡,因為setContentView的大部分內(nèi)容都在這里做了甲抖, Decorview需要填充自己的內(nèi)容布局有可能是LinearLayout或者FrameLayout,最后返回,將contentview復(fù)制給phonewindow的mContentParent然后通過ID_ANDROID_CONTENT獲取contentView心铃,代碼有點多准谚。
protected ViewGroup generateLayout(DecorView decor) {
//省略............很多代碼就看DecorView的布局選擇填充............
//填充窗口的裝飾
// Inflate the window decor.
int layoutResource;
//根據(jù)features選擇填充布局
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
//懸浮FloatButton
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
//layoutResource Decorview需要填充自己的內(nèi)容布局有可能是LinearLayout或者FrameLayout,然后將布局addView給自己
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//獲取真正的contenview
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgrsetyessBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks(contentParent);
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
別看代碼多實際上是根據(jù)features選擇填充布局去扣,這就是為什么我們要在setContentCiew之前調(diào)用requestWindowFeature的原因柱衔,先看看DecorView的直接子ViewGroup的布局長什么樣:
<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>
當然根ViewGroup在不僅僅是LinearLayout ,還有FrameLayout和其他的愉棱,這些沒什么好說的唆铐,就這說填充DecorView的布局吧, Decorview需要填充自己的內(nèi)容布局有可能是LinearLayout或者FrameLayout羽氮,然后調(diào)DecorViewon的ResourcesLoaded方法將選擇的布局addView給DecorView自己或链,然后通過findViewById方法找到contentParent,并返回賦值給PhoneWindow的mContentParent档押,接著回到PhoneWindow的setContentView中將我們從Activity的setContentView傳進來的layoutResID澳盐,填充到mContentParent,也就是DecorView的內(nèi)容ViewGroup中令宿,整個流程就完成了布局的填充叼耙。
最后看一下setContentView的時序圖:
小結(jié)
- Window是一個抽象類,提供了各種窗口操作的方法粒没,比如設(shè)置背景標題ContentView等等筛婉;
- PhoneWindow則是Window的唯一實現(xiàn)類,它里面實現(xiàn)了Window各種各種方法,添加背景主題ContentView等方法爽撒,內(nèi)部通過DecorView來添加頂級視圖
每一個Activity上面都有一個Window入蛆,可以通過getWindow獲取硕勿; - DecorView哨毁,頂級視圖,繼承與FramentLayout源武,setContentView則是添加在它里面的@id/content里
- setContentView里面創(chuàng)建了DecorView扼褪,根據(jù)Theme,F(xiàn)eature添加了對應(yīng)的布局文件粱栖,當setContentView設(shè)置顯示后會回調(diào)Activity的onContentChanged方法话浇;