本篇主要分为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();
}
}