第八章: 所有的视图都是Windown呈现的, 那它都干了什么?
Window表示一个窗口的概念, 如有需要在桌面上显示一个类似悬浮窗的东西, 那么这种效果就需要Window
来实现. Window
是一个抽象类, 具体实现是PhoneWindow
. 如果想要创建一个Window
只需要通过WindowManager
即可完成. WindowManager
是外界访问Window
的入口, Window具体实现位于WindowManagerService
中, WM和WMS的交互是一个IPC过程. Android中所有的视图都是通过Window
来呈现的, 不管是Activity, Dialog, Toast他们的视图实际上都是附加在Window上的.
Window和WindowManager
先演示使用WindowManger
添加一个Window.
|
Flag参数表示Window的属性,这些属性可以控制Window的显示特性. 下面是常用的属性:
FLAG_NOT_FOCUSABLE
: 表示Window不需要获取焦点, 而不需要接收各种输入事件, 此标记会同时启动FLAG_NOT_TOUCH_MODAL
, 最终事件会直接传递给下层的具有焦点的Window.FLAG_NOT_TOUCH_MODAL
: 这种模式下, 系统会将当前Window区域以外的点击事件传递给底层的Window, 当前Window区域以内的单击事件则自己处理. 这个标记很重要, 一般来说都需要开启此标记, 否则其他Window将无法接收到单击事件.FLAG_SHOW_WHEN_LOCKED
: 开启此模式可以让Window显示在锁屏的界面上.
Type参数表示Window的类型
Window共有三种类型, 分别是应用Window, 子Window, 系统Window. 应用类Window对应着一个Activity. 子Window不能单独存在, 他需要附属在特定的父Window中,比如常见的Dialog
就是一个子Window. 系统Window是需要声明权限才能创建的Window, 比如Toast
和系统状态栏都是系统的Window.
Window是分层的, 每个Window都有对应的z-ordered
, 层级大的会覆盖在层级小的Window的上面, 这和HTML中的z-index
的概念一样. 应用Window的层级范围是1~99, 子Window的层级范围是1000~1999, 系统Window的层级范围是2000~2999.
如果想要在最顶层显示, 可以选择使用TYPE_SYSTEM_OVERLAY
, TYPE_SYSTEM_ERROR
. 如果采用了TYPE_SYSTEM_ERROR
同时要声明权限android.permission.SYSTEM_ALERT_WINDOW
. 如果不声明那么在创建的时候就会报错.
WindowManager常用的功能
在ViewManager
接口中定义了三个方法. 就是我们常用的方法添加View,删除View,修改View. WM
继承了这个接口.
Window的内部机制
Window
是一个抽象的概念, 每一Window
都对应着一个View和一个ViewRootImpl, Window
和View通过ViewRootImpl
来建立联系, 因此Window并不是实际存在的, 他是以View的形式存在. 通过WindowManager
的定义和提供的三个接口方法看出都是针对View的. 说明View才是Windwo
存在的实体. 而在实际的使用中无法直接访问Window
, 对Window
的访问都是必须通过WM.
Window的添加过程
Window
的添加过程需要通过WindowManager
的addView()
来实现, 而WindowManager
是一个接口, 它的真正实现是WindowManagerImpl
类, 在WindowManagerImpl中Window的三大操作如下.
|
WindowManagerImpl
并没有直接实现Window的三大操作, 而是全部交给了WindowManagerGlobal
来处理. WindowManagerGlobal
以工厂的形式向外提供自己的实例. 而WindowManagerImpl
这种工作模式就典型的桥接模式, 将所有的操作全部委托给WindowManagerGlobal
来实现.
WindowManagerGlobal的addView()主要分为
- 检查所有参数是否合法, 如果是子Window那么还需要调整一些布局参数.
- 创建
ViewRootImpl
并将View添加到列表中. - 通过
ViewRootImpl
来更新界面并完成Window的添加过程. 这个过程是通过ViewRootImpl#setView()
来完成的. View的绘制过程是由ViewRootImpl
来完成的, 在内部会调用requestLayout()
来完成异步刷新请求. 而scheduleTraversals()
实际上是View绘制的入口. 接着会通过WindowSession
完成Window的添加过程(Window的添加过程是一次IPC调用). 最终会通过WindowManagerService
来实现Window的添加.
WindowManagerService
内部会为每一个应用保留一个单独的Session.
Window的删除过程
Window 的删除过程和添加过程一样, 都是先通过WindowManagerImpl
后, 在进一步通过WindowManagerGlobal的removeView()
来实现的.
|
方法内首先通过findViewLocked
来查找待删除的View的索引, 这个过程就是建立数组遍历, 然后调用removeViewLocked
来做进一步的删除.
|
这里通过ViewRootImpl
的die()
完成来完成删除操作. die()
方法只是发送了请求删除的消息后就立刻返回了, 这个时候View并没有完成删除操作, 所以最后会将其添加到mDyingViews
中, mDyingViews
表示待删除的View的列表.
|
die方法中只是做了简单的判断, 如果是异步删除那么就发送一个MSG_DIE
的消息, ViewRootImpl
中的Handler
会处理此消息并调用doDie()
; 如果是同步删除, 那么就不发送消息直接调用doDie()
方法.
在doDie()
方法中会调用dispatchDetachedFromWindow()
方法, 真正删除View的逻辑在这个方法内部实现. 其中主要做了四件事:
- 垃圾回收的相关工作, 比如清除数据和消息,移除回调.
- 通过Session的remove方法删除Window:
mWindowSession.remove(mWindow)
, 这同样是一个IPC过程, 最终会调用WMS
的removeWindow()方法. - 调用View的
dispatchDetachedFromWindow()
方法, 内部会调用View的onDetachedFromWindow()
以及onDetachedFromWindowInternal()
. 而对于onDetachedFromWindow()
就是在View从Window中移除时, 这个方法就会被调用, 可以在这个方法内部做一些资源回收的工作. 比如停止动画,停止线程 - 调用
WindowManagerGlobal#doRemoveView
方法刷新数据, 包括mRoots
,mParams
,mDyingViews
, 需要将当前Window所关联的这三类对象从列表中删除.
Window的更新过程
WindowManagerGlobal#updateViewLayout()
方法做的比较简单, 它需要更新View的LayoutParams
并替换掉老的LayoutParams
, 接着在更新ViewRootImpl
中的LayoutParams. 这一步主要是通过setLayoutParams()
方法实现.
在ViewRootImpl
中会通过scheduleTraversals()
来对View重新布局, 包括测量,布局,重绘. 除了View本身的重绘以外, ViewRootImpl
还会通过WindowSession
来更新Window的视图, 这个过程最后由WMS
的relayoutWindow()
实现同样是一个IPC过程.
Window的创建过程
View是Android中视图的呈现方式, 但是View不能单独存在, 它必须依附在Window这个抽象的概念上面, 因此有视图的地方就有Window
.
Activity的Window创建过程
Activity
的大体启动流程: 最终会由ActivityThread
中的PerformLaunchActivity()
来完成整个启动过程, 这个方法内部会通过类加载器创建Activity
的实例对象, 并调用其attach()
方法为其关联运行过程中所依赖的一系列上下文环境变量. 代码如下:
|
在attach()
方法里, 系统会创建Activity
所属的Window对象并为其设置回调接口, Window对象的创建是通过PolicyManager#makeNewWindow()
方法实现. 由于Activity
实现了Window
的CallBack接口, 因此当Window
接收到外界的状态改变的时候就会回调Activity方法. 比如说我们熟悉的onAttachedToWindow()
, onDetachedFromWindow()
, dispatchTouchEvent()
等等.代码如下
|
那么Activity视图是怎么附属在Window上的呢? 查看经常使用的setContentView()
方法干了什么
|
Activity
将具体实现交给了Window处理, 而Window
的具体实现就是PhoneWindow
, 所以只需要看PhoneWindow
的相关逻辑分为以下几步.
- 如果没有DecorView, 那么就创建它. 由
installDecor()-->generateDecor()
触发 - 将View添加到
DecorView
的mContentParent
中 - 回调Activity的
onContentChanged()
通知activity视图已经发生改变
这个时候DecorView
已经被创建并初始化完毕, Activity的布局文件也已经添加成功到DecorView的mContentParent
中. 但是这个时候DecorView
还没有被WindowManager
正式添加到Window中. 虽然早在Activity的attach
方法中window就已经被创建了, 但是这个时候由于DecorView
并没有被WindowManager
识别, 所以这个时候的Window无法提供具体功能, 因为他还无法接收外界的输入信息.
在ActivityThread#handleResumeActivity()
方法中, 首先会调用Activity#onResume()
, 接着会调用Activity#makeVisible()
, 正是在makeVisible方法中, DecorView
真正的完成了添加和显示这两个过程. 如下:
|
Dialog的Window创建过程
Dialog
的Window
的创建过程和Activity类似, 有如下几步
1. 创建Window
Dialog的创建后的实际就是PhoneWindow
, 这个过程和Activity的Window创建过程一致.
2. 初始化DecorView并将Dialog的视图添加到DecorView中
这个过程也类似, 都是通过Window
去添加指定的布局文件.
3. 将DecorView添加到Window中并显示
在Dialog
的show方法中, 会通过WindowManager
将DecorView
添加Window中.
|
普通的Dialog
有一个特殊之处, 那就是必须采用Activity
的Content, 如果采用Application
的Content, 那么就会报错. 报的错是没有应用token所导致的, 而应用token一般只有Activity才拥有.
还有一种方法. 系统Window比较特殊, 他可以不需要token
, 因此只需要指定对话框的Window为系统类型就可以正常弹出对话框.
|
Toast的Window创建过程
Toast
和Dialog
不同, 它的工作过程就稍显复杂. 首先Toast
也是基于Window来实现的. 但是由于Toast
具有定时取消的功能, 所以系统采用了Handler
. 在Toast
的内部有两类IPC过程, 第一类是Toast访问NotificationManagerService()
后面简称NMS
. 第二类是NotificationManagerService
回调Toast里的TN
接口.
Toast
属于系统Window, 它内部的视图有两种方式指定, 一种是系统默认的样式, 另一种是通过setView
方法来指定一个自定义View. 不管如何, 他们都对应Toast
的一个View类型的内部成员mNextView
. Toast内部提供了cancel
和show
两个方法. 分别用于显示和隐藏Toast
. 他们内部是一个IPC过程.
显示和隐藏Toast
都是需要通过NMS
来实现的. 由于NMS
运行在系统的进程中, 所以只能通过远程调用的方式来显示和隐藏Toast
. 而TN
这个类是一个Binder
类. 在Toast
和NMS
进行IPC的过程中, 当NMS处理Toast的显示或隐藏请求时会跨进程回调TN
的方法. 这个时候由于TN
运行在Binder线程池中, 所以需要通过Handler
将其切换到当前主线程. 所以由其可知, Toast
无法在没有Looper
的线程中弹出, 因为Handler需要使用Looper
才能完成切换线程的功能.
|
对于非系统应用来说, 最多能同时存在对Toast
封装的ToastRecord
上限为50个. 这样做是为了防止DOS(Denial of Service). 如果不这样, 当通过大量循环去连续的弹出Toast, 这将会导致其他应用没有机会弹出Toast, 那么对于其他应用的Toast请求, 系统的行为就是拒绝服务, 这就是拒绝服务攻击的含义.
在ToastRecord
被添加到mToastQueue()
中后, NMS
就会通过showNextToastLocked()
方法来显示当前的Toast.
Toast的显示是由ToastRecord
的callback
来完成的. 这个callback
实际上就是Toast
中的TN
对象的远程Binder. 通过callback来访问TN中的方法是需要跨进程的. 最终被调用的TN
中的方法会运行在发起Toast
请求的应用的Binder线程池.
|
如上代码就是在Toast
显示以后, NMS
通过这个方法来发送一个延时消息, 具体取决Toast的时长. LONG_DELAY
, SHORT_DELAY
分别对应着3.5秒和2秒. 当延时时间达到的时候. NMS
会通过cancelToastLocked()
方法来隐藏Toast
并将其从mToastQueue
中移除, 这个时候如果mToastQueue
中还有其余Toast
那么NMS
就继续显示其他.
Toast的隐藏也会通过ToastRecord的callback完成的.同样是一次IPC过程. 方式和Toast显示类似.
|
以上基本说明Toast
的显示和影响过程实际上是通过Toast
中的TN
这个类来实现的. 他有两个方法show()
, hide()
. 分别对应着Toast
的显示和隐藏. 由于这两个方法是被NMS
以跨进程的方式调用的, 因此他们运行在Binder线程池中
. 为了将执行环境切换到Toast请求所在线程中, 在他们内部使用了handler
,如下
|
上面代码中, mShow
, mHide
是两个Runnable, 他们内部分别调用了handleShow
和handleHide
方法. 所以这两个方法才是真正完成隐藏和显示Toast
的地方.
TN
的handleShow中会将Toast
的视图添加到Window
中.
TN
的handleHide中会将Toast
的视图从Window
中移除.
具体实现代码如下:
|
关于Toast
流程已经完事. 除了说到的Activity
, Dialog
, Toast
. 还有PopupWindow
菜单栏, 状态栏都是通过Window
来实现的.
下一章: 第9章: 四大组件的工作过程