OpenHarmony开发者论坛

标题: 窗口子系统基本概念与流程分析 [打印本页]

作者: Laval社区小助手    时间: 2024-3-4 10:27
标题: 窗口子系统基本概念与流程分析
[md]窗口子系统位于 `\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:ARAM\_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。布局策略的主要能力就是决定窗口的排列布局方式、位置与大小。两种策略的区别如下:

![布局策略](https://devpress.csdnimg.cn/5a6c67f77ac946ef9996cf03c11ff730.PNG)

#### 总结

![总结](https://devpress.csdnimg.cn/6f2d5e1e657b4fa5a28d1b8039fdd38c.PNG)

### 设置全屏

设置全屏可以通过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\_->rocessBackPressed()
* uiContent\_->rocessKeyEvent(keyEvent)
* uiContent\_->rocessPointerEvent(pointerEvent)
* uiContent\_->rocessVsyncEvent(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提交增加或删除子节点的事务。
[/md]
作者: faceoh    时间: 2024-8-14 15:51
怎么设置窗口置顶呢?




欢迎光临 OpenHarmony开发者论坛 (https://forums.openharmony.cn/) Powered by Discuz! X3.5