开发艺术之旅 | 理解Window和WindowManager
初识Window
Window
- 表示一个“窗口”的概念,用于展示给用户,View是其具体实现,Window是View的实际管理者
- 表示Window抽象类,具体实现是PhoneWindow,即Activity视图层级下我们看到的那个
WindowManager
外界访问Window的入口,继承于ViewManager,常用三个方法:
- public void addView(View view,ViewGroup.LayoutParams params);
- public void updateViewLayout(View view,ViewGroup.LayoutParams params);
- public void removeView(View view)
Window以及其内部机制
window有三类:
- 应用Window(Activity)
- 子Window(Dialog、Toast)
- 系统Window
在addView(View view,ViewGroup.LayoutParams params)方法,需要传入一个LayoutParams,除了常见的gravity、坐标还有两个参数很重要:
flags:常用的有三种:
- FLAG_NOT_FOCUSABLE 标明Window不需要获取焦点,事件会向下传递
- FLAG_NOT_TOUCH_MODAL 会将当前Window区域以外的单击事件传递给底层的Window
- FLAG_SHOW_WHEN_LOCKED Window显示在锁屏的界面上
type:设定Window的类型(Window的层级)
- 应用Window 1~99
- 子Window 1000~1999
- 系统Window 2000~2999
例如如果需要设置在所有Window的顶层可以设置 LayoutParams.TYPE_SYSTEM_ERROR 并且声明 SYSTEM_ALERT_WINDOW 权限
内部机制
每一个Window都对应一个View 和 ViewRootImpl,View是 Window存在的实体;
几个关键类关系如图:
WindowManagerGlobal
具体实现在这个类,当中维护了几个列表:
// 所有的View
private final ArrayList<View> mViews = new ArrayList<View>();
// 所有的Window对应的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots= new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams= new ArrayList<WindowManager.LayoutParams>();
// 正在被销毁的View
private final ArraySet<View> mDyingViews= new ArrayList<View>();
添加Window
Window的增删更新都是通过WindowManagerGlobal具体实现,在WindowManagerGlobal中添加View分为三步:
- 检查参数是否合法;调整子Window的布局参数
- 创建ViewRootImpl并将View添加到列表中
- 通过ViewRootImpl更新界面并完成Window的添加
流程如下图所示。可以看到最后是调用了WindowManagerService进行添加,也是进行一次IPC通信。
删除过程
也是通过WindowManagerGlobal进行具体的操作:
最终调用dispatchDetachedFromWindow方法,里面主要做了四件事:
- 垃圾回收、清除数据、消息、移除回调等
- 通过Session 的 remove方法删除Window:mWindowSession.remove(mWindow),同样也是一IPC过程,最后也会调用到WindowManagerService的removeWindow方法
- 调用View的dispatchDetachedFromWindow方法,内部会调用View的onDetachedFromWindow 以及 onDetachedFromWindowInternal
- 调用WindowManagerGlobal的doRemoveView方法刷新数据,包括删除mRoots、mParams、mDyingViews
更新过程
一样也是通过WindowManagerGlobal进行更新,调用他的updateViewLayout,主要逻辑
- 更新View的LayoutParams
- 更新ViewRootImpl中的LayoutParams(setLayoutParams)(在ViewRootImpl中会通过scheduleTraversals对View重新布局)
- 在ViewRootImpl通过WindowSession更新Window视图(IPC过程)
小结
- 可以发现正如开头梳理的关系那样,具体的实现流程都在WindowManagerGlobal
- 在WindowManagerGlobal中又通过ViewRootImpl操作View,因此ViewRootImpl是Window和View联系的桥梁
Window的创建
Activity的Window创建过程
- 需要了解Activity的启动流程,在ActivityThread中的performLaunchActivity创建Activity并关联上下文对象
- 通过PolicyManager(真正实现是Policy类)创建Window(实际创建的是PhoneWindow)
- 将Activity的视图和Window关联起来(从Activity.setContentView 到 PhoneWindow.setContentView)
- 经过 创建DecorView、添加contentView、通知Activity添加完成 三步完成DecorView的创建
- 通过Activity.makeVisiable 调用WindowManager将decorView添加并显示
具体流程图如下:
Dialog的Window创建过程
- 创建Window
- 初始化DecorView并将Dialog的视图添加到DecorView
- 将DecorView添加到Window并显示
- 创建Window和Activity的类似,也是通过PolicyManager的makeNewWindow创建一个PhoneWindow对象。
- 和Activity的类似,也是通过Window去加载指定文件
- 在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中
注意点:
- 当关闭Dialog时会调用WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)
- 必须采用Activity的context,否则会报错,除非设定为系统的Window:dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR) 并且声明权限
Toast的Window创建
基础信息
- 属于系统Window
- 有定时消失功能所以内部包含一个Handler
- 两个IPC过程:Toast访问NMS(NotificationManagerService)和 NMS回调Toast里的TN接口
TN类
- TN是个Binder类
- 跨进程IPC,所以TN类的show、hide方法运行在Binder线程池中(因此通过Handler切换回调用线程)
- Handler会绑定调用的线程,因此在非looper线程Toast不显示;TN回调show、hide方法会通过Handler发送一个消息切换线程,实际执行的是handleShow、handleHide方法
两个操作:
- show
- cancel
均为IPC过程(与NMS通信,NMS是系统服务)
显示View
- 默认样式
- 通过setView设定
show显示过程
Toast.show方法
public void show() {
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
实际过程:
- 将TN对象、包名、显示时间传入 service.enqueueToast,enqueue会将这些信息封装成一个ToastRecord对象并添加到一个名为mToastQueue的队列中;
- NMS会通过showNextToastRecord显示当前的Toast,然后由ToastRecord.callback.show(callback就是TN的远程Binder)显示,最终会回调到TN中的show方法;
- 显示后会发送一个延时消息通知隐藏Toast,也是通过ToastRecord.callback完成(隐藏Toast并且从队列中移除,然后显示下一个Toast)
- 在TN类的实际实现方法handleShow会将Toast的视图添加到Window;handlerHide会将Toast视图从Window移除
过程示意图
小结
- Toast.show方法会将信息交给NMS,NMS会封装成ToastRecord并管理一个ToastRecord队列,并进行对这个消息队列进行管理
- Toast实际的显示会通过远程调用执行Toast里面的TN类中的方法
推荐参考:厘米姑娘的Window笔记 最后的Toast流程图总结得很好
总结
- 任何一个View都是附属在Window上的,View的事件分发机制也是从Window分发到DecorView的,Window是View的实际管理者
- Window是一个抽象概念,每个Window都有对应的View和ViewRootImpl
- 我们对Window的访问只能通过WindowManager,而WindowManager最终会通过WindowSession调用WindowManagerService对Window进行处理
关于Window和WindowManager的内部机制
- Window有三类:应用Window、子Window、系统Window,并且有对应的层级,可以设置
- WindowManager具体实现是WindowManagerImpl,但是他把具体操作交给WindowManagerGlobal,WindowGlobal又会通过WindowSession调用WindowManagerService完成对Window(具体体现是View)的管理
- Window的增、删、改都是:通过WindowManagerImpl获取到WindowManagerGlobal,然后WindowManagerGlobal又会 找到/创建 ViewRootImpl,再通过ViewRootImpl调用WindowManagerService对Window进行操作(IPC过程)
关于Window的创建
- PolicyManager通过makeNewWindow创建一个PhoneWindow对象(Window的实现类)
- Activity与Window关联:
- 在Activity的setContentView会调用Window的setContentView;
- 在这个方法会创建DecorView并添加ContentView;
- 完成后会调用Activity的makeVisiable显示窗口
- Toast:
- 属于系统Window;
- Toast内部有个TN类,是一个Binder类;
- 当调用Toast的show方法,会将信息发送给NMS“入队”,NMS内部管理一个ToastRecord队列,按顺序显示,会回调到TN内的方法;
- TN回调到的接口方法是在Binder线程池中,因此通过Handler切换到调用的线程,再调用WindowManager 添加/删除View
文章链接
深入了解WMS,推荐刘望舒的解析WindowManager系列