0%

(06)2021.12.21-WindowManager对窗口的操作---Colin老师

一、Window 总览

window 是屏幕上一块区域,但不是实实在在的,实际显示的是 View 。WMS、WindowManager 以及 Window 之间的关系如下:

Window相关方的关系

  • Window: 窗口的概念,Android 中所有的师徒都依赖于 Window 显示

  • WindowManager : 管理 Window ,包括 增、删、更新 等操作,类似一个代理的概念,最终要丢给 WMS 去实际操作。

  • WMS : Window 最终管理者,负责 window 的启动、添加、删除,Window 的大小和层级也是 WMS 管理的

1.1 Window 分类

Application Window

xxxx

statusBar 和 searchBar 等居然也都是 System Window 。

打电话时,脸靠近手机的时候,黑屏 以及 脸不能按屏幕上的按钮,这些都是 Window 的标记来实现的。

二、WindowManager

WindowManager 在应用层。WindowManager 是继承了 ViewManager 的,这里发散以下, ViewGroup 也是实现了 ViewManager ,联动一下。

Activity 的 attach 方法里面,会创建 Window ,以及 绑定到 WindowManager 。

Activity 中 Window 下最直接管理的 View 是 DecorView ,示意图如下:

DecorView

注意,我们平时在代码中调用 setContentView 是我们上图蓝色部分的 ContentView 吗?答案不是的,我们 setContentView 的 View 到时候会作为上述 蓝色 ContentView 的子 View 。

我们都说onResume 的时候,是可见可操作的。这里其实应该是Activity 可见 ,就是 Activity 已经创建好了!但其实里面的元素还是不可见的,因为 performResume 之后,才会执行 window.addView 将 View 添加进去,所以,要注意区分。

WindowManagerGlobal 主要的职责是:

  • 设置view的参数(layoutparams)

  • 创建 ViewRootImpl

其中,WindowManagerGlobal 的 addView 方法中会创建 ViewRootImpl 的对象。

总结一下各方关系就是:每个 Activity 都有一个 PhoneWindow ,每个 PhoneWindow 中有个根 View——DecorView ,并且PhoneWindow 中有个 WindowManagerImpl 用于管理 Activity 中 VIew 的细节,然后 WindowManagerGlobal 用于全局管理所有的 WindowManagerImpl ,示意图如下所示:

Window各方关系如下

三、绘制流程

在 ViewRootImpl 中,接收到同步信号的时候,就会触发 performTraversalses() 方法,在里面会执行 performMeasure、performLayout、performDraw 等,

ViewRootImpl

ViewRootImpl 的功能主要体现在如下几个方面:

  • 作为 View 树的树根(注意,它自己并不是View,只是树根的意思而已)并管理 View 树

  • 触发 View的测量、布局和绘制

  • 输入响应的中转站

  • 负责与 WMS 进行进程间通信

关于输入响应的中转站,我们可以看如下图:

ViewRootImpl是输入的中转站

事件产生后,会传递到 ViewRootImpl 中,之后被传递给 DecorView ,我们根据这个步骤可以看到,这里面传递很弯弯绕绕,DecorView 传递给Acitivty ,第 3 步的时候,又回到了 DecorView 中。

为什么要这么设计呢?省略中间商赚差价直接到 ViewGroup 行不行?答案是不行。因为诸如打电话脸贴到屏幕上时需要黑屏,点击操作不能响应等操作, View 和 ViewGroup 上面没有相关的处理机制的,都在 PhoneWindow 里面,所以必须经过 PhoneWindow 。

view 或者 Window 的刷新都依赖于 ViewRootImpl 中的 scheduleTraversals 方法。

UI刷新

以 2 个问题开始:

  • TextView 连续 2 次setTextView ,那么会触发几次重绘?

  • Android 为什么要求帧率是 60?

答案:UI必须等待 16ms 的间隔才会绘制下一帧,所以连续2次 setTextView 只会触发一次重绘。同时必须每秒 60 帧(电影是 24 帧)用户才不会感觉卡顿,所以就 16ms 就要一帧。

关于刷新的整体流程我们可以参考如下图:

刷新整体流程

从图可以看出,setText 之后,就会触发 invalidate ,由于 View 自己不能刷新,需要向上请示老大,一直到 ViewRootImpl ,而 ViewRootImpl 自己也不能刷新,只能插入同步栅栏,等待下一次的 Vsync 信号(通过编舞者postcallback)来了之后再绘制。等到 Vsync 信号来了之后,我们会把同步栅栏给移除掉

小结

总结一下主要有几点需要注意:

  • Android 一般是 60FPS ,是 VSYNC 决定的,每 16ms 最多一帧

  • VSYNC 要客户端主动申请,才又 VSYNC 到来才会刷新

  • UI没有更改的话,不会请求 VSYNC 也就不会有刷新

  • UI局部重绘其实只会去重绘有更新的 View

SurfaceFlinger

SurfaceFlinger 是整个 Android 系统渲染的核心进程,整个流程是这样的:每个 DecorView 对应一个 Surface ,每个 Surface 里面包含一个 canvas ,每个 surface 对应一个 layer(图层), SurfaceFlinger 将各类的图层合成。

SurfaceFlinger 的整体流程如下图所示:

SurfaceFlinger整体流程

面试题

onResume 里面度量的宽高有效吗?

分情况。Activity 第一次调用 onResume 的时候是无效的,从 Activity A 跳转到 B 再返回来,这时候 A 的onResume 中是有效的。因为 onResume 的时候,还没有 执行 window.addView 呢。

Activity 、Window、view 三者的联系和区别。

先整体,再细节。所以先讲这3个是啥,Activity 是xxx,window 是xxx,View 是xxx。Activity 没有界面,委托给 Window 展示,

首次 View 的绘制流程是什么时候触发的?

WindowManagerImpl.addView -> WindowManagerGlobal.addView

ViewRootImpl.setView -> ViewRootImpl.requestLayout

ViewRootImpl.scheduleTraversals

这时候就触发了首次绘制

我们调用 invalidate() 之后会马上进行屏幕刷新吗?

一定不。需要等到下一个 Vsync 信号来了才会

我们说丢帧是因为主线程做了耗时操作,为什么做了耗时操作就会引起丢帧?

一言以蔽之:主线程的耗时操作会影响下一帧的绘制。我们知道有以下信息:

  • 在 ViewRootImpl 的 scheduleTravesals 方法中会去发送同步屏障,接着发送异步 Message,用于处理 UI 更新

  • 在Handler 机制中碰到 target == null 这种 Message 的时候,就知道这是同步屏障了,此后,就只执行异步 Message

  • 我们知道主线程耗时操作的体现形式也是 Message

  • 如果有耗时操作之前的消息有耗时操作,那么可能耗时导致推迟执行 同步屏障那个 Message ,也就推迟了后续的 异步 Message的执行,也就影响了下一帧的绘制

谢谢你的鼓励