窗口子系统位于 \fundation\windowmanager
目录下,提供对窗口与 Display 管理的基础能力
概览
窗口是什么
每个 Ability 在创建时都会创建一个主窗口,并且为该窗口设置 ACE 中的 UIContent 用于加载展示 UI 界面。基本上所有的 UI 视图都是在窗口中展示的,比如弹窗、toast、系统状态栏导航栏、应用等。因此窗口子系统是系统图形界面显示所需的基础子系统。
窗口的种类
- 主窗口 应用显示的主窗口,即每个 Ability 持有的主窗口
- 子窗口 必须依附于主窗口来创建与显示
- 系统窗口 其他窗口均属于系统窗口
窗口的属性
WindowFlag
flag 指定窗口的部分测量规则:
- WINDOW_FLAG_NEED_AVOID 是否避开区域,默认避开,比如状态栏导航栏区域
- WINDOW_FLAG_PARENT_LIMIT 是否受到父窗口的限制,默认不限制,如果限制,则宽高不能超过父窗口,需与 WINDOW_MODE_FLOATING 配合使用
WindowMode
mode 指定窗口的布局规则:
- WINDOW_MODE_UNDEFINED 默认模式,默认宽高为 display 宽高减去状态栏导航栏等的宽高
- WINDOW_MODE_FULLSCREEN 全屏模式,但需要与 WINDOW_FLAG_NEED_AVOID 一起使用,默认宽高为 display 宽高
- WINDOW_MODE_SPLIT_PRIMARY 分屏主窗口模式,如果是横屏则位于左侧,竖屏位于上方
- WINDOW_MODE_SPLIT_SECONDARY 分屏副窗口模式,如果是横屏则位于右侧,竖屏位于下方
- WINDOW_MODE_FLOATING 悬浮模式,悬浮窗口可以通过窗口边缘改变窗口大小,默认宽高为 display 宽高的 3/4
应用主窗口可以通过启动 ability 时的参数 Want::PARAM_RESV_WINDOW_MODE(ohos.aafwk.param.windowMode)来侧面指定 WindowMode 的值
priority
窗口优先级决定了窗口的层级,priority 越大窗口越靠近顶部。该属性位于 WindowNode 内,且只能由 WindowType 决定。
WindowType
-
窗口类型的改变会引起 flag、mode、priority 或其他属性的改变,从而达到改变窗口的测量、排列与层级的目的。如:
property_->SetWindowMode(WindowMode::WINDOW_MODE_FLOATING);
property_->SetFocusable(false);
RemoveWindowFlag(WindowFlag::WINDOW_FLAG_NEED_AVOID);
SetWindowMode(WindowMode::WINDOW_MODE_FULLSCREEN);
-
层级则是由 WindowType 的 Priority 值与类型共同决定,同类型取值越大层级越高,WindowType 的 Priority 定义位于 foundation\windowmanager\wmserver\include\window_zorder_policy.h
中,如:
- WINDOW_TYPE_WALLPAPER = 0
- WINDOW_TYPE_DESKTOP = 1
- WINDOW_TYPE_APP_MAIN_WINDOW = 0
- WINDOW_TYPE_APP_SUB_WINDOW = 1
- WINDOW_TYPE_STATUS_BAR = 110
- WINDOW_TYPE_KEYGUARD = 114
- WINDOW_TYPE_BOOT_ANIMATION = 117
WindowType 的类型则有三种:
- BelowApp 位于底层,如桌面、壁纸等
- App 位于中间,如应用主窗口、子窗口
- AboveApp 位于上方,如锁屏、状态栏等
-
WindowType 是在这几个属性中,开发者目前唯一能直接修改的窗口属性:
window.setWindowType(type: WindowType): Promise
Window、Display、Screen 的关系
Screen 是物理屏幕,Display 是逻辑屏幕,Window 则依附于 Display。Screen 与 Display 之间是多对多的关系,Display 与 Window 也是多对多的关系。在普通的单屏场景下,Screen 与 Display 是一对一,Display 与 Window 则是一对多。
WindowManagerService
WMS 主要负责 Window 的管理,比如创建、销毁、布局、层级的管理,并提供窗口布局、焦点、事件分发的能力,但不负责绘制。主要职责如下:
- 管理 Window 的创建与销毁、窗口的属性的维护
- 窗口树的维护
- 窗口焦点的管理
- 窗口的层级管理以及输入法窗口的层级提升
- 窗口布局与策略的管理
- 提供窗口的缩放与拖拽能力
- 避开区域的管理
- 加载 ACE 布局并触发布局回调事件
DisplayManagerService
DMS 提供 Display 信息、Display 事件通知以及管理 Display 与 Screen 映射关系,其他能力主要通过 RenderService 实现。主要职责如下:
- 通过 RenderService 获取并管理 Screen
- ScreenGroup 的管理
- Display 的管理,以及其与 Screen 的映射管理
- 对外提供显示信息,如宽高、虚拟像素比等
- 提供截屏、量灭屏、横竖屏、亮度等屏幕相关能力
- 提供扩展屏幕或镜像屏等多屏能力
- 虚拟屏幕的管理
- Display 事件的通知,如屏幕亮灭、显示大小、横竖屏、冻结等事件
窗口管理流程分析
创建窗口
窗口的创建从 Ability 的 OnStart 声明周期函数中触发。
- Ability 持有 AbilityWindow,AbilityWindow 则持有 WindowScene
- WindowScene 在初始化阶段会创建一个主窗口
- 窗口的创建会调用 Window::Create 函数创建 WindowImpl 对象,并调用 WindowManagerService::CreateWindow 函数
- 在 WindowManagerService 中,则通过 WindowController 生成 windowId 并创建 WindowNode
- 最后通过 WindowRoot 将 WindowNode 管理起来
AbilityWindow 与 WindowScene 的关系
AbilityWindow 是 Ability 持有用来在生命周期函数中生成或调用窗口生命周期的类,操作窗口的类则是 WindowScene。WindowScene 由 WindowManager client 端提供,用于屏蔽元能力与窗口管理之间强耦合,方便后续无屏幕的小型设备裁剪显示系统。
WindowImpl 与 WindowNode 的区别
WindowImpl 是 IWindow 的实现,是提供给上层操作窗口的接口。WindowNode 与 WindowImpl 一一对应,是 WMS 中操作窗口的实体,其通过 WindowRoot 管理。WindowNode 内部维护了一个 windowToken_对象,该对象的指向就是 WindowImpl。
- WindowImpl 负责对应用于其他子模块提供操作窗口的能力,能力通过 WMS 与 RenderService 实现。WindowImpl 在创建时会创建 RSSurfaceNode 对象,该对象则会向 RenderService 提交一条窗口创建的事务。
- 在 WindowNode 创建时,WindowImpl 会将 RSSurfaceNode 的引用传递给 WindowNode。
- WindowNode 则是 WMS 中对窗口的抽象,内部维护了父子关系、显示隐藏、布局大小等。
WindowRoot 的作用
顾名思义,WindowRoot 管理着所有的窗口。其内部维护着 WindowNode 与 WindowId 的 map,提供了对 WindowNode 的增删改查操作,并且提供了最小化所有窗口、最大化窗口、设置布局策略等能力。
WindowImpl 的管理
主窗口的 WindowImpl 由 WindowScene 持有,子窗口则由主窗口自己管理维护。在 Ability 销毁时,会通知 WindowScene 销毁主窗口,主窗口则会销毁所有的子窗口,并通知 WMS 中的 WindowRoot 销毁相应的 WindowNode。
窗口的显示
创建的流程仅仅是创建了 WindowImpl 与 WindowNode,并未涉及布局与渲染,那么窗口是如何显示的呢?
- 窗口的显示也是通过 Ability 触发,在其生命周期函数 OnActive/OnForground 内,会调用到 WindowScene::GoForeground 中。
- 窗口的显示也可以通过在 ets 中手动调用 window.show()触发
- 调用主窗口的 show 方法,即 WindowImpl::Show
- 在其中会做一些判断,比如桌面的显示,会将其他 app 都最小化
- 接着 WindowImpl 通过 WMS 调用 WindowRoot 的 AddWindowNode 函数,并将 windowId 传递过来
- WindowRoot 通过 windowId 查找 WindowNode,并通过 diaplayId 创建或者获取 WindowNodeContainer 对象,并调用其 AddWindowNode 函数
- 在 WindowNodeContainer 内,会判断 window 类型并将 window 加入到相应的父窗口中(appRoot、belowRoot、aboveRoot)
- 接着会处理 WindowNode 中父子关系的映射,并调用 DMS 服务的 UpdateRSTree
- 处理所有窗口的 z 值,并按规则设置到每个窗口的 surfaceNode 中,该操作会向 RenderService 提交一条事务。
- z 值的规则为:从 belowRoot->appRoot->aboveRoot,z 值越来越大。同一类型中,window 的 priority 越大,z 值越大。同一 priority 的情况下,窗口被添加的越晚,z 值越大。z 值越大,排列越靠上。
- WindowNodeContainer 维护着两种布局策略,CASCADE 与 TILE,在维护完 z 值与父子关系等操作后,会调用布局策略的 AddWindowNode 函数
- 下面的流程均基于 CASCADE 策略
- 判断窗口 Visibility,为 false 则不布局
- 判断避开区域,限制窗口大小。如果是全屏窗口,则宽高与 display 一致。
- 如果是悬浮窗口,默认大小设置为 display 的 3/4,并设置一个 Decorate 矩形,该矩形为窗口增加了 37vp、5vp、5vp、5vp(上右下左),该矩形用于拖拽与平移
- 如果设置了 WINDOW_FLAG_PARENT_LIMIT 标记并且是子窗口,限制子窗口的大小不能超过父窗口
- 为悬浮窗口设置 hotZone,上下左右均增加 20vp。该区域用于多模输入模块判断手指是否落在 window 内,也就是增加判断范围。
- 调用窗口的 surfaceNode 的 SetBounds 函数,指定窗口的坐标与大小。该函数也会向 RenderService 提交一条事务。
- 迭代子窗口,为其执行同样的流程
- 总结下来,窗口的显示就是处理了父子关系、窗口先后关系,以及确定了坐标与大小,最后向 RenderService 提交事务,等待下个 vsync 的绘制
WindowNodeContainer 的作用
WindowNodeContainer 与 Display 一一对应,其管理了单个 Display 中的所有窗口,WindowRoot 则管理了所有的窗口与 WindowNodeContainer。WindowNodeContainer 提供了布局策略的决策与设置、窗口焦点设置、窗口排列、避开区域管理、窗口分屏显示等能力。
布局策略
OH 目前支持两种策略,CASCADE(层叠)与 TILE(平铺)。默认的布局策略是 CASCADE,分屏显示也会将策略切换至 CASCADE。布局策略的主要能力就是决定窗口的排列布局方式、位置与大小。两种策略的区别如下:
总结
设置全屏
设置全屏可以通过 ets 调用 window.setFullScreen(true),window 会占满全屏,并且状态栏与导航栏会消失。接下来来看看底层是如何实现的。
- setFullScreen 会走到 WindowImpl 中,其中主要做了 3 件事
- SetWindowMode(WindowMode::WINDOW_MODE_FULLSCREEN)
- RemoveWindowFlag(WindowFlag::WINDOW_FLAG_NEED_AVOID)
- 通过 SetSystemBarProperty 将状态栏与导航栏的 enable 置为 false
- SetWindowMode
- 代码会调用到 WindowController::SetWindowMode 内,其中会对 mode 做一些判断。针对 FULLSCREEN 的情况,会最小化其他 app 的 window
- 接着调用 WindowNodeContainer::UpdateWindowNode,其中会调用布局策略来更新窗口的布局
- RemoveWindowFlag
- 为窗口的 property 这是 flag 后,同样会走到 WindowNodeContainer::UpdateWindowNode 中
- 与窗口显示流程一样,其判断为全屏窗口后,不会避开状态栏与导航栏区域
- SetSystemBarProperty
- SetSystemBarProperty 同样会在 WindowNodeContainer 中更新窗口
- 迭代所有窗口,遇到全屏窗口,就将窗口内的 SystemBarProperty 与默认的对比,有变化(enable 值不同)就通知订阅了 systemBarTintChange 事件的组件
- 即 ets 中:window.on('systemBarTintChange')
- systemui 订阅了该事件,在收到事件后,根据 enable 的值,去调用 statusBar/navigationBar 窗口的 hide 方法,来达到隐藏状态栏导航栏的目的
如何设置全屏并且显示状态栏导航栏
只需要在调用 window 的 setFullScreen 函数后,在调用其 setSystemBarEnable 即可:
window.setSystemBarEnable(['status', 'navigation']).then(() => {})
加载 ui
在 Stage 模式中,我们通过 WindowStage 的 setUIContent 来加载页面,这个过程是如何实现的?WindowStage 是 WMS 提供给前端的一套 api,其通过调用 WindowImpl 的 setUIContent 来实现:
uiContent_ = Ace::UIContent::Create(context_.get(), engine)
uiContent_->Initialize(this, contentInfo, storage)
WindowImpl 会在合适的时机,调用 UIContent 内的回调:
- uiContent_->UpdateViewportConfig(config, reason) 宽高位置等变化
- uiContent_->UpdateWindowMode(mode)
- uiContent_->ProcessBackPressed()
- uiContent_->ProcessKeyEvent(keyEvent)
- uiContent_->ProcessPointerEvent(pointerEvent)
- uiContent_->ProcessVsyncEvent(static_cast(timeStamp))
- uiContent_->UpdateConfiguration(configuration) 系统语言、颜色模式等变化
触摸事件的传递
触摸事件由多模输入模块传递到窗口,经过处理后,传递给 ACE 中的 UIContent 中。
- 通过 InputManager 注册为窗口输入事件消费者
- 触摸事件会回调至 WindowInputChannel::HandlePointerEvent 中
- 如果调用了窗口的 AddInputEventListener 设置触摸监听,转发事件至监听内,并且只将 POINTER_ACTION_DOWN 与 POINTER_ACTION_BUTTON_DOWN 传递给窗口。
- 如果是 POINTER_ACTION_MOVE 事件,在下一帧将事件传递给窗口。如果是其他事件,立即传递给窗口。
- 在窗口内,如果是悬浮窗口:
- POINTER_ACTION_DOWN
- 判断手指是否落在窗口之外,窗口 Decorate 矩形内,如果是,开启拖拽模式
- 如果触摸的 window 类型为 WINDOW_TYPE_DOCK_SLICE,开始移动模式
- POINTER_ACTION_MOVE
- 如果开启拖拽模式,根据手指移动的距离,通过 WindowNodeContainer 修改窗口大小
- 如果开启移动模式,根据手指移动的距离,通过 WindowNodeContainer 修改窗口位置
- 如果开启了开启拖拽或移动模式,事件不会继续传递,如果未开启,则会传递给 ACE 的 UIContent
Display 管理流程分析
DMS 启动流程
DMS 在启动时的主要工作就是从 RenderService 获取屏幕信息,并创建 ScreenGroup 与 Display
- 通过 RSInterface 注册屏幕连接回调,在屏幕连接后,创建 AbsScreen
- 再通过 RSInterface 获取屏幕支持的分辨率、刷新率等信息,设置到 AbsScreen 中
- 创建 ScreenGroup,将 AbsScreen 添加到 group 中
- 添加后会为 AbstractScreen 初始化 RSDisplayNode,并向 RenderService 提交一条 RSDisplayNode 创建的事务
- ScreenGroup 与 AbsScreen 初始化完毕后,会为 AbsScreen 创建一个 AbstractDisplay
- AbstractDisplay 保存了 AbsScreen 中的分辨率刷新率等信息,并且根据屏幕宽与高,决定虚拟像素比。
- 通知感兴趣的部件,Display 已经创建好
- WMS 也会通过 DMS 监听 Display 的变化,比如大小变化、横竖屏变化,WMS 会通知到 WindowNodeContainer 去更新 Display 的状态并重新布局所有窗口
- 如果遇到 BEFORE_SUSPEND 事件,比如进入锁屏状态,WMS 会通过 WindowNodeContainer 通知每个窗口(WindowImpl)更新其状态为 STATE_FROZEN,并告知 AMS,让持有窗口的 Ability 进入后台状态
ScreenGroup 是什么
ScreenGroup 顾名思义是屏幕组,屏幕组中定义了多个屏幕的连接方式,如扩展或镜像。每个物理屏幕在连接后都会加入到默认的屏幕组中。屏幕组也可以包含虚拟屏幕。ScreenGroup 与 AbsScreen 都由 AbstractScreenController 管理。
UpdateRSTree
UpdateRSTree 会在窗口节点显示或隐藏时调用,其作用就是为 AbsScreen 中的 RSDisplayNode 添加或删除窗口的 RSSurfaceNode,并向向 RenderService 提交增加或删除子节点的事务。