窗口子系统基本概念与流程分析
窗口子系统位于\fundation\windowmanager目录下,提供对窗口与Display管理的基础能力 概览 窗口是什么 每个Ability在创建时都会创建一个主窗口,并且为该窗口设置ACE中的UIContent用于加载展示UI界面。基本上所有的UI视图都是在窗口中展示的,比如弹窗、toast、系统状态栏导航栏、应用等。因此窗口子系统是系统图形界面显示所需的基础子系统。 窗口的种类 主
窗口子系统位于\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或其他属性的改变,从而达到改变窗口的测量、排列与层级的目的。如:
-
WINDOW_TYPE_STATUS_BAR
property_->SetWindowMode(WindowMode::WINDOW_MODE_FLOATING); property_->SetFocusable(false);
-
WINDOW_TYPE_KEYGUARD
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<void>
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<uint64_t>(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提交增加或删除子节点的事务。
更多推荐
所有评论(0)