一、概述
Window是一个窗口的概念,当我们需要一个悬浮窗时,就用到了Window。
Activity、Dialog、Toast等都是通过一个Window来实现视图的展示的。
添加一个Window,其实就是通过WindowManager添加一个View,然后它会和WindowManagerService进行IPC,然后使添加的这个View形成一个Window窗口。所以Window是View的直接管理者。
Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View的形式存在。
我理解其实Window有三种概念:
1、通过WindowSession向远程WindowManagerService添加Window时传递的Binder——IWindow.Stub,它会向远程提供View(因为它内部持有ViewRootImpl,而ViewRootImpl持有View),并会对远程的Window事件作出本地的响应
2、在远程真正实现的显示在屏幕上的窗口
3、本地的抽象类Window类,它唯一的实现类是PhoneWindow,它的作用是初始化一个DecorView,并将Activity的视图放入DecorView中。并且用它来回调Activity相应的方法。
二、添加Window的一个简单示例
下面代码可以将一个Button添加到屏幕坐标为(100,300)的位置上
WindowManager继承自ViewManager
最终会分别调用WindowManagerService的addWindow()、removeWindow()、relayoutWindow()
三、Window的几个重要的参数
WindowManager.LayoutParams中的flags和type参数、Window层级z-ordered概念:
flags:
type:
层级z-orderde:
四、Window内部机制
添加Window
WindowManager是一个接口,实现类是WindowManagerImpl,WindowManagerImpl又委托给
WindowManagerGlobal。
1、WindowManagerGlobal在addView()时通过如下方式将Window的一系列对象添加到列表中
2、调用ViewRootImpl的setView()方法
1)将对应View的引用存起来
2)完成View的绘制。在setView()内部会通过requestLayout()来完成异步刷新请求,里面的
scheduleTraversals()实际是View绘制的入口
3)接着会通过WindowSession最终来完成Window的添加过程。在下面代码中,mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次
IPC调用。
上面的mWindow在ViewRootImpl中的定义是这样的
它是在ViewRootImpl的构造方法里被初始化的
另:
1)W的定义如下。
W里有ViewRootImpl的虚引用,这样将W传到WindowManagerService里后,WindowManagerService就有了ViewRootImpl,而ViewRootImpl里又有View,所以WindowManagerService也就有了View,也就可以将这个View形成一个窗口。
同时W还是一个Binder类,这样当在别的进程中的窗口有什么事件时,可以通过这个Binder通知到我们的进程处理,W会交给它的ViewRootImpl处理
2)所以ViewRootImpl有如下功能
a、在添加Window的时候,View对应的ViewRootImpl被初始化
b、通过它完成View的绘制
c、通过它向WindowManagerService请求添加Window
d、通过它将窗口对应的View传给WindowManagerService
c、通过它来响应处理远程窗口的事件
3、在Session内部会通过WindowManagerService来实现Window的添加
删除Window
WindowManagerGlobal会通过ViewRootImpl删除相应Window
1、调用ViewRootImpl的die()方法。
有异步删除和同步删除。一般不用同步删除以免发生意外的错误。
2、在doDie()中会调用dispatchDetachedFromWindow(),真正的删除View的逻辑都在它中实现,主要做了4件事:
更新Window
五、Window的创建过程
Acitivity的Window创建过程
1、Acitivity启动完成后最初:初始化Activity实例和它所关联的Window对象(PhoneWindow)
Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程。在这个方法里会通过类加载器创建Acitivity的实例对象,并调用其attach()为其关联运行过程中所依赖的一系列上下文环境变量。
在attach()里,系统会创建Activity所属的Window对象并为其设置回调接口(Acitivity就实现了这个接口)
PolicyManager是一个策略类,它实现的方法全在IPolicy中声明了,如下:
PolicyManager的真正实现类是Policy,Policy中makeNewWindow()的实现如下:
2、将Activity的视图附属到它所关联的Window上(其实Window内部有一个DecorView成员,就是初始化这个DecorView,并将视图放到这个DecorView里)
Activity的视图由setContentView()提供,如下
所以接下来我们看PhoneWindow的setContentView(),里面有如下步骤:
1)初始化一个DecorView(FrameLayout)
2)一般来说包含标题栏和内容栏,依据主题的不同,加载不同的布局结构,将这个布局结构的View添加到DecorView里
3)在上面添加的View中找到id为“com.android.internal.R.id.content”的View(它是一个FrameLayout,系统会把它赋值到名为mContentParent的View变量中,这个名字很形象)
4)将Activity的视图inflate成一个View,并将它添加到上述的mContentParent中。(这就是setContentView方法名字的来历,为什么不叫setView呢)
至此Activity的视图已经添加到DecorView里了,也就是添加到Acitivity所附属的Window里了
5)由于Acitivity实现了Window的Callback接口,所以这里会回调Activity的onContentChanged(),表示Activity的布局文件已经添加进了DecorView的mContentParent中了
3、在ActivityThread的handleResumeActivity方法中,首先会调用Activity的onResume方法,接着会调用Activity的makeVisible(),正是在makeVisible()里,DecorView真正完成了添加和显示这两个过程,到这里Activity的视图才能被用户看见。如下:
Dialog的Window创建过程
Dialog的Window创建过程和Activity类似
1、创建Dialog所关联的Window对象
2、将Dialog的视图附属到它所关联的Window对象中
3、在Dialog的show()方法中,会通过WindowManager远程添加窗口
注:
普通Dialog有一个特殊之处,那就是必须采用Activity的Context,如果采用Application的Context,那么就会报错。
上述代码会报错,如下:
1、上述错误信息很明确,是没有应用token所导致的,而应用token一般只有Activity拥有,所以这里只需要用Activity作为Context显示对话框即可。
2、系统Window比较特殊,它不需要token,因此上述例子只需要指定对话框的Window为系统Window即可正常显示。如下:
还要在AndroidManifest文件中声明权限从而可以使用系统Window,如下:
Toast的Window创建过程
1、向远程的NotificationManagerService发送请求,在远程服务里将Toast排队
Toast属于系统Window。它的视图有两种:一种是系统默认的视图,是在makeText()方法里初始化的;一种是通过setView()放入的。不管如何,它们都对应这Toast的一个成员变量mNextView。
在Toast的构造方法中会初始化它的一个成员变量TN,TN的定义如下,它是一个Binder类。TN也有一个成员变量mNextView
Toast的show()方法:
2、在远程的NotificationManagerService中对Toast请求的处理
enqueueToast()会将Toast请求封装成ToastRecord,并将其添加进mToastQueue队列中。mToastQueue最多只能同时存在50个非系统应用的ToastRecord。
3、在远程的NotificationManagerService中执行显示Toast的逻辑,以及对这个Toast的定时取消
NMS会通过showNextToastLocked()来显示当前轮到显示的Toast
4、客户端TN对NotificationManagerService远程调用show()的处理