当前位置: 首页> 汽车> 新车 > Android Framework(五)WMS-窗口显示流程——窗口布局与绘制显示

Android Framework(五)WMS-窗口显示流程——窗口布局与绘制显示

时间:2025/7/11 10:42:37来源:https://blog.csdn.net/baiduwaimai/article/details/142033047 浏览次数: 0次

文章目录

  • relayoutWindow流程概览
  • 应用端处理——ViewRootImpl::setView -> relayoutWindow
    • ViewRootImpl::setView
    • ViewRootImpl::performTraversals
    • ViewRootImpl::relayoutWindow
  • Surface的创建
    • WindowManagerService::relayoutWindow
    • 了解容器类型和Buff类型的Surface
    • Buff类型Surface的创建与挂载
      • 设置窗口状态——DRAW_PENDIND
      • 创建与挂载“Buff”类型Surface
    • 创建Surface小结
    • WindowState “容器”概念拓展
  • 计算窗口大小与Surface放置

relayoutWindow流程概览

addWindow 流程在上一篇已经分析完了,现在 WindowManagerService 中已经有一个 WindowState 了并且也挂载到层级树中了。
但是一个窗口想要有 UI 内容需要底下的 View 树完成绘制,而 View 的绘制必须要有一个 Surface ,并且要进行绘制还需要自己的窗口在屏幕上的位置和宽高等信息。
这就是第二步 relayoutWindow 流程要做的2件事:

  • 1、为窗口申请Surface并返回给应用端
  • 2、计算返回窗口的大小,位置信息并返回给应用端。

整体流程框图如下:
在这里插入图片描述

  • ViewRootImpl 下有3个成员变量

    • mSurfaceControl 是应用端控制 Surface 的类
    • mTmpFrames 是应用端临时保存最新窗口尺寸信息的类
    • mWinFrame 是应用端真正保存窗口尺寸信息的类
  • 在触发 relayoutWindow 流程时,mSurfaceControl 和 mTmpFrames 会以出参的形式传递,在 system_service 端进行赋值

  • WindowManagerService 会与 SurfaceFlinger 通信创建 Surface 并返回给应用端

  • WindowManagerService 还会执行一次 layout 流程来重新计算所有窗口的位置和大小,并将当前这个窗口的大小位置信息返回给应用端,并设置给 mWinFrame

  • relayoutWindow 流程处理的2件事将分为2篇进行分析,本篇分析第一个处理:Surface 的创建,以及应用端的处理。

应用端处理——ViewRootImpl::setView -> relayoutWindow

回顾下应用的调用链: 窗口显示的三部曲的触发点都是在 ResumeActivityItem 事务执行到 ViewRootImpl::setView 方法触发的,调用链如下:

ViewRootImpl::setViewViewRootImpl::requestLayoutViewRootImpl::scheduleTraversals             ViewRootImpl.TraversalRunnable::run          -- Vsync相关--scheduleTraversalsViewRootImpl::doTraversalViewRootImpl::performTraversals ViewRootImpl::relayoutWindow        -- 第二步:relayoutWindowSession::relayout                -- 跨进程执行 relayoutWindow流程ViewRootImpl::updateBlastSurfaceIfNeededSurface::transferFrom         -- 应用端Surface赋值ViewRootImpl::setFrame           -- 应用端窗口大小赋值ViewRootImpl::performMeasure        -- View绘制三部曲ViewRootImpl::performLayoutViewRootImpl::performDraw        ViewRootImpl::createSyncIfNeeded    --- 第三步:绘制完成 finishDrawingWindowSession.addToDisplayAsUser                     --- 第一步:addWindow

应用端的逻辑还是从 ViewRootImpl::setView 方法开始看。

ViewRootImpl::setView

# ViewRootImpl// 重点* 1. 应用端这个View树的 Surfacepublic final Surface mSurface = new Surface();// 对应的SurfaceControlprivate final SurfaceControl mSurfaceControl = new SurfaceControl();// 临时保存最新的窗口信息private final ClientWindowFrames mTmpFrames = new ClientWindowFrames();// 当前窗口大小final Rect mWinFrame; // frame given by window manager. final IWindowSession mWindowSession;public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {synchronized (this) {// 当前第一次执行肯定为nullif (mView == null) {mView = view;......int res; // 定义稍后跨进程add返回的结果// 重点* 3. 第二步:会触发relayoutWindowrequestLayout();  InputChannel inputChannel = null; // input事件相关if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}......try {......// 重点* 2. 第一步:addWindow流程res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,mTempControls);......}// 后续流程与addWindow主流程无关,但是也非常重要......// 计算window的尺寸......if (res < WindowManagerGlobal.ADD_OKAY) {......// 对WMS调用后的结果判断是什么错误}......// DecorView::getParent 返回的是 ViewRootImpl 的原因view.assignParent(this);......}}}
  • 1、首先看到 ViewRootImpl 下面有2个和Surface相关的变量 mSurface,mSurfaceControl。 但是点击去会发现都没什么东西,这是因为真正的 Suface 创建是在 system_service 端触发
  • 2、调用 addToDisplayAsUser 方法触发了addWindow 流程
  • 3、本篇重点,触发 relayoutWindow

requestLayout 这个方法写App的同学可能比较熟悉,布局刷新的使用调用 View::requestLayout 虽然不是当前 ViewRootImpl 下的这个方法,但是最终也会触发 ViewRootImpl::requestLayout 的执行。

看看 ViewRootImpl::requestLayout 的代码:

# ViewRootImplboolean mLayoutRequested;@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {......// 只有主线程才能更新UIcheckThread();// 正确请求layoutmLayoutRequested = true;scheduleTraversals();}}void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}

这个方法主要是做了2件事:

  • 线程检查,可以看到 checkThread() 方法的报错很多写App的同学就很熟悉: 不能在子线程更新UI。
  • 执行 scheduleTraversals()
# ViewRootImplfinal TraversalRunnable mTraversalRunnable = new TraversalRunnable();//  是否在执行scheduleTraversalspublic boolean mTraversalScheduled;void scheduleTraversals() {// 如果遍历操作尚未被调度 if (!mTraversalScheduled) {// 将调度标志设置为true,表示遍历操作已被调度mTraversalScheduled = true;// 设置同步屏障 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 重点 * 执行mTraversalRunnablemChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);// 通知渲染器有一个新的帧即将开始处理notifyRendererOfFramePending();// 根据需要戳一下绘制锁pokeDrawLockIfNeeded();}}

这个方法虽然代码不多,但是还是有不少知识点的,比如: 同步屏障和Vsync,感兴趣的自行了解,当前不做拓展。
当前只要知道当下一个 VSync-app 到来的时候,会执行 TraversalRunnable 这个 Runnable 就好,所以重点看看这个 TraversalRunnable 做了什么。

前面看 ViewRootImpl::setView 方法的时候看到在代码顺序上是先执行 requestLayout 再执行 addToDisplayAsUser,就是因为 requestLayout 方法内部需要等待 Vsync 的到来,并且还是异步执行 Runable ,所以 addToDisplayAsUser 触发的 addWindow 流程是先于 relayoutWindow 流程执行的。

# ViewRootImplfinal class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}void doTraversal() {if (mTraversalScheduled) {// 正在执行或已经执行完毕 mTraversalScheduled = false;// 移除同步屏障mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);.....performTraversals();......}}

这里移除了同步屏障,那么 mHandler 就可以正常处理后面的消息了, 主要流程还是在 performTraversals() 中,这个方法非常重要。

ViewRootImpl::performTraversals

我手上 android 14 的源码中这个方法有 1890 行。 所以我省略了很多代码,保留了个人认为和当前学习相关的一些逻辑,本篇重点看注释的第2步 relayoutWindow 。

# ViewRootImplprivate SurfaceSyncGroup mActiveSurfaceSyncGroup;private void performTraversals() {......// mWinFrame保存的是当前窗口的尺寸Rect frame = mWinFrame;----1.1 硬绘相关----// 硬件加速是否初始化boolean hwInitialized = false;......----2. relayoutWindow流程----// 内部会将经过WMS计算后的窗口尺寸给mWinFramerelayoutResult = relayoutWindow(params, viewVisibility, insetsPending);......// 1.2 初始化硬件加速,将Surface与硬件加速绑定hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);......----3. View绘制三部曲----performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);......performLayout(lp, mWidth, mHeight);......----4. finishDrawing流程----createSyncIfNeeded();......   mActiveSurfaceSyncGroup.markSyncReady();......}
  • 1、后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法

  • 2、relayoutWindow 相关,也是当前分析重点

  • 3、经过第第二步 relayoutWindow 后就 View 就可以绘制了,也是需要分析的重点流程,后面会陆续写博客

  • 4、绘制完成后就要通知 SurfaceFlinger 进行合作了,finishDrawing 流程也很重要。

上面的分析有个印象就好,当前不关注其他,只看 relayoutWindow 流程,关心的太多没有重点分析对象就很容易跑偏。

ViewRootImpl::relayoutWindow

ViewRootImpl::relayoutWindow 方法如下:

# ViewRootImplpublic final Surface mSurface = new Surface();private final SurfaceControl mSurfaceControl = new SurfaceControl();// 临时保存最新的窗口信息private final ClientWindowFrames mTmpFrames = new ClientWindowFrames();// 当前窗口大小final Rect mWinFrame; // frame given by window manager. private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,boolean insetsPending) throws RemoteException {......int relayoutResult = 0;if (relayoutAsync) {// U 新增,暂时忽略mWindowSession.relayoutAsync(....);} else {// 重点* 1. 调用WMS的 relayoutWindow流程relayoutResult = mWindowSession.relayout(mWindow, ...,mTmpFrames, ..., mSurfaceControl,...);}......if (mSurfaceControl.isValid()) {if (!useBLAST()) {mSurface.copyFrom(mSurfaceControl);} else {// 重点* 2. 给mSurface赋值updateBlastSurfaceIfNeeded(); // 目前版本都走这}if (mAttachInfo.mThreadedRenderer != null) {// 注意* 置硬件加速渲染器的 SurfaceControl 和 BlastBufferQueuemAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);}} else ............// 重点* 3.将WMS计算的窗口大小设置到当前setFrame(mTmpFrames.frame, true /* withinRelayout */);return relayoutResult;}
  • 1、跨进程通信触发 relayoutWindow 流程,注意这里将 mTmpFrames 和 mSurfaceControl 作为参数传递了过去。执行这个方法前 mSurfaceControl 只是一个没有实际内容的对象,但是经过 WMS::relayoutWindow 流程处理后,mSurfaceControl 就会真正持有一个 native 层的 Surface 句柄,有个这个 native 的 Surface 句柄,View 就可以把图像数据保存到Surface 中了。
  • 2、将 mSurfaceControl 下的 Surface 赋值给当前的变量 mSurface
  • 3、relayoutWindow 流程后 mTmpFrames 就有最新的尺寸信息了,需要赋值给真正保存窗口尺寸的变量 mWinFrame

在看主流程之前先看一下 ViewRootImpl::updateBlastSurfaceIfNeeded 方法:

# ViewRootImplprivate BLASTBufferQueue mBlastBufferQueue;void updateBlastSurfaceIfNeeded() {// 经过system_service处理后的mSurfaceControl有值if (!mSurfaceControl.isValid()) {return;}......// 创建对象mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);Surface blastSurface = mBlastBufferQueue.createSurface();// Only call transferFrom if the surface has changed to prevent inc the generation ID and// causing EGL resources to be recreated.// 给当前mSurface赋值mSurface.transferFrom(blastSurface);}

现在知道了 relayoutWindow 流程执行后拿应用端拿到到 Surface 的和尺寸信息一些处理,需要回头正式看一下 relayoutWindow 流程到底做了什么。

Surface的创建

在【WindowContainer窗口层级-Surface树】中提过 SurfaceFlinger 层也映射了一个 Surface 树,还知道了“容器”类型和“Buff”类型 Surface 的区别。

只有“Buff”类型 Surface 才可以显示 UI 内容,relayoutWindow 流程的目的就是为创建创建一个“Buff”类型 Layer 。

addWindow 后 SurfaceFlinger 层也是创建的 WindowState 对应的 Layer ,但是实际上 WindowState 下面还有一个“Buff”类型 Layer ,这一步就是 relayoutWindow 流程创建的。

在这里插入图片描述
下面完整介绍 relayoutWindow 流程是如何创建“Buff”类型 Layer 的。

WindowManagerService::relayoutWindow

应用端端通过 Session 与 system_service 端通信。

# Session@Overridepublic int relayout(IWindow window, ...ClientWindowFrames outFrames,...SurfaceControl outSurfaceControl,...) {......int res = mService.relayoutWindow(this, window, attrs,requestedWidth, requestedHeight, viewFlags, flags,outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,outActiveControls, outSyncSeqIdBundle);......return res;}

主要就是调用到通过 Session 调用到 WindowManagerService::relayoutWindow 方法,上面看到 ViewRootImpl 的 mSurface 和mSurfaceControl 对象都是直接创建的,然后将mSurfaceControl 专递到了 WMS ,这里注意在 Session::relayout 方法的参数中应用端传过来的 mSurfaceControl 变成了:outSurfaceControl,说明这是个出参会在 WindowManagerService::relayoutWindow 方法对其进行真正的赋值。

WindowManagerService::relayoutWindow代码如下:

# WindowManagerServicepublic int relayoutWindow(Session session, IWindow client, LayoutParams attrs,int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,int lastSyncSeqId, ClientWindowFrames outFrames,MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,Bundle outSyncIdBundle) {......synchronized (mGlobalLock) {// 重点* 从mWindowMap中获取WindowStatefinal WindowState win = windowForClientLocked(session, client, false);if (win == null) {return 0;}......if (viewVisibility != View.GONE) {// 把应用端请求的大小,保存到WindowState下win.setRequestedSize(requestedWidth, requestedHeight);}......if (attrs != null) {// 调整窗口属性和类型displayPolicy.adjustWindowParamsLw(win, attrs);......}.......// 设置窗口可见 viewVisibility = VISIBLEwin.setViewVisibility(viewVisibility);// 打印Proto日志ProtoLog.i(WM_DEBUG_SCREEN_ON,"Relayout %s: oldVis=%d newVis=%d. %s", win, oldVisibility,viewVisibility, new RuntimeException().fillInStackTrace());......if (shouldRelayout && outSurfaceControl != null) {try {// 重点* 1. 创建SurfaceControlresult = createSurfaceControl(outSurfaceControl, result, win, winAnimator);} catch (Exception e) {......return 0;}}// 重点* 2. 计算窗口大小 (极其重要的方法)mWindowPlacerLocked.performSurfacePlacement(true /* force */);......if (focusMayChange) {// 更新焦点if (updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/)) {imMayMove = false;}}......// 重点* 3. 填充WMS计算好后的数据,返回应用端win.fillClientWindowFramesAndConfiguration(outFrames, outMergedConfiguration,false /* useLatestConfig */, shouldRelayout);......}Binder.restoreCallingIdentity(origId);return result;}
  • 1、WindowManagerService::windowForClientLocked 方法是从 mWindowMap 去获取 WindowState ,这就体现出 addWindow 流程中首次看到 mWindowMap 的重要性了。

然后 setViewVisibility 设置可见性了,这里的参数是传过来的,根据打印的 ProtoLog:

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 流程,所以主要跟踪下面3个执行逻辑:

  • 1、createSurfaceControl : 创建“Buff”类型的Surface
  • 2、performSurfacePlacement :窗口的摆放 (View一般有变化也要执行 layout,WMS在管理窗口这边肯定也要执行layout)
  • 3、fillClientWindowFramesAndConfiguration :将计算好的窗口尺寸返回给应用端

了解容器类型和Buff类型的Surface

看调用栈一般除了debug外,还可以在关键点加上堆栈,比如在SurfaceControl的构造方法加堆栈,只要有触发创建SurfaceControl的地方必然会打印,然后发现有以下2个输出(模拟的场景是在MainActivity点击按钮启动MainActivity2)
addWindow触发的堆栈:

09-25 19:42:46.028 13422 14723 E biubiubiu: SurfaceControl mName: 4e72d78 com.example.myapplication/com.example.myapplication.MainActivity2  mCallsiteWindowContainer.setInitialSurfaceControlProperties
09-25 19:42:46.028 13422 14723 E biubiubiu: java.lang.Exception
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at android.view.SurfaceControl.<init>(SurfaceControl.java:1580)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at android.view.SurfaceControl.<init>(Unknown Source:0)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at android.view.SurfaceControl$Builder.build(SurfaceControl.java:1240)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowContainer.setInitialSurfaceControlProperties(WindowContainer.java:630)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowContainer.createSurfaceControl(WindowContainer.java:626)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowContainer.onParentChanged(WindowContainer.java:607)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowContainer.onParentChanged(WindowContainer.java:594)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowState.onParentChanged(WindowState.java:1341)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowContainer.setParent(WindowContainer.java:584)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowContainer.addChild(WindowContainer.java:730)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowToken.addWindow(WindowToken.java:302)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.ActivityRecord.addWindow(ActivityRecord.java:4248)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowManagerService.addWindow(WindowManagerService.java:1814)
09-25 19:42:46.028 13422 14723 E biubiubiu: 	at com.android.server.wm.Session.addToDisplayAsUser(Session.java:215)

relayoutWindow触发的堆栈:

09-25 19:42:46.036 13422 14723 E biubiubiu: SurfaceControl mName: com.example.myapplication/com.example.myapplication.MainActivity2  mCallsiteWindowSurfaceController
09-25 19:42:46.036 13422 14723 E biubiubiu: java.lang.Exception
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at android.view.SurfaceControl.<init>(SurfaceControl.java:1580)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at android.view.SurfaceControl.<init>(Unknown Source:0)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at android.view.SurfaceControl$Builder.build(SurfaceControl.java:1240)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowSurfaceController.<init>(WindowSurfaceController.java:109)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowStateAnimator.createSurfaceLocked(WindowStateAnimator.java:335)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowManagerService.createSurfaceControl(WindowManagerService.java:2686)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at com.android.server.wm.WindowManagerService.relayoutWindow(WindowManagerService.java:2449)
09-25 19:42:46.036 13422 14723 E biubiubiu: 	at com.android.server.wm.Session.relayout(Session.java:267)

发现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 。

原来在 addWindow 流程中,将 WindowState 挂在到层级树中就创建了一个容器类型的 SurfaceControl ,而后在执行 WindowManagerService::relayoutWindow 又创建了一个BufferStateLayer 类型的 SurfaceControl 用来做真正的显示数据。

Buff类型Surface的创建与挂载

relayoutWindow的调用链如下:

WindowManagerService::relayoutWindowWindowManagerService::createSurfaceControlWindowStateAnimator::createSurfaceLocked -- 创建“Buff” 类型LayerWindowStateAnimator::resetDrawState   -- DRAW_PENDINGWindowSurfaceController::initSurfaceControl.Builder::buildSurfaceControl::initWindowSurfaceController::getSurfaceControl  -- 给应用端Surface赋值

在这里插入图片描述
开始撸代码,WindowManagerService::relayoutWindow 下调用 createSurfaceControl 方法有4个参数。

# WindowManagerService public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,int requestedWidth, int requestedHeight, int viewVisibility, int flags,ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,SurfaceControl outSurfaceControl, InsetsState outInsetsState,InsetsSourceControl[] outActiveControls, Bundle outSyncIdBundle) {......result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);......}

createSurfaceControl 方法有4个参数:

  • outSurfaceControl: WMS 创建好一个 Surface 后,还需要返回给应用端用于 View 的绘制,就是通过这个参数,由参数命名也可以知道这是一个“出参”。
  • result: 方法执行结果
  • win: 当前窗口对应的WindowState,稍后创建Surface会挂载到这个WindowState节点之下
  • winAnimator:WindowStateAnimator对象,管理窗口状态和动画,稍后通过其内部方法创建Surface
# WindowManagerServiceprivate int createSurfaceControl(SurfaceControl outSurfaceControl, int result,WindowState win, WindowStateAnimator winAnimator) {if (!win.mHasSurface) {result |= RELAYOUT_RES_SURFACE_CHANGED;}// 1. 创建WindowSurfaceController对象WindowSurfaceController surfaceController;try {// 2. 创建“Buff”类型SurfaceTrace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "createSurfaceControl");surfaceController = winAnimator.createSurfaceLocked();} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}if (surfaceController != null) {// 3. 出参给应用端surfaceController.getSurfaceControl(outSurfaceControl);// 打印日志,outSurfaceControl复制到了framework的值ProtoLog.i(WM_SHOW_TRANSACTIONS, "OUT SURFACE %s: copied", outSurfaceControl);}......return result;}

这个方法主要有三步,都是围绕着 WindowSurfaceController 来的:

  • 1、先创建出一个WindowSurfaceController 对象 surfaceController
  • 2、通过 WindowStateAnimator::createSurfaceLocked 对 surfaceController 赋值,根据方法名猜测是创建了一个 Surface
  • 3、通过 WindowSurfaceController::getSurfaceControl,给应用端 Surface 赋值

这么看来重点是在第二步 WindowStateAnimator::createSurfaceLocked 是如何创建 Surface 的。

# WindowStateAnimatorWindowSurfaceController mSurfaceController;// WindowState的状态int mDrawState;WindowSurfaceController createSurfaceLocked() {final WindowState w = mWin;if (mSurfaceController != null) {return mSurfaceController;}w.setHasSurface(false);// 打印窗口状态ProtoLog.i(WM_DEBUG_ANIM, "createSurface %s: mDrawState=DRAW_PENDING", this);// 重点* 1. 重置窗口状态  -- DRAW_PENDINGresetDrawState();......// 重点* 2. 创建WindowSurfaceControllermSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,flags, this, attrs.type);......return mSurfaceController;}

这里有2个重点:

  • 1、设置窗口状态为 DRAW_PENDING
  • 2、创建Surface

设置窗口状态——DRAW_PENDIND

# WindowStateAnimatorvoid resetDrawState() {// 设置windowState状态为DRAW_PENDINGmDrawState = DRAW_PENDING;if (mWin.mActivityRecord == null) {return;}if (!mWin.mActivityRecord.isAnimating(TRANSITION)) {mWin.mActivityRecord.clearAllDrawn();}}

WindowState有很多状态,以后会单独说,这里需要注意:

  • 1、WindowState 状态是保存在 WindowStateAnimator 中
  • 2、WindowStateAnimator::createSurfaceLocked 方法会将 WindowState 状态设置为 DRAW_PENDING 表示等待绘制

创建与挂载“Buff”类型Surface

# WindowSurfaceControllerSurfaceControl mSurfaceControl;WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,int windowType) {mAnimator = animator;// 1. 也会作为Surface的nametitle = name;mService = animator.mService;// 2. 拿到WindowStatefinal WindowState win = animator.mWin;mWindowType = windowType;mWindowSession = win.mSession;Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");// 3. 重点* 构建Surface(也是通过makeSurface 方法)final SurfaceControl.Builder b = win.makeSurface().setParent(win.getSurfaceControl()) // 设置为父节点.setName(name) //设置name.setFormat(format).setFlags(flags).setMetadata(METADATA_WINDOW_TYPE, windowType).setMetadata(METADATA_OWNER_UID, mWindowSession.mUid).setMetadata(METADATA_OWNER_PID, mWindowSession.mPid).setCallsite("WindowSurfaceController");final boolean useBLAST = mService.mUseBLAST && ((win.getAttrs().privateFlags& WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0);// 高版本都为BLASTif (useBLAST) {// 4. 重点* 设置为“Buff”图层b.setBLASTLayer();}// 触发buildmSurfaceControl = b.build();Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}

分我4步:

  • 1、第一个参数传递的字符串最终也会作为Surface的name
  • 2、获取到 WindowState 对象,后面会设置为创建 Surface 的父节点
  • 3、构建出一个 Surface 对象, 注意 name 和父节点的设置。 另外可以知道也是通过 makeSurface() 方法构建的, 这个方法会构建出一个“容器”类型的 Surface。
  • 4、将 Surface 设置为“Buff”类型,这个非常重要,因为上一步默认还是“容器”类型,所以需要设置成“Buff”类型,再后面就是 build 出一个 Surface 了。

那么到这里 Surface 的创建就完成了,这里可能有的人如果对 Surface 知识不太清楚的话会比较迷糊,WindowSurfaceController,SurfaceController,Surface 到底是什么关系,这个不在当前流程的重点,暂且理解为同级吧,在 java 层这些都是空格,都是靠内部的 native 指针或者句柄持有底层对象。
在这里插入图片描述

  • 1、WindowSurfaceController 和 SurfaceController 在java层是持有关系。
  • 2、SurfaceController 创建的时候,会触发 native 层创建一个 SurfaceController 并返回句柄给 java 层,同时还会触发一个 Layer 的创建
  • 3、BLASTBufferQueue 的构建依赖一个 SurfaceController
  • 4、BLASTBufferQueue::createSurface 方法会创建一个 Surface 并返回指针给上层
  • 5、java 层的 Surface 靠指针找到 native 层的 Surface

最后再来看一下 WMS 这边创建好后的 Surface 是如何设置给应用端的,也就是如何设置给 relayoutWindow 的参数 outSurfaceControl 。
这一步在 WindowManagerService::createSurfaceControl 放中执行 WindowSurfaceController::getSurfaceControl 时完成。

# WindowSurfaceControllervoid getSurfaceControl(SurfaceControl outSurfaceControl) {// 将framework层的SurfaceControl copy给应用层传递过来的outSurfaceControloutSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");}

这样一来应用端就有了可以保持绘制数据的 Surface ,然后就可以执行 View 树的绘制了。

创建Surface小结

对于Surface的知识是一个复杂的模块,是需要单独详细讲解的,目前可以知道的是原以为给 WindowState 创建图层就是一个,但是实际上发现创建了2个。

1、WindowState 本身对应的是“容器”类型的 Surface ,在“addWindow流程”就创建了,而 relayoutWindow 创建的是一个“BufferStateLayer”类型的 Surface 这个也是被 copy 到应用层的Surface ,说明应用层的数据是被绘制在这个 Surface上 的

2、“BufferStateLayer”类型 Surface 的创建会先创建一个 WindowSurfaceController 对象,然后内部会创建 SurfaceController 。从 WindowSurfaceController 这个类名也能看出来是针对 Window显 示的

3、不仅仅 Framework 层的层级树有容器概念,SurfaceFlinger 里的 Layer 树也有容器概念

4、我们在执行adb shell dumpsys activity containers 看到层级结构树,最底层的 WindowState 其实也是个容器,不是真正显示的地方。这个点从 “containers”也能理解,毕竟是容器树。

WindowState “容器”概念拓展

WindowState是容器这个是肯定的,也是WindowContainer子类,然后他的孩子也是WindowState定义如下:

# WindowStatepublic class WindowState extends WindowContainer<WindowState> implementsWindowManagerPolicy.WindowState, InsetsControlTarget, InputTarget {}

那么什么场景下 WindowState 下还有孩子呢?答案是子窗口,子窗口的定义在 Window 类型里,具体的不在当前讨论,之前我一直有个误区,一直以为弹出的 Dialog 是子窗口,但是实际上并不是,我目前找到了一个比较常见的子窗口是 PopupWindow。
以在google电话应用打开一个菜单为例
对应的dump 为:
在这里插入图片描述
看的到子窗口PopupWindow的WindowState是被挂载到Activity的WindowState下 对应的winscope trace为:
在这里插入图片描述
这里能看到 PopupWindow 也有一个容器图层和显示图层,容器图层挂载在 Activity 窗口容器图层下,和 Activity 下的窗口显示图层同级。

计算窗口大小与Surface放置

上篇说过 relayoutWindow 流程主要做了两件事:

  • 1、通过 createSurfaceControl 创建SurfaceControl
  • 2、通过 WindowSurfacePlacer::performSurfacePlacement 计算窗口大小和摆放Surface

这一节主要分析核心方法:WindowSurfacePlacer::performSurfacePlacement

根据上篇的分析,relayoutWindow 流程会触发 WindowSurfacePlacer::performSurfacePlacement 的执行,需要注意的是会触发执行这个方法的逻辑非常多,为什么呢?

比如写 App 的时候界面有变化了,或者我们手动执行“View::requestLayout”就会触发界面重绘,也就是执行 View 绘制三部曲,其中第二步就是 layout。那为什么要执行 layout 呢?
因为界面上有 View 的添加移除,或者横竖屏切换等情况,那其他 View 的位置很可能就会受影响,所以为了保证界面上 View 所在位置的正确,就会触发一次 layout 重新计算每个 View 所在的位置。
触发 layout 是 ViewRootImpl 触发的,他只需要对整个 View 树的 RootView(DecorView)触发layout就行,然后 RootView(DecorView)内部会递归触发整个 View 树的 layout逻辑,从而保证整个 View 树的每一个 View 都出在正确的位置。

这里提取2个 View 层 layout 逻辑的信息:

  • 1、目的是确保界面View树中的各个View处在正确的位置。
  • 2、触发逻辑是从RootView(DecorView) 开始递归执行

其实 WMS 对窗口的管理也是和 View 管理的一样的,窗口有添加移除,或者屏幕旋转等场景,为了确保手机屏幕上各个窗口处在正确的位置,显示正确的大小。所以会触发执行 WindowSurfacePlacer::performSurfacePlacement 方法。

作为 WMS 的核心方法之一,WindowSurfacePlacer::performSurfacePlacement 方法做的事情其实也远不仅这些,但是当前是分析 relayoutWindow 流程进入了这个方法做的事,所以还是只关心 relayoutWindow 流程触发需要做哪些事情就好。

然后对这个方法有个印象,因为以后会经常看这个方法,毕竟界面上有点风吹操作都要触发 WindowSurfacePlacer::performSurfacePlacement 方法的执行。

本篇的调用链和时序图如下:

WindowSurfacePlacer::performSurfacePlacementWindowSurfacePlacer::performSurfacePlacementLoopRootWindowContainer::performSurfacePlacementRootWindowContainer::performSurfacePlacementNoTraceRootWindowContainer::applySurfaceChangesTransactionDisplayContent::applySurfaceChangesTransactionDisplayContent::performLayout DisplayContent::performLayoutNoTraceDisplayContent::mPerformLayout DisplayPolicy::layoutWindowLw WindowLayout::computeFrames   -- 计算窗口大小,保存在 sTmpClientFrames中WindowState::setFrames        -- 将计算结果 sTmpClientFrames 的数据设置给窗口
关键字:Android Framework(五)WMS-窗口显示流程——窗口布局与绘制显示

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: