Android Window概述

什么是窗口

PopupWindow 也是一个窗口,我们经常使用的 Toast 也是一个窗口。像 Dialog,ContextMenu,以及 OptionMenu 等等这些都是窗口。

我们知道,WindowManagerService(后面简称 WmS)管理所有的窗口。但是对于WmS来讲,一个窗口其实就是一个 View 类,而不是 Window 类。WmS 负责管理这些 View 的 Z-order,显示区域,以及把消息派发到对应的 View 中。View 本身并不能直接从 WmS 中接收消息,而是通过实现了 IWindow 接口的 ViewRootImpl.W 类来实现。

在WmS 眼中的,窗口是可以显示用来显示的 View。对于 WmS 而言,所谓的窗口就是一个通过 WindowManagerGlobal.addView()添加的 View 罢了。

  1. 在 Window System 中,分为两部分的内容,一部分是运行在系统服务进程(WmS 所在进程)的 WmS 及相关类,另一部分是运行在应用进程的 WindowManagerImpl, WindowManagerGlobal,ViewRootImpl 等相关类。WmS 用 WindowState 来描述一个窗口,而应用进程用 ViewRootImpl,WindowManager.LayoutParms 来描述一个窗口的相关内容。

  2. 对于 WmS 来讲,窗口对应一个 View 对象,而不是 Window 对象。添加一个窗口,就是通过 WindowManager 的 addView 方法。同样的,移除一个窗口,就是通过 removeView 方法。更新一个窗口的属性,通过 updateViewLayout 方法。

  3. Window 类描述是一类具有某种通用特性的窗口,其实现类是 PhoneWindow。Activity 对应的窗口,以及 Dialog 对应的窗口,会对应一个 PhoneWindow 对象。PhoneWindow 类把一些操作的统一处理了,例如长按,按”Back”键等。

  4. Android Framework 把窗口分为三种类型,应用窗口,子窗口以及系统窗口。不同类型的窗口,在执行添加窗口操作时,对于 WindowManager.LayoutParams 中的参数 token 具有不同的要求。应用窗口,LayoutParams 中的 token,必须是某个有效的 Activity 的 mToken。而子窗口,LayoutParams 中的 token,必须是父窗口的 ViewRootImpl 中的 W 对象。系统窗口,有些系统窗口不需要 token,有些系统窗口的 token 必须满足一定的要求。

  5. 只能通过 Context.getSystemServer 来获取 WindowManager(即获取一个 WindowManagerImpl 的实例)。如果这个 context 是 Activity,则直接返回了 Activity 的 mWindowManager,其 WindowManagerImpl.mParentWindow 就是这个 Activity 本身对应的 PhoneWindow。如果这个 context 是 Application,或者 Service,则直接返回一个 WindowManagerImpl 的实例,而且 mParentWindow 为 null。

  6. 在调用 WindowManagerImpl 的 addView 之前,如果没有给 token 赋值,则会走默认的 token 赋值逻辑。默认的 token 赋值逻辑是这样的,如果 mParentWindow 不为空,则会调用其 adjustLayoutParamsForSubWindow 方法。在 adjustLayoutParamsForSubWindow 方法中,如果当前要添加的窗口是,应用窗口,如果其 token 为空,则会把当前 PhoneWindow 的 mToken 赋值给 token。如果是子窗口,则会把当前 PhonwWindow 对应的 DecorView 的 mAttachInfo 中的 mWindowToken 赋值给 token。而 View 中的 AttachInfo mAttachIno 来自 ViewRootImpl 的 mAttachInfo。因此这个 token 本质就是父窗口的 ViewRootImpl 中的 W 类对象。

  7. Activity.java attach方法中, 都会新建一个WindowManager给PhoneWindow。

Tip

最近项目中遇到一个bug,很多手机通知权限关闭后toast也显示不出来了,无奈之下只好自定义toast,首先想到的方案就是使用windowManager添加一个view,并且指定param.type=TYPE_TOAST,这种方案可以在大多数手机上显示出来,后来发现在一些国产手机下此方案不可行,他们的rom对TYPE_TOAST做了限制,需要申请悬浮框权限。退而求次,设置param.type=TYPE_APPLICATION,这样的话弹出的toast需要依附在activity上面,而且可以展示在dialog上面,也不用申请权限,勉强符合需求。