Window是一个抽象类,它的具体实现是PhoneWindow。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际是View的直接管理者。
为了分析Window的工作机制,先通过代码了解如何使用WindowManager添加一个Window,下面一段代码将一个Button添加到屏幕坐标为(100, 300)的位置上
mFloatingButton = new Button(this);
mFloatingButton.setText("test button");
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);//0,0 分别是type和flags参数
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mFloatingButton.setOnTouchListener(this);
mWindowManager.addView(mFloatingButton, mLayoutParams);
Flags
参数表示Window的属性,以下列举常用的选项:
FLAG_NOT_FOCUSABLE
:表示Window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启动FLAG_NOT_TOUCH_MODEL,最终事件会传递给下层的具有焦点的WindowFLAG_NOT_TOUCH_MODAL
:在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件。FLAG_SHOW_WHEN_LOCKED
:开启此模式可以让显示在锁屏的界面Type
参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。
Window是分层的,每个Window都有对应的z-ordered,层级最大的会覆盖在层级小的Window上面,这和HTML中的z-index的概念是完全一致的。在三类Window中,应用Window的层级范围是199,子Window的层级范围是10001999,系统Window的层级范围是2000~2999,这些层级属性范围对应着
WindowManager.LayoutParams
的type
参数。
如果采用TYPE_SYSTEM_ERROR
,只需要为type
参数指定这个层级即可:
mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR
同时声明权限:
WindowManager
所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager。
WindowManager
操作Window的过程更像是在操作Window中的View
Window是一个抽象的概念,并不是实际存在的,它是以View的形式存在,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系。在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager。
Window的添加过程需要通过WindowManager
的addView()
来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl
类。WindowManager的实现类对于addView()
、updateView()
和removeView()
方法都是委托给WindowManagerGlobal
类。
WindowManagerGlobal
的addView()方法分为如下几步:
ViewRootImpl
并将View添加到列表中。Window对应的View, ViewRootImpl和待删除的View对象都有对应的列表。ViewRootImpl
的setView()
来更新界面(View的绘制由ViewRootImpl完成),setView()
中通过requestLayout()
完成异步刷新请求WindowSession
来添加Window,添加过程的本质是一个IPC过程,其中用到了Binder对象IWindowSession(实现类Session),实际添加是交给WindowManagerService
去处理和添加过程一样,都是先通过WindowManagerImpl
后,再进一步通过WindowManagerGlobal
来实现的↓
调用removeView()
,其中先找到待删除的View索引,然后调用removeViewLocked()
->ViewRootImpl
,有同步删除和异步删除,在异步删除中,就会发送一个信息,放进刚刚ViewRootImpl中的待删除View的列表。
真正删除View的逻辑在dispatchDetachedFromWindow()
方法的内部实现。主要做四件事:
调用WindowManagerGlobal 的updateViewLayout()
:
首先需要更新View的LayoutParams并替换掉老的LayoutParams,接着再更新ViewRootImpl
中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams()
方法来实现的。在ViewRootImpl中会通过scheduleTrversals方法来对View重新布局,包括测量、布局、重绘三个过程。除了View本身的重绘以外,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的,同样是一个IPC过程。
1、Activity的启动过程很复杂,最终会由ActivityThread
中的performLaunchActivity()
来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach()
方法为其关联运行过程中所依赖的一系列上下文环境变量。
在attach()中,系统会创建所属的Window对象并设置回调接口。
Window对象的创建是通过
PolicyManager
的makeNewWindow()
实现
2、Activity实现了Window的Callback接口,当Window接收到外界的状态变化时就会调用Activity的方法,例如onAttachedToWindow()
、onDetachedFromWindow()
、dispatchTouchEvent()
等。
3、Activity的Window是由PolicyManager
来创建的 - > 真正实现是Policy
类,它会新建一个PhoneWindow
对象,Activity的setContentView()
的实现是由PhoneWindow来实现的/
PhoneWindow的
setContentView()
方法大致遵循如下几个步骤:
- 如果没有
DecorView
(FrameLayout,顶级View,包含内容和标题栏),那么就创建它,通过gernerateLayout()
加载具体的布局文件。- 将View添加到DecorView的
mContentParent
中,- 回调Activity的
onCreateChanged()
方法通知Activity视图已经发生改变
Window更多表示的是一种抽象的功能集合…
Dialog的Window的创建过程和Activity类似,有如下步骤:
makeNewWindow()
方法来完成的,创建后的对象实际上就是PhoneWindow。show()
,将DecorView添加到Window中并显示, 也与Activity类似。系统Window比较特殊,不需要token,系统Window的层级范围type: 2000~2999,可以指定Dialog的Window类型为系统Window。
dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR);
//要声明权限
NotificationManagerService
,第二类是NotificationManagerService
回调Toast里的TN接口。show()
和隐藏cancel()
是IPC过程,都需要NotificationManagerService(NMS)来实现,在Toast和NMS进行IPC过程时,NMS会跨进程回调Toast中的TN类中的方法,TN类是一个Binder类,运行在Binder线程池中,所以需要通过Handler将其切换到当前发送Toast请求所在的线程,因为使用了Handler,所以Toast无法在没有Looper的线程中弹出。对于非系统应用来说,mToastQueue最多能同时存在50个ToastRecord(应用的mToastQueue队列最多只能存在50个),这样做是为了防止DOS(Denial of Service,拒绝服务)。因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。
Toast的显示是通过ToastRecord的callback来完成,callback -> Tn对象的远程Binder,需要跨进程,会运行在Toast的应用的Binder线程池中。
并且会发送一个延时消息,时长取决于Toast的持续时长,用来调用cancelToastLocked
来隐藏Toast,并且从队列中移除,然后继续显示队列中的Toast
Toast的显示和隐藏实际上通过Toast的
TN
类中的两个Runnable -->handleShow()
和handleHide()
方法,用于将Toast的视图从Window添加或者移除。