鸿蒙HarmonyOS开发手册:窗口子系统基本概念与流程分析

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

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值