本篇主要分爲3個部分:
1、通過官方文檔註釋,來理解Window、WindowManager;
2、通過分析Activity中setContentView()源碼,來理解Window在Activity中的工作流程;
3、通過分析Dialog源碼,來理解Window在Dialog中的工作流程;
源碼角度理解Window、WindowManager
Window相關的主要有以下幾個類、接口:
Window抽象類、Window.Callback接口,WindowManager接口、ViewManager接口、WindowManagerImpl實現類、WindowManagerGlobal類、ViewRootImpl類。
先把這幾個類的作用、特性、工作流程總結一下,捋一捋思路,然後再去分析每個類,這樣思路會更清晰。
1、Window表示一個窗口的概念,只有一個唯一實現類PhoneWindow,所有的能讓用戶看到的組件都是通過Window來展現的,Window規定了UI的展現方式、接收用戶的觸摸等交互,然後傳給各個組件;這些組件通過實現Window.Callback接口,就可以接受到Window的通知了;
2、Window持有一個WindowManager對象,該對象的主要作用就是幫助Window完成部分功能實現,比如添加View、刪除View(ViewManager定義了添加、刪除View,WindowManager繼承自ViewManager);
3、WindowManagerImpl就是WindowManager的具體實現(非唯一實現),大部分功能都在這裏完成。除了完成自己的本職工作外,WindowManagerImpl還要將Window和View的顯示通知給系統,所以這個類持有一個WindowManagerGlobal對象,該對象是單例、全局存在,的主要作用就是對Window和View的全局管理;
4、WindowManagerGlobal做兩件事,一件是添加/刪除/更新View(真正的執行者是ViewRootImpl),另一件就是通過List管理着所有View、ViewRootImpl、LayoutParamas等。而ViewRootImpl則通過performTraversals()發起View的繪製流程。
5、Activity通過new PhoneWindow()得到Window,通過getSystemService()得到WindowManager,通過mWindow.setWindowManager()方法使Window和WindowManager綁定,通過實現Window.Callback接口接收Window的通知。
Window
理解Window是什麼、幹什麼用的,只看Google官方註釋就已經明白了一大半。
/**重點內容
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
這個類是頂層視圖外觀和行爲策略的抽象基類,這個類的實例應該被添加在窗口管理器的頂層視圖中,它提供了標準的UI策略,規定UI的顯示、交互等準則;
這個抽象類的唯一實現是android.view.PhoneWindow,當你需要一個窗口時你應該實例化它。
Window.Callback
實現這個接口的類有下面這些:
Activity, ActivityGroup, AlertDialog, AliasActivity, CharacterPickerDialog, DatePickerDialog, Dialog, ExpandableListActivity, LauncherActivity, ListActivity, PreferenceActivity, ProgressDialog, TabActivity, TimePickerDialog 。
截取了Window.Callback的部分方法,如下:
public interface Callback {
public boolean dispatchKeyEvent(KeyEvent event);
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onMenuOpened(int featureId, Menu menu);
public void onContentChanged();
public void onWindowFocusChanged(boolean hasFocus);
public void onAttachedToWindow();
}
看這些方法的命名,就已經知道了一切。Window接收到用戶的交互操作,然後通過這個接口通知到Activity、Dialog。
WindowManager
看官方註釋:
* The interface that apps use to talk to the window manager.
* <p>
* Use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code> to get one of these.
* </p><p>
* Each window manager instance is bound to a particular {@link Display}.
* To obtain a {@link WindowManager} for a different display, use
* {@link Context#createDisplayContext} to obtain a {@link Context} for that
* display, then use <code>Context.getSystemService(Context.WINDOW_SERVICE)</code>
* to get the WindowManager.
* </p><p>
* The simplest way to show a window on another display is to create a
* {@link Presentation}. The presentation will automatically obtain a
* {@link WindowManager} and {@link Context} for that display.
這個接口是app用來與窗口管理器通信的;
通過Context.getSystemService()獲取實例;
每個窗口管理器實例都綁定一個特定的Display(即Activity、Dialog),要爲不同Display獲取WindowManager,請使用createDisplayContext()獲取該Display對應的Context,然後使用Context.getSystemService獲取WindowManager;
在Display上顯示window最簡單方法是創建一個Presentation, Presentation將自動獲得該Display的WindowManager和Context。
WindowManagerImpl
* Provides low-level communication with the system window manager for
* operations that are bound to a particular context, display or parent window.
* Instances of this object are sensitive to the compatibility info associated
* with the running application.
*
* This object implements the {@link ViewManager} interface,
* allowing you to add any View subclass as a top-level window on the screen.
* Additional window manager specific layout parameters are defined for
* control over how windows are displayed. It also implements the {@link WindowManager}
* interface, allowing you to control the displays attached to the device.
*
* <p>Applications will not normally use WindowManager directly, instead relying
* on the higher-level facilities in {@link android.app.Activity} and
* {@link android.app.Dialog}.
*
* <p>Even for low-level window manager access, it is almost never correct to use
* this class. For example, {@link android.app.Activity#getWindowManager}
* provides a window manager for adding windows that are associated with that
* activity -- the window manager will not normally allow you to add arbitrary
* windows that are not associated with an activity.
提供Context操作與系統window的最低級別通信,這個對象的實例與正在運行的應用高度相關;
該對象實現了ViewManager接口,允許您將任何View子類添加到屏幕上的頂層窗口中,佈局參數用來控制這些View如何顯示在窗口中。這個類也實現了WindowManager接口;
應用程序中通常不會直接使用WindowManager,而是使用Activity和Dialog;
即使對於低級別的窗口管理器訪問,直接使用這個類也是不正確的。比如,{@link android.app.Activity#getWindowManager}提供了一個窗口管理器,用於添加與該活動相關聯的窗口,但是,窗口管理器通常不會允許添加與活動無關的窗口。
這是直譯,我的理解是,Activity提供封裝好的方法,比如setContentView(),用於往窗口中添加View,但是不允許直接使用WindowManger對象來自行添加無關的View。
WindowManagerGlobal
這個類是單例、全局存在。
它做兩件事,一件是添加/刪除/更新View(真正的執行者是ViewRootImpl),另一件就是通過List管理着所有View、ViewRootImpl、LayoutParamas等。
* Provides low-level communication with the system window manager for
* operations that are not associated with any particular context.
*
* This class is only used internally to implement global functions where
* the caller already knows the display and relevant compatibility information
* for the operation. For most purposes, you should use {@link WindowManager} instead
* since it is bound to a context.
與特定Context無關的操作,通過這個類,可以和系統窗口管理器實現低級別的通信;
這個類只是在內部用來實現全局功能,大多數情況下,你應該使用WindowManger,因爲它綁定了特定的Context;
ViewRootImpl
Window不會直接管理View的具體繪製,而是委託ViewRootImlp類來管理,View的測量、佈局、繪製流程就是由這個類發起的(performTraversals()方法)。
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*/
視圖層次結構的頂部,在View和WindowManager之間實現所需的協議。 這大部分是 WindowManagerGlobal的內部實現細節。
Activity的setContentView()源碼分析
查看Activity的setContentView()方法,發現是通過Activity所綁定的Window執行的
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
至於這個getWindow(),網上、書上很多人的分析都是通過PolicyManager的makePhoneWindow()得到的,我看的源碼是6.0的,是在Activity生成的時候直接通過new PhoneWindow()得到的,不過這不影響。下面就分析下PhoneWindow的setContentView()流程。
@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) {
// 初始化mContentParent
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 {
// 把layoutResID佈局加載到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
邏輯很簡單,就是創建mContentParent,把id爲layoutResID的佈局文件加載進去。layoutResID就是我們創建的Activity對應的佈局文件id,下面要分析mContentParent是什麼。看下installDecor()部分源碼:
private void installDecor() {
if (mDecor == null) {
// 初始化mDecor
// 該方法中通過new DecorView(getContext(), -1)得到了DecorView對象,這是PhoneWindow中的一個內部類,繼承自FrameLayout
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
// 通過mDecor得到mContentParent
mContentParent = generateLayout(mDecor);
......
......
......
}
......
......
......
}
mDecor是一個FrameLayout,再看一下generateLayout()方法:
protected ViewGroup generateLayout(DecorView decor) {
......
......
......
// 定義佈局id,通過不同的特性(實際上就是主題、風格等),給layoutResource賦不同的值(加載不同的佈局)
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 if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
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();
//加載根佈局
View in = mLayoutInflater.inflate(layoutResource, null);
//把根佈局添加到decor容器中
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//找到根佈局中id爲ID_ANDROID_CONTENT的控件,ID_ANDROID_CONTENT的值爲R.id.content
// 前面根據不同主題風格加載了不同的根佈局,但是所有根佈局中,都有id爲R.id.content的控件(FrameLayout)
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
......
......
// 自此,根佈局已經添加到mDecor中了,根佈局中的contentParent已經初始化並返回
return contentParent;
}
mContentParent已經清楚了,就是Window的根佈局中,負責顯示內容的FrameLayout。按照前面的邏輯,我們創建的Activity對應的佈局文件,將被加載到這個FrameLayout中。
到目前爲止,layout文件已經添加到PhoneWindow的DecorView中了,但是這個DecorView並沒有在系統中“註冊”,系統也並沒有爲它分配資源、顯示等。
最後,在Activity的onResume()方法中,會調用Activity的makeVisible()方法:
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
這裏會調用WindowManagerImpl的addView()方法把DecorView添加到系統中,上面已經講過,addView()最終會由WindowManagerGlobal來執行,而這個類就是負責全局的window管理的。
總結一下:
1、創建Activity的同時,會創建一個PhoneWindow,同時也會創建一個WindowManagerImpl對象,並與PhoneWindow綁定;
2、調用Activity的setContentView()方法,會委託到PhoneWindow,讓PhoneWindow來添加layout文件。這就印證了上一塊講的,Window是用來添加View、顯示View、UI交互的;
3、PhoneWindow中,首先會根據不同的主題、風格等,加載不同的佈局,把這個佈局添加到DecorView中。但不管是哪種佈局,都會有一個id爲R.id.content的FrameLayout控件,初始化這個FrameLayout,把我們創建的layout文件添加到這個FrameLayout文件中;
4、最後由WindowManagerGlobal把DecorView添加到系統中,爲其分配顯示資源、分配交互事件。
Dialog中setContentView()源碼分析
先看Dialog的構造方法,執行邏輯在代碼註釋中,就不單獨分析了:
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
// 獲取WindowManagerImpl實例
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
// new一個PhoneWindow實例
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
// PhoneWindow綁定WindowManagerImpl
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
再看setContentView():
public void setContentView(@NonNull View view, @Nullable ViewGroup.LayoutParams params) {
mWindow.setContentView(view, params);
}
又進入了PhoneWindow的setContentView()方法,這個流程與Activity一樣,上面已經分析過了。
再看show()和dismiss()方法,dialog的顯示和隱藏是通過WindowManagerGlobal的addView()和removeView()來實現的,下面就直接在註釋中給出執行邏輯,不再單獨分析:
// show()方法
public void show() {
// 這是重複執行show()方法的判斷
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
if (!mCreated) {
dispatchOnCreate(null);
} else {
// Fill the DecorView in on any configuration changes that
// may have occured while it was removed from the WindowManager.
final Configuration config = mContext.getResources().getConfiguration();
mWindow.getDecorView().dispatchConfigurationChanged(config);
}
onStart();
// 獲取DecorView。因爲在dismiss()時,會把mDecor置空
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
// 通過WindowManagerGlobal把mDecor添加到系統中
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
}
// dismiss()方法
void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
if (mWindow.isDestroyed()) {
Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
return;
}
try {
// 通過WindowManagerGlobal移除mDecor
mWindowManager.removeViewImmediate(mDecor);
} finally {
if (mActionMode != null) {
mActionMode.finish();
}
// mDecor置空
mDecor = null;
mWindow.closeAllPanels();
onStop();
mShowing = false;
sendDismissMessage();
}
}