博文出處:Window源碼解析(一):與DecorView的那些事,歡迎大家關注我的博客,謝謝!
注:本文解析的源碼基于 API 25,部分內容來自于《Android開發藝術探索》。
Header
今天我們來講講 Window ,Window 代表著一個窗口。
比如在 Activity 中,我們可以設置自定義的視圖 View ,其實 View 并不是直接附著在 Activity 上,而是 View 附著在 Window 上,Activity 又持有一個 Window 對象。可見,Window 是一個重要的角色,主要用來負責管理 View 的。而 Window 和 View 又是通過 ViewRootImpl 來建立聯系的,這在之前的《View的工作原理》中介紹過。
所以一個 Window 就對應著一個 View 和一個 ViewRootImpl 。
同理,Dialog 和 Toast 等的視圖也都是附著在 Window 上。
除此之外,相信看過《Android開發藝術探索》的同學都知道。Window 有三種類型,分別對應著:
- 應用 Window ,即 Activity 的 Window 。對應的 type 為1~99;
- 子 Window ,比如 Dialog 的 Window ,子 Window 并不能單獨存在,需要有父 Window 的支持。對應的 type 為1000~1999;
- 系統 Window ,需要權限聲明才可以創建,比如常用的 Toast 和狀態欄等都是系統級別的 Window。對應的 type 為2000~2999;
這三種 Window 的區分方法就是依靠 WindowManager.LayoutParams 中的 type 來決定的。type 越大,Window 就越顯示在層級頂部。
粗看有這么多知識點,所以我們確實有必要對 Window 好好深入了解一下。在這,我們先詳細介紹一下 Window 和 Activity 的那些“糾葛”,然后再深入 Window 的內部機制。
初見Window
Activity
attach(Context context, ActivityThread aThread, ...)
Window 第一次出現在 Activity 的視野中,是在 Activity 的 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, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 創建 window 對象
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
// 設置回調,用來回調接收觸摸、按鍵等事件
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
...
// 設置窗口管理器,其實是創建了 WindowManagerImpl 對象
// WindowManager 是接口,而 WindowManagerImpl 是 WindowManger 的實現類
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());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
在方法中創建了一個 PhoneWindow 對象,而 PhoneWindow 其實就是 Window 的具體實現類,Window 只是一個接口而已。之后設置了回調,這樣當 Window 接收到觸摸或者按鍵等事件后,會回調給 Activity 。
另外還給 Window 對象設置了窗口管理器,也就是我們經常用到的 WindowManager 。
WindowManager 是外界接觸 Window 的入口,也就是說,想要對 Window 進行一些操作需要用過 WindowManager 來完成。
與DecorView的那些事
在開頭中說到,Window 是用來負責管理 View 的。
現在 Window 已經創建完畢了,那么到底什么時候與 View 發生了交集了呢?
我們需要深入到 onCreate()
中一個熟悉的方法: setContentView(R.layout.activity_main)
。
Activity
setContentView(@LayoutRes int layoutResID)
public void setContentView(@LayoutRes int layoutResID) {
// 這里 getWindow 得到的正是上面創建的 PhoneWindow 對象
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
發現它調用的是 Window 中的同名方法。
接著到 PhoneWindow 中跟進,查看具體實現的邏輯。
PhoneWindow
setContentView(int layoutResID)
@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.
// mContentParent 是放置窗口內容的父 viewgroup ,可能是 decorView 本身,也有可能是它的子 viewgroup
// 如果 mContentParent 是空的,那么就說明 decorView 是空的
if (mContentParent == null) {
// 創建 decorview
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 {
// 將 layout 布局加入到 mContentParent 中并去解析 layout xml 文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
// 通知 activity 窗口內容已經發生變化了
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
在 setContentView(int layoutResID)
中,一開始判斷了 mContentParent 。mContentParent 其實就是我們設置的 contentView 的父視圖。
關于 mContentParent ,在 PhoneWindow 中有注釋:
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
意思就是說,當我們不需要 titlebar 的時候,mContentParent 其實就和 DecorView 一樣了;有 titlebar 的時候,DecorView 的內容就分為了 titlebar 和 mContentParent 。
所以如果 mContentParent 為空,那么可以說明還沒有創建過 DecorView 。
我們總結一下,在 setContentView(int layoutResID)
中主要就是這三件事:
- 創建 DecorView 視圖對象;
- 將自定義的視圖 layout_main.xml 進行解析并添加到 mContentParent 中;
- 去通知 activity 窗口視圖已經改變了,進行相關操作;
我們去 installDecor()
中看看究竟怎么創建 DecorView 的。
installDecor()
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 如果 decorview 為空,調用 generateDecor 來創建 decorview
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 創建 mContentParent ,也就是 contentView 的父視圖
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);
...
}
}
}
在 installDecor()
中,調用了 generateDecor()
方法來創建 DecorView;
之后又調用 generateLayout(mDecor)
來創建 mContentParent 。
generateDecor(int featureId)
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
// 得到 context 上下文
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
// 創建 DecorView 對象
return new DecorView(context, featureId, this, getAttributes());
}
generateDecor(int featureId)
方法比較簡單,之前初始化了一下 context ,然后直接 new 了一個 DecorView 完事!
generateLayout(DecorView decor)
protected ViewGroup generateLayout(DecorView decor) {
// 應用當前的主題,比如設置一些 window 屬性等
...
// 根據主題設置去選擇 layoutResource
// 這個 layoutResource 也就是 DecorView 的子 View 的布局
int layoutResource;
int features = getLocalFeatures();
...
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 的布局轉換并添加到 DecorVew 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 得到 contentParent(id = android.R.id.content), 也就是我們 setContentView 的父視圖
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) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// 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;
}
這個方法中大致的邏輯就是,根據主題的設置情況來選擇 DecorView 子 View 的 layoutResource 。在這,我們就看看最常用的一種布局 R.layout.screen_title (位于 /frameworks/base/core/res/res/layout/screen_title.xml ):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- Popout bar for action modes -->
<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:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
我們可以看到,DecorView 的子 View 其實是一個 LinearLayout ,而 LinearLayout 中有分為 titlebar 和 id 為 android:id/content 的 FrameLayout(其實就是 mContentParent)。
之后將這個視圖創建出來并添加到 DecorView 中。
具體的代碼可以深入 DecorView 的 onResourcesLoaded(LayoutInflater inflater, int layoutResource)
中去看:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
// 解析之前選擇出來的 layoutResource ,該 root 也就是 DecorView 的直接子 View
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
// 將 root 視圖添加到 DecorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 可以看出成員變量 mContentRoot 就是 DecorView 的直接子 View
// 也就是 mContentParent 的父視圖
mContentRoot = (ViewGroup) root;
initializeElevation();
}
看到這,我們可以畫一張圖出來了,把 PhoneWindow 、DecorView 和 mContentParent 都理清楚:
然后進行標題設置之類的工作。最后得到并返回 mContentParent 。
到了這里,基本上把 Window 、DecorView 和 Activity 三者之間的關系整理清楚了,但是事情并沒有結束。這時候的 DecorView 并沒有真正添加到 Window 上去,只是創建出對象了并解析了視圖而已。DecorView 還沒有被 WindowManager 識別,Window 也還無法接受外界的輸入信息。
那么,到底 DecorView 是什么時候附著到 Window 上去的?
這個答案需要我們到 ActivityThread 的 handleResumeActivity()
中找找了。回調 Activity 的 onResume()
生命周期后,又調用了 Activity 的 makeVisible()
方法。
Activity
makeVisible()
void makeVisible() {
if (!mWindowAdded) {
// WindowManager 是 ViewManager 的實現類
ViewManager wm = getWindowManager();
// 將 decorview 添加到 window 中
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
// 設置 decorview 可見
mDecor.setVisibility(View.VISIBLE);
}
走完這步,DecorView 才完成添加和顯示出來,Activity 的視圖才能被用戶看到。
整個 Window 創建的流程也結束了。
Footer
Window 和 Decor 的“愛恨情仇”到這里就告一段落了,但是 Window 的內部機制我們還可以好好敘一敘。
注意到上面 WindowManager 的 addView
方法了吧?
Window 是怎么添加上去的,究竟在這里面發生了什么事呢?
只能留到下一篇再詳細講講了。
bye bye !