Android窗口的层级是如何管理的
Android窗口的层级管理是一个复杂的系统,主要由 WindowManagerService (WMS) 这个系统服务来负责。
其核心思想是使用一个 “Z-order” 的概念,即垂直于屏幕的、虚拟的Z轴。窗口在这个轴上进行排序,Z轴值越大的窗口,离用户越近,就越靠上显示。管理这个顺序的规则就是层级(Layer) + 层级内的顺序(Order)。
核心管理者:WindowManagerService (WMS)
WMS是系统中最重要的服务之一,它承担了所有窗口的“大管家”角色,主要职责包括:
窗口的创建、销毁和布局(Layout)。计算窗口的最终显示位置和层级(Z-ordering)。将窗口信息传递给SurfaceFlinger进行合成和渲染。管理输入事件的分发(与InputDispatcher协作)。
窗口层级(Z-order)的管理规则
窗口的Z轴顺序并非一个简单的全局计数器,而是由两个关键因素共同决定:
窗口类型(Type):决定了窗口的基本层级范围(即处于哪个“阶层”)。同类型窗口的添加顺序:决定了在同一阶层内的先后顺序(“先来后到”)。
1. 按窗口类型(Type)划分阶层
Android定义了多种窗口类型,并为每种类型分配了一个基础Z轴顺序值。这些类型在
中以常量形式定义。从底层到顶层,主要分为以下几个大类:
WindowManager.LayoutParams
类型大类 | 具体类型示例 | Z值范围 | 说明 | 典型代表 |
---|---|---|---|---|
应用窗口 |
|
1~99 | 普通应用Activity所在的窗口层级。 | 应用的主界面。 |
子窗口 |
|
1000~1999 | 必须依附于一个父应用窗口。 | PopupWindow、Dialog。 |
系统窗口 |
|
2000~2999 | 系统级别的弹窗,显示在最上层。 | Toast、授权对话框。 |
|
系统错误级弹窗,甚至比系统Alert更高。 | 系统崩溃提示(如ANR对话框)。 | ||
|
系统叠加层,显示在所有内容之上。 | 手电筒应用的全屏覆盖。 | ||
|
用于电话相关的系统窗口(来电显示)。 | 来电接听界面。 | ||
|
状态栏。 | 系统状态栏。 | ||
|
导航栏。 | 虚拟按键栏。 | ||
|
输入法窗口。 | 键盘。 | ||
|
壁纸窗口。 | 桌面壁纸。 |
重要规则:一个系统窗口(如Toast,Z值2000+)永远会显示在一个应用窗口(Z值1-99)之上,无论应用窗口是何时添加的。
2. 同类型窗口的顺序:Order within Type
在同一类型的阶层内,窗口的显示顺序则由其添加的先后顺序决定。后添加的窗口会显示在先添加的窗口之上。
例如,在一个Activity中:
你先打开了一个PopupWindow(子窗口)。然后又打开了一个AlertDialog(也是一个子窗口)。
虽然它们都属于“子窗口”阶层(1000-1999),但后弹出的AlertDialog会覆盖在先弹出的PopupWindow之上,因为它在同一阶层内拥有更高的Z-order。
管理流程:从应用请求到屏幕显示
应用请求:应用通过
方法请求添加一个窗口(如PopupWindow),并传入定义了类型(Type)的
WindowManager.addView()
。WMS处理:
LayoutParams
WMS接收到请求,根据
确定新窗口的基本Z轴位置(把它放到哪个阶层)。WMS然后检查这个阶层内部,将新窗口放在该阶层的最顶层(Z值最大)。
LayoutParams.type
布局与合成:
WMS协同SurfaceFlinger,根据计算出的最终Z-order列表,将所有窗口的Surface进行合成(Compose),并输出到屏幕帧缓冲区。Z-order高的窗口的像素会覆盖Z-order低的窗口的像素。
开发者如何控制窗口层级?
开发者可以通过设置
的相关属性来影响窗口的层级:
WindowManager.LayoutParams
:最重要的属性,直接决定窗口的“阶层”。注意:普通应用无法使用高于
type
(>=2000) 的类型,否则会抛出
TYPE_APPLICATION_OVERLAY
(需要系统签名权限
SecurityException
)。
SYSTEM_ALERT_WINDOW
:一些标志位也能影响层级和行为。
flags
:允许窗口扩展到屏幕外。
FLAG_LAYOUT_NO_LIMITS
:允许窗口之外的触摸事件传递给下层的窗口。
FLAG_NOT_TOUCH_MODAL
:窗口不获取焦点,输入事件直接穿透。
FLAG_NOT_FOCUSABLE
:设置窗口变暗的程度,通常用于模态对话框,WMS会为它创建一个特殊的Dim层,位于对话框窗口之下、其他所有内容之上。
dimAmount
总结
Android窗口层级管理就像一个严格的“社会阶层”:
“阶层”固化:窗口类型(Type) 就像一个人的出身,决定了你属于哪个社会阶层(应用层、子窗口层、系统层)。系统层的人天生就比应用层的人“高”。“论资排辈”:在同一个阶层内部,添加顺序决定了你的地位。后加入的(后弹出的)会排在更前面(更上层)。
最终,WMS作为“统治者”,严格维护着这个阶层的秩序,确保所有窗口都能按照正确的Z-order进行显示和交互。理解这个概念对于分析UI覆盖、触摸事件穿透等问题至关重要。
TYPE_APPLICATION
对于
类型的窗口(即普通的Activity窗口),其内部的Z-order定义方式与其他类型的窗口(如系统窗口)有所不同。它不仅仅依赖于简单的添加顺序,还紧密关联着 Activity在任务栈(Task)中的位置 和 窗口本身的属性。
TYPE_APPLICATION
核心答案是:
层级内的Z-order主要由其对应的Activity在任务栈中的顺序决定,并辅以窗口本身的
TYPE_APPLICATION
属性进行微调。
LayoutParams
下面是详细的分解:
1. 主导因素:Activity任务栈 (Activity Task Stack)
这是管理
窗口层级的最主要机制。Android应用遵循“后进先出”(LIFO)的栈模型。
TYPE_APPLICATION
规则:位于任务栈顶的Activity,其窗口位于Z-order的顶部。最近启动的Activity会覆盖在之前的Activity之上。工作原理:
你启动应用A的MainActivity(我们称之为A1),WMS会为其创建一个
窗口,并放置在应用窗口层级中。从A1中启动另一个Activity A2。A2会被压入任务栈顶,WMS会为A2创建另一个
TYPE_APPLICATION
窗口。WMS在安排Z-order时,会确保A2的窗口位于A1的窗口之上,因为A2在栈中位于A1之上。当你按下返回键,A2被销毁并从栈中弹出,A1重新成为栈顶,它的窗口也就重新成为了最顶层的应用窗口。
TYPE_APPLICATION
这个过程是由ActivityManagerService (AMS) 和 WindowManagerService (WMS) 协同工作完成的。AMS管理任务栈的逻辑顺序,WMS则根据这个顺序来安排窗口的Z-order。
2. 辅助因素:窗口本身的LayoutParams
在同一个Activity内部,如果它添加了多个属于
类型的窗口(这种情况不常见,但可能发生),或者需要微调与其他Activity窗口的层级,就会用到
TYPE_APPLICATION
中的相关属性。
WindowManager.LayoutParams
a)
windowToken
和
activityToken
windowToken
activityToken
这是系统级别的关联标识。一个
窗口在添加时,必须附着在一个Activity的Token上。WMS通过这个Token知道该窗口属于哪个Activity,从而将其Z-order与Activity在任务栈中的位置绑定。
TYPE_APPLICATION
b)
type
type
对于应用窗口,虽然都是
,但也有一些子类型,它们之间有细微的Z-order差别:
TYPE_APPLICATION
: Activity窗口的基类类型,是最底层。
TYPE_BASE_APPLICATION
: 标准类型。
TYPE_APPLICATION
: 用于在应用启动时绘制的“启动窗口”(Starting Window),也就是那个在App完全加载前显示的预览窗口。它通常位于本应用其他窗口之下,但在前一个应用窗口之上。
TYPE_APPLICATION_STARTING
它们的Z值都在1-99的范围内,但有高低之分:
<
TYPE_BASE_APPLICATION
<
TYPE_APPLICATION
(注意:这个顺序可能因版本略有调整,但逻辑是存在的)。
TYPE_APPLICATION_STARTING
c)
flags
flags
一些标志位可以影响窗口在同层级内的行为:
FLAG_LAYOUT_IN_OVERSCAN
FLAG_LAYOUT_IN_SCREEN
FLAG_LAYOUT_INSET_DECOR
…这些标志主要影响窗口的布局范围,间接可能影响其被视为“顶层”窗口的优先级,但对Z-order的直接影响不如任务栈关系那么大。
d)
dimAmount
dimAmount
当窗口需要使背景变暗时(如对话框风格的Activity),WMS会创建一个特殊的Dim层。这个Dim层位于该Activity窗口之下,但在所有其他窗口(包括栈中在它下面的Activity窗口)之上。这是一种特殊的Z-order管理。
3. 特殊情况:多窗口模式
在分屏(Split-Screen)、自由窗口(Freeform)等多窗口模式下,
窗口的Z-order管理会变得更加复杂。
TYPE_APPLICATION
每个Activity窗口可能位于不同的栈中,并且显示在屏幕的不同区域。此时,哪个窗口拥有焦点(Focus) 成为了更关键的因素。获得焦点的窗口虽然不一定在物理位置上完全覆盖另一个窗口,但在Z-order上会被提升以确保其接收输入事件,并且其装饰(如标题栏)可能会高亮显示。用户点击哪个窗口,哪个窗口就会移动到Z-order的顶部并获得焦点。
总结
可以将
层级想象成一个公司部门:
TYPE_APPLICATION
部门层级(任务栈):整个部门(应用)在公司(系统)中有固定的位置。部门内部的员工(Activity)有明确的上下级汇报关系(栈顺序),领导(栈顶Activity)总是在最前面。这是最主要的排序规则。员工工牌(LayoutParams):每个员工的工牌(
,
type
等)定义了他的一些特权,比如是否可以进入高管餐厅(特殊子类型),但这不会改变他的汇报关系。工牌主要用于微调其在部门内部的精确位置。
flags
因此,对于普通应用开发者来说,你通常不需要(也不应该)直接去操纵
窗口的Z-order。你应该通过标准的 Activity导航(如
TYPE_APPLICATION
)和任务栈管理 来控制你的窗口谁前谁后,让AMS和WMS自动为你处理Z-order的复杂细节。直接操作Z-order是系统窗口和特定系统应用才需要的权限和能力。
startActivity()
希望让应用窗口显示在其他应用上方,这在实现一些特殊功能(如悬浮窗、全局提醒)时是很有用的需求。下面我将为你解释两种主要的实现方式、需要注意的事项,并提供一些代码示例。
实现应用窗口置顶,主要有两种思路:
一是使用
直接添加视图(View),
WindowManager
二是调整当前 Activity 的窗口属性。
它们的大致流程如下:
🧩 你需要了解,从 Android 6.0 (API level 23) 开始,
权限被列为危险权限,仅靠在
SYSTEM_ALERT_WINDOW
中声明还不够,还需要在运行时动态向用户申请。你可以通过
AndroidManifest.xml
来检查是否已授权,并通过
Settings.canDrawOverlays(context)
引导用户前往设置页面开启权限。
Intent( Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
🪟 采用
添加视图的方式,灵活性和控制度更高,适合创建非全屏的悬浮窗口。而通过设置 Activity 窗口属性的方式,更依赖于 Activity 的生命周期。
WindowManager
🤖 不同手机厂商可能会对悬浮窗的显示和行为做出定制和限制,这可能导致你的应用在某些机型上表现不一致。在开发过程中,需要在目标机型上进行充分测试。
🎯 由于滥用悬浮窗可能会干扰用户,Android 系统和对用户体验敏感的应用市场(如 Google Play)对悬浮窗功能的使用有严格的审核政策。因此,务必确保你的应用有明确且合理的用途(例如前台服务的通知、无障碍功能、设备管理等),并在需要时向用户清晰解释为什么需要这个权限。