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,说白了就三步:
- 创建WindowManager对象
- 创建要添加的View
- 调用
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会做几件事:
- 检查权限(比如系统窗口需要权限)
- 分配Window的层级(根据type计算z-order)
- 创建WindowState对象,记录窗口状态
- 通知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,看看它到底是怎么驱动整个绘制流程的。嗯,那才是真正的重头戏。