以冷启动的场景看启动 TargetActivity 需要2个条件:
- TargetActivity 所在的进程需要启动
- 需要SourceActivity 的 completePause 流程
启动进程和completePause 流程是异步执行的,这2个流程都会执行到 ActivityTaskSupervisor::realStartActivityLocked 方法试图启动 TargetActivity ,但是因为需要同时满足上面的2个条件,所以肯定是后执行到这个方法的流程来触发 TargetActivity 的创建和启动。
不过 TargetActivity 的启动了并不能代表屏幕上就显示了UI数据,中间还有很多流程。
1.2 应用端显示窗口一级框图
一个应用想要将它的UI内容显示到屏幕窗口上,涉及到3个模块: 应用端,SystemService端和SurfaceFlinger端。
- 应用端的Activity启动后,会通过Session与SystemService端通信处理显示UI的逻辑
- 真正显示是SurfaceFlinger端是控制的,所以SystemService端还需要与SurfaceFlinger端通信
SurfaceFlinger控制屏幕显示,非常重要但不是现在分析的重点,当前阶段以黑盒的形式了解SurfaceFlinger端的概念即可,当前小节要知道SystemService会在relayoutoutWindow流程的时候创建一个Surface返回给客户端绘制。
在【Activity启动流程】的几篇记录里已经把Activity启动的主流程介绍了,也知道WMS最后做的事是触发了2个事务:LaunchActivityItem 和 ResumeActivityItem ,剩下的逻辑就又交给了应用端处理。
这两个事务后续的调用链如下:
1 | arduino复制代码LaunchActivityItem::execute |
其中 LaunchActivityItem 事务会触发 Activity 的创建,并且执行到 Activity 的 onCreate 生命周期。
ResumeActivityItem 事务则做了另外2件事:
- 执行到 Activity 的 onResume 生命周期
- 与WMS通信触发窗口的显示逻辑
可以看到Activity的创建和生命周期的执行与是否屏幕上有对应的窗口信息没有绝对的联系,因为屏幕上窗口UI的显示是在 ViewRootImpl::setView 方法中触发的。
1.3 应用端显示窗口二级框图
在App开发中一个View想要显示需要经过3个步骤,也就是View三部曲:Measure,Layout,Draw
对应的一个Window想要在屏幕上显示也需要经过3个步骤:
- addWindow : SystemService端为应用窗口创建对应的WindowState并且挂载到窗口树中
- relayoutWindow : 这一步会创建一个Surface返回给应用端进行绘制,并且触发WMS的各个窗口的位置摆放和窗口尺寸计算(relayout)
- finishDrawingWindow:这一步是应用端的View绘制完成后,Surface已经有UI信息了,需要通过SurfaceFlinger进行合成
因为是窗口是以 SystemService 端的角度来分析的,所以对比上面的框图,这3个步骤均是在WMS中完成的。而应用端会触发这WMS这3个步骤的执行,从框图中可以看到应用端这边多了一个“View绘制三部曲”的步骤。
这是因为经过第二步relayoutWindow后,应用端就有了一个可以用来绘制的Surface,所以就需要进行View的绘制了,屏幕上窗口显示的UI信息本质性就是View绘制的, View绘制后才可以进行第三步:finishDrawingWindow。
窗口显示的三部曲的触发点都是在 ResumeActivityItem 事务执行到 ViewRootImpl::setView 方法触发的,调用链如下:
1 | arduino复制代码ViewRootImpl::setView |
doTraversal 方法是异步执行,所以 Session.addToDisplayAsUser 触发的 addWindow 流程是比 relayoutWindow 先执行的。
上一篇介绍【addWindow流程】知道在 SystemService 端已经为应用窗口创建对应的WindowState并且挂载到窗口树中了,现在开始分析第二步:relayoutWindow。
relayoutWindow 流程主要做了2件事:
- 创建装载View绘制数据的Surface
- 触发窗口摆放,计算窗口位置
应用端通过 Session 与 SystemService 端通信,应用端的ViewRootImpl下有2个成员变量分别代码应用端的Surface和应用端的窗口尺寸信息。
这2个变量在跨进程通信时以“出参”的形式传递到 SystemService 端,经过 WindowManagerService::relayoutWindow 方法处理后应用端这2个变量就有值了。
本篇分析 relayoutWindow 的第一件事:创建装载View绘制数据的Surface
本篇的核心内容和窗口层级-4-Surface树中的 “Buff类型Surface的创建与挂载” 小结内容是一样的,但是本篇会先把前面的调用流程介绍完全。
在【addWindow流程】中分析了一个WindowState是如何创建并被挂载到层级树中的,但是WindowState本身也是一个“容器”,其对应的Surface也是一个“容器”类型的Surface,没有UI数据。
在 【WindowContainer窗口层级-4-Surface树】中也知道了SurfaceFlinger层也映射了一个Surface树,还知道了 “容器”类型和“Buff”类型Surface的区别。
下面完整介绍 relayoutWindow 流程是如何创建“Buff”类型Surface的。
2.1 应用端逻辑处理
开始撸代码,先看一下 ViewRootImpl::setView 方法
1 | java复制代码# ViewRootImpl |
- 首先看到 ViewRootImpl 下面有2个和Surface相关的变量 mSurface,mSurfaceControl。 但是点击去会发现都没什么东西,这是因为真正的Suface创建是在 SystemSerive 端触发
- 调用 addToDisplayAsUser 方法触发了addWindow 流程
- 本篇重点,触发 relayoutWindow
requestLayout 这个方法写App的同学可能比较熟悉,布局刷新的使用调用 View::requestLayout 虽然不是当前 ViewRootImpl 下的这个方法,但是最终也会触发 ViewRootImpl::requestLayout 的执行。
所以看看ViewRootImpl::requestLayout的代码
1 | typescript复制代码# ViewRootImpl |
这个方法主要是做了2件事
- 线程检查,可以看到checkThread() 方法的报错很多写App的同学就很熟悉: 不能在子线程更新UI。
- 执行 scheduleTraversals()
1 | scss复制代码# ViewRootImpl |
这个方法虽然代码不多,但是还是有不少知识点的,比如: 同步屏障,Vsync,感兴趣的自行了解,当前不做拓展。
当前只要知道当下一个软件Vsync到来的时候,会执行 TraversalRunnable 这个 Runnable 就好,所以重点看看这个 TraversalRunnable 做了什么。
前面看 ViewRootImpl::setView 方法的时候看到在代码顺序上是先执行 requestLayout 再执行 addToDisplayAsUser,就是因为 requestLayout 方法内部需要等待 Vsync 的到来,并且还是异步执行Runable,所以 addToDisplayAsUser 触发的 addWindow 流程是先于 relayoutWindow 流程执行的。
1 | scss复制代码# ViewRootImpl |
这里移除了同步屏障,那么 mHandler 就可以正常处理后面的消息了, 主要流程还是在 performTraversals() 中,这个方法非常重要。
我手上android 13的源码中这个方法有1890行。 所以我省略了很多代码,保留了个人认为和当前学习相关的一些逻辑,本篇重点看注释的第2步relayoutWindow。
1 | ini复制代码# ViewRootImpl |
- 后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法
- relayoutWindow 相关,也是当前分析重点
- 经过第第二步relayoutWindow后就View就可以绘制了,也是需要分析的重点流程,后面会陆续写博客
- 绘制完成后就要通知SurfaceFlinger进行合作了,finishDrawing流程也很重要。
上面的分析有个印象就好,当前不关注其他,只看 relayoutWindow 流程,关心的太多没有重点分析对象就很容易跑偏。
2.2 ViewRootImpl::relayoutWindow
ViewRootImpl::relayoutWindow方法如下:
1 | java复制代码# ViewRootImpl |
这个方法主要就是跨进程通信触发 WMS的relayoutWindow流程,注意这里将 mTmpFrames 和 mSurfaceControl 作为参数传递了过去。执行这个方法前 mSurfaceControl 只是一个没有实际内容的对象,但是经过 WMS::relayoutWindow 流程处理后,mSurfaceControl 就会真正持有一个 native层的Surface句柄,有个这个native的Surface句柄,View就可以把图像数据保存到Surface中了。
而mTmpFrames 表示临时窗口大小,和 mSurfaceControl 一样传递给WMS处理,然后由WMS计算出一个值再返回到应用端。
上面这个方法我标记了3个重点,除了第一个重点是主流程外,后面2个重点都是在应用端真正拿到Surface后的处理,比如第三点会把Surface设置给硬绘渲染器。重点二是给ViewRootImpl下的mSurface赋值,执行完后这个 mSurface也就持有一个 native层的Surface句柄。
在看主流程之前先看一下ViewRootImpl::updateBlastSurfaceIfNeeded 方法:
1 | scss复制代码# ViewRootImpl |
现在知道 WMS::relayoutWindow 流程执行后拿应用端拿到到Surface的一些处理,需要回头正式看一下 relayoutWindow到底做了什么。
2.3 WindowManagerService::relayoutWindow
应该端通过Session 与 SystemService 端通信
1 | typescript复制代码# Session |
主要就是调用到通过Session调用到WMS的relayoutWindow流程,上面看到在ViewRootImpl的mSurface和mSurfaceControl对象都是直接创建的,然后将mSurfaceControl专递到了WMS,这里注意在 Session::relayout 方法的参数中应用端传过来的 mSurfaceControl 变成了:outSurfaceControl,说明这是个出参,会在 WMS::relayoutWindow 对其进行真正的赋值。
outFrames 参数也同理。
WindowManagerService::relayoutWindow 代码如下:
1 | java复制代码# WindowManagerService |
方法开始 就执行了 windowForClientLocked 方法是从mWindowMap去获取WindowState,mWindowMap在【addWindow流程】讲过了。
然后 setViewVisibility 设置可见性了,这里的参数是传过来的,根据打印的ProtoLog:
1 | ini复制代码09-25 14:10:36.963 10280 16547 I WindowManager: Relayout Window{2fa12a u0 com.example.myapplication/com.example.myapplication.MainActivity2}: oldVis=4 newVis=0. java.lang.RuntimeException |
值为0,也就是VISIBLE了。
这个方法在WMS中也是个核心方法了,注释都在代码中了,当前分析的 relayoutWindow 流程,所以主要跟踪下面2个执行逻辑:
- createSurfaceControl : 创建“Buff”类型的Surface
- performSurfacePlacement :计算窗口大小 (View一般有变化也要执行 layout,WMS在管理窗口这边肯定也要执行layout)
- fillClientWindowFramesAndConfiguration :将计算好的窗口尺寸返回给应用端
由于篇幅原因,本篇先介绍 createSurfaceControl 这个分支是如何创建 Surface的。
2.3.1 了解“容器”和“Buff”类型的Surface
看调用栈一般除了debug外,还可以在关键点加上堆栈,比如在SurfaceControl的构造方法加堆栈,只要有触发创建SurfaceControl的地方必然会打印,然后发现有以下2个输出(模拟的场景是在MainActivity点击按钮启动MainActivity2)
addWindow触发的堆栈
1 | yaml复制代码09-25 19:42:46.028 13422 14723 E biubiubiu: SurfaceControl mName: 4e72d78 com.example.myapplication/com.example.myapplication.MainActivity2 mCallsiteWindowContainer.setInitialSurfaceControlProperties |
relayoutWindow触发的堆栈
1 | yaml复制代码09-25 19:42:46.036 13422 14723 E biubiubiu: SurfaceControl mName: com.example.myapplication/com.example.myapplication.MainActivity2 mCallsiteWindowSurfaceController |
发现2个地方创建了SurfaceControl,而且看名字都是为MainActivity2创建的,区别就是调用栈不同,和一个是带 “4e72d78 “这种对象名的,这让我很好奇,然后我立马想到这种类型之前在窗口层级树中见过。于是dump了层级树的信息
果然就是以WindowState的名字取的,看调用栈在addWindow的时候将这个WindowState添加到层级树的时候就创建了。后面的“mCallsiteWindowContainer.setInitialSurfaceControlProperties”2个调用栈输出的也不同,代表的是调用的地方。
这就很奇怪了,在addWindow的时候就创建好了SurfaceControl为什么执行relayoutWindow的时候又创建一个?那到底是用的哪个呢?
我用winscope看了trace后发现原来是下面这个结构:
原来下面创建的才是真正可见的,而带 “4e72d78 “的则是作为parent,dump一下SurfaceFlinger看一下
发现带”4e72d78 “ 的是ContainerLayer类型,而下面的是BufferStateLayer类型,也是作为起孩子的存在,我们知道BufferStateLayer类型的才是真正绘制显示数据的Surface。
容器类型的图层不能显示只能作为容器,只有BufferStateLayer才可以作为显示图层
原来在addWindow流程中,将WindowState挂在到层级树中就创建了一个容器类型的SurfaceControl,而后在执行WindowManagerService::relayoutWindow又创建了一个BufferStateLayer类型的SurfaceControl用来做真正的显示数据。
这块的内容和后面的内容其实在【WindowContainer窗口层级-4-Surface树】也介绍过了,关于“容器”类型比如WindowState的Surface是如何创建就不再重复了,但是“Buff”类型Surface的创建还是需要再说一遍,比较是本篇分析的重点。看过并且熟悉这一流程的可以忽略 2.4 小节
2.4 Buff类型Surface的创建与挂载
2.4.1 流程概览
relayoutWindow的调用链如下:
1 | arduino复制代码WindowManagerService::relayoutWindow |
开始撸代码,WindowManagerService::relayoutWindow 下调用 createSurfaceControl 方法有4个参数
1 | csharp复制代码# WindowManagerService |
createSurfaceControl 方法有4个参数:
- outSurfaceControl: WMS创建好一个Surface后,还需要返回给应用端用于View的绘制,就是通过这个参数,由参数命名也可以知道这是一个“出参”。
- result: 方法执行结果
- win: 当前窗口对应的WindowState,稍后创建Surface会挂载到这个WindowState节点之下
- winAnimator:WindowStateAnimator对象,管理窗口状态和动画,稍后通过其内部方法创建Surface
1 | csharp复制代码# WindowManagerService |
这个方法主要有三步,都是围绕着 WindowSurfaceController 来的:
- 先创建出一个WindowSurfaceController 对象 surfaceController
- 通过WindowStateAnimator::createSurfaceLocked 对 surfaceController 赋值,根据方法名猜测是创建了一个Surface
- 通过 WindowSurfaceController::getSurfaceControl,给应用端 Surface 赋值
这么看来重点是在第二步 WindowStateAnimator::createSurfaceLocked 是如何创建Surface的。
1 | csharp复制代码# WindowStateAnimator |
这里有2个重点:
- 设置窗口状态为 DRAW_PENDING
- 创建Surface
2.4.2 设置窗口状态–DRAW_PENDING
1 | ini复制代码# WindowStateAnimator |
WindowState有很多状态,以后会单独说,这里需要注意
- WindowState状态是保存在WindowStateAnimator中
- WindowStateAnimator::createSurfaceLocked方法会将WindowState状态设置为DRAW_PENDING,表示等待绘制。
2.4.3 创建与挂载“Buff”类型Surface
继续回到主流程,看看 WindowSurfaceController 的构造方法
1 | ini复制代码# WindowSurfaceController |
这个方法有4个点
- 第一个参数传递的字符串最终也会作为Surface的name
- 获取到WindowState对象,后面会设置为创建Surface的父节点
- 构建出一个Surface对象, 注意name和 父节点的设置。 另外可以知道也是通过makeSurface()方法构建的, 这个方法会构建出一个“容器”类型的Surface。
- 将Surface设置为“Buff”类型,这个非常重要,因为上一步默认还是“容器”类型,所以需要设置成“Buff”类型,再后面就是build出一个Surface了
makeSurface() 方法是如何构建Surface的需要移步【WindowContainer窗口层级-4-Surface树】看第二小节:2 容器类型的创建,就不重复介绍了。
那么到这里Surface的创建就完成了,这里可能有的人如果对Surface知识不太清楚的话会比较迷糊,WindowSurfaceController,SurfaceController,Surface到底是什么关系,这个不在当前流程的重点,暂且理解为同级吧,有WindowSurfaceController就可以拿到内部的SurfaceController,而SurfaceController又可以获取到Surface。
2.4.4 返回Surface到应用端
最后再来看一下 WMS这边创建好后的Surface是如何设置给应用端的。
应用端View的绘制信息都是保存到Surface上的,因为必定要有一个”Buff”类型的Surface,也就是上面流程中创建的这个Surface。
应用端的ViewRootImpl触发WMS的relayoutWindow会传递一个出参 :outSurfaceControl过来, 现在WMS会通过以下方法将刚刚创建好是Surface传递到应用端。
这样一来应用端就有了可以保持绘制数据的Surface,然后就可以执行 View::draw。
1 | csharp复制代码# WindowSurfaceController |
2.4.5 创建Surface小结
对于Surface的知识是一个复杂的模块,是需要单独详细讲解的,目前可以知道的是原以为给WindowState创建图层就是一个,但是实际上发现创建了2个。
- WindowState本身对应的是“容器”类型的Surface,在“addWindow流程”就创建了,而relayoutWindow创建的是一个“BufferStateLayer”类型的Surface,这个也是被copy到应用层的Surface,说明应用层的数据是被绘制在这个Surface上的。
- “BufferStateLayer”类型Surface的创建其实创建一个WindowSurfaceController对象,然后内部会创建SurfaceController。从WindowSurfaceController这个类名也能看出来是针对Window显示的。
- 不仅仅Framework层的层级树有容器概念,SurfaceFlinger里也有容器概念
- 我们在执行adb shell dumpsys activity containers 看到层级结构树,最底层的WindowState其实也是个容器,不是真正显示的地方。这个点从 “containers”也能理解,毕竟是容器树。
2.4.5.1 WindowState “容器”概念拓展
WindowState是容器这个是肯定的,也是WindowContainer子类,然后他的孩子也是WindowState定义如下:
1 | scala复制代码# WindowState |
那么什么场景下WindowState下还有孩子呢?答案是子窗口,子窗口的定义在Window类型里,具体的不在当前讨论,之前我一直有个误区,我一直以为我们弹出的Dialog是子窗口,但是实际上并不是,我目前找到了一个比较常见的子窗口是PopupWindow。
以在google电话应用打开一个菜单为例
对应的dump 为
看的到子窗口PopupWindow的WindowState是被挂载到Activity的WindowState下
对应的winscope trace为:
这里能看到PopupWindow也有一个容器图层和显示图层,容器图层挂载在Activity窗口容器图层下,和Activity窗口显示图层同级
本篇介绍了应用端发起relayoutWindow的逻辑,然后介绍了一些“容器”和“Buff”类型Surface的概念,知道了 relayoutWindow 流程主要是做2件事,本篇介绍了第一件事:创建Surface。下一篇开始分析窗口尺寸计算和摆放流程。
本文转载自: 掘金