积分582 / 贡献0

提问0答案被采纳0文章52

[经验分享] 窗口子系统基本概念与流程分析 原创

Laval社区小助手 显示全部楼层 发表于 2024-3-4 10:27:23

窗口子系统位于 \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提交增加或删除子节点的事务。

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

精彩评论1

faceoh

沙发 发表于 2024-8-14 15:51:50
怎么设置窗口置顶呢?

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部