Window 的概念与分类:应用窗口、子窗口、系统窗口的区别与使用场景
大家好,我是你们的老朋友。今天我们来聊聊 Window 的分类。
说实话,Window 这个概念,很多同学刚接触时容易混淆。它不像 Activity 那么直观,但又是 Android 显示体系里最核心的一环。我个人习惯把 Window 理解成「一块画板」,所有 View 都要挂在这块画板上才能显示。而这块画板,根据它的「出身」和「用途」,可以分为三大类:应用窗口、子窗口、系统窗口。
1. 应用窗口(Application Window)
应用窗口,说白了就是每个 App 默认的主窗口。它对应一个 Activity,也是我们最常打交道的窗口类型。
特点:
- 必须绑定一个 Activity。
- 拥有独立的 WindowManager.LayoutParams,type 范围在
FIRST_APPLICATION_WINDOW到LAST_APPLICATION_WINDOW(1 ~ 99)。 - 通常占据全屏或部分屏幕区域。
使用场景:
- App 的主界面、弹窗(Dialog 其实也是应用窗口的一种变体)。
- 启动页、主页面、设置页等。
核心要点:应用窗口的 token 必须来自 Activity。没有 token,窗口就挂不上。我在项目中遇到过新手直接把 Dialog 的 window type 设成系统窗口,结果崩溃了——嗯,token 不对。
2. 子窗口(Sub Window)
子窗口,顾名思义,它不能独立存在,必须依附于一个父窗口。你可以把它想象成「画板上的小画板」。
特点:
- type 范围在
FIRST_SUB_WINDOW到LAST_SUB_WINDOW(1000 ~ 1999)。 - 必须有一个有效的父窗口 token。
- 它的显示层级受父窗口影响,但可以浮动在父窗口之上。
使用场景:
- PopupWindow:最常见的子窗口实现。
- ContextMenu、Spinner 下拉列表。
- 某些自定义的 Tooltip 或悬浮菜单。
避坑指南:我曾经在项目里用 PopupWindow 做下拉筛选,结果在部分机型上点击外部无法关闭。后来发现是父窗口 token 传递出了问题。记住:子窗口的 token 必须来自父窗口的 getWindowToken(),而不是 getApplicationToken()。
3. 系统窗口(System Window)
系统窗口,这是权限最高的一类窗口。它不依赖任何 Activity,可以显示在所有应用之上。说白了,它就是「全局浮层」。
特点:
- type 范围在
FIRST_SYSTEM_WINDOW到LAST_SYSTEM_WINDOW(2000 ~ 2999)。 - 需要申请
SYSTEM_ALERT_WINDOW权限(悬浮窗权限)。 - 显示层级最高,甚至可以覆盖状态栏和导航栏。
使用场景:
- 悬浮球、手机管家类 App 的浮窗。
- 来电界面、闹钟提醒。
- 系统级 Toast(非应用内 Toast)。
注意:系统窗口滥用会导致用户体验极差。我记得 Google 在 Android 10 以后对系统窗口做了严格限制,后台启动的 Activity 和窗口都被限制了。如果你要做悬浮窗,一定要处理好生命周期和权限申请。
4. 三种窗口的对比
为了让你更直观地理解它们的区别,我整理了一张表:
| 维度 | 应用窗口 | 子窗口 | 系统窗口 |
|---|---|---|---|
| type 范围 | 1 ~ 99 | 1000 ~ 1999 | 2000 ~ 2999 |
| 依赖 Activity | 是 | 是(依赖父窗口) | 否 |
| 显示层级 | 低 | 中(在父窗口之上) | 高(全局最上层) |
| 权限要求 | 无 | 无 | SYSTEM_ALERT_WINDOW |
| 典型代表 | Activity、Dialog | PopupWindow、Menu | 悬浮窗、系统 Toast |
5. 核心逻辑图:Window 分类与层级关系
下面我用一张 SVG 图来展示这三种窗口在系统中的位置关系。你想想看,窗口层级就像一栋楼:应用窗口在一楼,子窗口在二楼(但必须踩着父窗口的地板),系统窗口在顶楼,可以俯瞰一切。
6. 如何选择正确的窗口类型?
这个问题,我经常被问到。其实判断标准很简单:
- 你的 UI 是否属于某个 Activity? 如果是,用应用窗口或子窗口。
- 是否需要浮在别的 App 之上? 如果是,用系统窗口,但别忘了申请权限。
- 是否需要跟随父窗口移动? 如果是,用子窗口(比如 PopupWindow 会跟随锚点 View)。
我的经验:能不用系统窗口就不用。系统窗口不仅权限麻烦,而且在 Android 10+ 上限制很多。我曾经为了做一个全局悬浮球,折腾了三个版本才适配好。后来改用前台 Service + 系统窗口,才稳定下来。
7. 避坑总结
- token 问题:应用窗口和子窗口都需要有效的 token。没有 token,WindowManager.addView() 会直接抛异常。
- 权限问题:系统窗口在 Android 6.0+ 需要动态申请悬浮窗权限,且用户可以在设置中关闭。
- 生命周期:子窗口随着父窗口销毁而销毁。如果你在 Activity 销毁后还尝试操作子窗口,会引发 WindowLeaked 崩溃。
- type 值不要乱设:每个 type 都有对应的行为约束。比如
TYPE_APPLICATION_OVERLAY在 Android 10 以后替代了旧的TYPE_PHONE,如果你还用旧的,可能无法显示。
好了,关于 Window 的分类就讲到这里。这三种窗口类型,你只要记住它们的「出身」和「权限」,在实际开发中就不会用错。下一章我们会深入 WindowManager 的添加流程,看看 addView 到底做了什么。