2、Window与WindowManager:Window的概念、类型与层级,WindowManager如何添加View

好,咱们今天聊聊Window和WindowManager。

说实话,我刚接触Android那会儿,一直以为屏幕上显示的东西都是Activity。后来踩了坑才发现——Activity只是冰山一角。真正管理屏幕显示的核心,其实是Window。

2.1 Window到底是什么?

简单来说,Window是一个抽象的概念。它不是View,也不是ViewGroup,而是一个“承载View的容器”。

你可以把Window想象成一块画板。画板本身不画画,但它决定了画布的位置、大小、层级。View就是画布上的内容。而WindowManager,就是那个帮你把画板挂到墙上的工人。

在Android源码里,Window是一个抽象类,它的唯一实现是PhoneWindow。嗯,你没看错,就这一个实现类。

核心关系:

  • 每个Activity持有一个Window(PhoneWindow)
  • Window内部包含一个DecorView(顶级View)
  • WindowManager负责将Window添加到屏幕

2.2 Window的类型与层级

Window不是随便放的。它分三种类型,每种类型有对应的层级范围。这个层级决定了谁盖在谁上面。

类型 层级范围 典型用途
应用窗口(Application Window) 1 ~ 99 Activity
子窗口(Sub Window) 1000 ~ 1999 Dialog、PopupWindow
系统窗口(System Window) 2000 ~ 2999 Toast、系统状态栏、输入法

层级数值越大,显示越靠上。比如系统窗口的Toast,一定会盖在你的Activity上面。

避坑指南:我曾经在项目里用系统窗口类型做了一个悬浮球,结果在Android 10以上直接被系统拦截了。后来才发现,TYPE_PHONE这类窗口需要申请SYSTEM_ALERT_WINDOW权限,而且高版本限制越来越严。建议能用应用窗口就别碰系统窗口。

2.3 WindowManager如何添加View?

好,重点来了。WindowManager添加View,说白了就三步:

  1. 创建WindowManager对象
  2. 创建要添加的View
  3. 调用addView()方法

但背后的流程,远比你想象的复杂。咱们直接看代码:

// 获取WindowManager
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

// 创建要显示的View
TextView textView = new TextView(this);
textView.setText("我是一个悬浮窗");

// 设置布局参数
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    WindowManager.LayoutParams.WRAP_CONTENT,
    WindowManager.LayoutParams.WRAP_CONTENT,
    // 注意:这里指定类型
    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
    // 标志位:不可聚焦、触摸可穿透
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
    PixelFormat.TRANSLUCENT
);

// 添加到屏幕
wm.addView(textView, params);

这段代码看起来简单,对吧?但addView()内部干了什么?

2.4 addView() 的幕后流程

我习惯把addView的流程分成三个阶段:

第一阶段:参数校验与预处理

WindowManagerImpl会检查params是否合法,比如类型是否在允许范围内。如果类型不对,直接抛异常。我记得有一次传了个TYPE_APPLICATION给悬浮窗,结果崩溃了——因为应用窗口必须依附于Activity。

第二阶段:创建ViewRootImpl

这是最核心的一步。WindowManagerImpl会创建一个ViewRootImpl对象。ViewRootImpl是什么?说白了,它是View和WindowManager之间的桥梁。

ViewRootImpl负责:

  • 将View添加到WindowManagerService
  • 处理View的测量、布局、绘制
  • 接收触摸事件并分发给View

第三阶段:通过Binder与WMS通信

ViewRootImpl内部会通过Binder机制,调用WindowManagerService.addWindow()。WMS会做几件事:

  1. 检查权限(比如系统窗口需要权限)
  2. 分配Window的层级(根据type计算z-order)
  3. 创建WindowState对象,记录窗口状态
  4. 通知SurfaceFlinger创建Surface

关键点:WMS返回成功后,ViewRootImpl会通过requestLayout()触发第一次绘制流程。这就是View绘制的起点。

2.5 我踩过的坑

讲几个实际项目里遇到的坑,你们以后碰到能少走弯路:

  • 类型选择错误:我曾经用TYPE_PHONE做悬浮窗,结果在Android 8.0以上直接闪退。后来改成TYPE_APPLICATION_OVERLAY才解决。
  • 忘记设置FLAG_NOT_FOCUSABLE:如果不加这个标志,悬浮窗会抢走焦点,导致Activity收不到按键事件。用户点返回键都没反应。
  • 重复添加View:同一个View不能add两次。如果第二次add,会抛出IllegalStateException。我习惯在add之前先检查getParent()是否为空。

注意:从Android 10开始,系统对后台启动Activity做了严格限制。如果你在Service里通过WindowManager添加View,一定要确保用户可见或者有相应权限。否则View会显示不出来。

2.6 小结

Window和WindowManager,说白了就是Android显示系统的“地基”。

你看到的每个Activity、Dialog、Toast,底层都是通过WindowManager把View挂到屏幕上的。理解了Window的层级和添加流程,后面学View的测量、布局、绘制就会轻松很多。

下一章,咱们会深入ViewRootImpl,看看它到底是怎么驱动整个绘制流程的。嗯,那才是真正的重头戏。