【Android 14源码分析】WMS-窗口显示-第二步:relayoutWindow -1

news2024/11/22 23:21:24

忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
                  
                  
                                           – 服装学院的IT男

本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS
V:WJB6995
Q:707409815

正文

窗口显示流程一共分为以下5篇:

窗口显示-流程概览与应用端流程分析

窗口显示-第一步:addWindow

窗口显示-第二步:relayoutWindow -1

窗口显示-第二步:relayoutWindow -2

窗口显示-第三步:finishDrawingWindow

SurfaceFlinger 控制屏幕显示,非常重要但不是现在分析的重点,当前阶段以黑盒的形式了解 SurfaceFlinger 端的概念即可,本篇知道 SystemService 会在 relayoutoutWindow 流程的时候创建一个 Surface 返回给客户端绘制即可。

1. 流程概述

回顾下窗口显示的三个流程:

在这里插入图片描述
google 把窗口的显示分为了三个流程:

    1. addWindow流程

    WMS 是维护系统所有窗口的模块,所以应用必须先向 WMS 请求添加窗口,这一阶段 WMS 的处理为:

    • 为应用端创建对应的 WindowState 并挂载
    1. relayoutWindow流程

    addWindow 流程后执行后,屏幕上就有新的 WindowState 添加了,WMS 也需要对屏幕上所有的窗口执行一遍 layout 来确定各个窗口所在的位置。
    而应用端想要绘制 UI 数据,则也需要知道自己的窗口大小,位置信息,并且还需要一个 Surface 来承载 UI 数据。所以这一阶段 WMS 的处理为:

    • 为窗口申请 Surface 并返回给应用端
    • 计算返窗口的大小,位置信息并返回给应用端
    1. finishDrawingWindow流程

    执行完上一流程后,应用端就可以绘制 UI 了,绘制完成后需要将 UI 显示到屏幕上,这一步还是需要 WMS 来完成。

    • 通知 SurfaceFlinger 进行显示这个 Surface

addWindow 流程在上一篇已经分析完了,现在 WindowManagerService 中已经有一个 WindowState 了并且也挂载到层级树中了。

但是一个窗口想要有 UI 内容需要底下的 View 树完成绘制,而 View 的绘制必须要有一个 Surface ,并且要进行绘制还需要自己的窗口在屏幕上的位置和宽高等信息。

这就是第二步 relayoutWindow 流程要做的2件事:

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

整体流程框图如下:

在这里插入图片描述

  • ViewRootImpl 下有3个成员变量

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

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

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

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

2. 应用端处理

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

ViewRootImpl::setView
   ViewRootImpl::requestLayout
      ViewRootImpl::scheduleTraversals             
            ViewRootImpl.TraversalRunnable::run          -- Vsync相关--scheduleTraversals
               ViewRootImpl::doTraversal
                  ViewRootImpl::performTraversals 
                     ViewRootImpl::relayoutWindow        -- 第二步:relayoutWindow
                        Session::relayout                -- 跨进程执行 relayoutWindow流程
                        ViewRootImpl::updateBlastSurfaceIfNeeded
                           Surface::transferFrom         -- 应用端Surface赋值
                        ViewRootImpl::setFrame           -- 应用端窗口大小赋值
                     ViewRootImpl::performMeasure        -- View绘制三部曲
                     ViewRootImpl::performLayout
                     ViewRootImpl::performDraw        
                     ViewRootImpl::createSyncIfNeeded    --- 第三步:绘制完成 finishDrawingWindow
   Session.addToDisplayAsUser                     --- 第一步:addWindow

doTraversal 方法是异步执行,所以 Session.addToDisplayAsUser 触发的 addWindow 流程是比 relayoutWindow 先执行的

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

2.1 ViewRootImpl::setView

# ViewRootImpl
   // 重点* 1. 应用端这个View树的 Surface
   public final Surface mSurface = new Surface();
   // 对应的SurfaceControl
   private 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) {
            // 当前第一次执行肯定为null
            if (mView == null) {
               mView = view;
               ......
               int res; // 定义稍后跨进程add返回的结果
               // 重点* 3. 第二步:会触发relayoutWindow
               requestLayout();  
               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 端触发
    1. 调用 addToDisplayAsUser 方法触发了addWindow 流程
    1. 本篇重点,触发 relayoutWindow

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

看看 ViewRootImpl::requestLayout 的代码。

# ViewRootImpl

   boolean mLayoutRequested;

   @Override
   public void requestLayout() {
         if (!mHandlingLayoutInLayoutRequest) {
            ......
            // 只有主线程才能更新UI
            checkThread();
            // 正确请求layout
            mLayoutRequested = 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件事:

    1. 线程检查,可以看到 checkThread() 方法的报错很多写App的同学就很熟悉: 不能在子线程更新UI。
    1. 执行 scheduleTraversals()
# ViewRootImpl

   final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

   //  是否在执行scheduleTraversals
   public boolean mTraversalScheduled;

   void scheduleTraversals() {
         // 如果遍历操作尚未被调度 
         if (!mTraversalScheduled) {
            // 将调度标志设置为true,表示遍历操作已被调度
            mTraversalScheduled = true;
            // 设置同步屏障 
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 重点 * 执行mTraversalRunnable
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            // 通知渲染器有一个新的帧即将开始处理
            notifyRendererOfFramePending();
            // 根据需要戳一下绘制锁
            pokeDrawLockIfNeeded();
         }
   }

这个方法虽然代码不多,但是还是有不少知识点的,比如: 同步屏障和Vsync,感兴趣的自行了解,当前不做拓展。

当前只要知道当下一个 VSync-app 到来的时候,会执行 TraversalRunnable 这个 Runnable 就好,所以重点看看这个 TraversalRunnable 做了什么。

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

# ViewRootImpl

   final class TraversalRunnable implements Runnable {
         @Override
         public void run() {
            doTraversal();
         }
   }

   void doTraversal() {
         if (mTraversalScheduled) {
            // 正在执行或已经执行完毕 
            mTraversalScheduled = false;
            // 移除同步屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            .....
            performTraversals();
            ......
         }
      }

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

2.1 ViewRootImpl::performTraversals

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

# ViewRootImpl
   private SurfaceSyncGroup mActiveSurfaceSyncGroup;

   private void performTraversals() {
      ......
      // mWinFrame保存的是当前窗口的尺寸
      Rect frame = mWinFrame;
      ----1.1 硬绘相关----
      // 硬件加速是否初始化
      boolean hwInitialized = false;
      ......
      ----2. relayoutWindow流程----
      // 内部会将经过WMS计算后的窗口尺寸给mWinFrame
      relayoutResult = 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. 后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法
    1. relayoutWindow 相关,也是当前分析重点
    1. 经过第第二步 relayoutWindow 后就 View 就可以绘制了,也是需要分析的重点流程,后面会陆续写博客
    1. 绘制完成后就要通知 SurfaceFlinger 进行合作了,finishDrawing 流程也很重要。

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

2.3 ViewRootImpl::relayoutWindow

ViewRootImpl::relayoutWindow 方法如下:

# ViewRootImpl

   public 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 和 BlastBufferQueue
                     mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
                  }
               } else ......
               ......
               // 重点* 3.将WMS计算的窗口大小设置到当前
               setFrame(mTmpFrames.frame, true /* withinRelayout */);
               return relayoutResult;
            }

这部分代码在 U 上还是有修改的,不过不影响主流程的分析。

    1. 跨进程通信触发 relayoutWindow 流程,注意这里将 mTmpFrames 和 mSurfaceControl 作为参数传递了过去。执行这个方法前 mSurfaceControl 只是一个没有实际内容的对象,但是经过 WMS::relayoutWindow 流程处理后,mSurfaceControl 就会真正持有一个 native 层的 Surface 句柄,有个这个 native 的 Surface 句柄,View 就可以把图像数据保存到Surface 中了。
    1. 将 mSurfaceControl 下的 Surface 赋值给当前的变量 mSurface
    1. relayoutWindow 流程后 mTmpFrames 就有最新的尺寸信息了,需要赋值给真正保存窗口尺寸的变量 mWinFrame

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

# ViewRootImpl

    private 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 流程到底做了什么。

3. relayoutWindow流程–Surface 的创建

3.1 创建的 Surface 是什么

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

只有“Buff”类型 Surface 才可以显示 UI 内容,relayoutWindow 流程的目的就是为创建创建一个“Buff”类型 Layer ,通过 Winscope 可以看到区别:
在这里插入图片描述
addWindow 后 SurfaceFlinger 层也是创建的 WindowState 对应的 Layer ,但是实际上 WindowState 下面还有一个“Buff”类型 Layer ,这一步就是 relayoutWindow 流程创建的。

在这里插入图片描述

下面完整介绍 relayoutWindow 流程是如何创建“Buff”类型 Layer 的。

3.2 WindowManagerService::relayoutWindow

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

# Session
    @Override
    public 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 方法对其进行真正的赋值。

outFrames 参数也同理。

WindowManagerService::relayoutWindow 代码如下:

# WindowManagerService

    public 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中获取WindowState
            final 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 = VISIBLE
            win.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. 创建SurfaceControl
                    result = 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;
      }

方法开始 就执行了 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
    1. performSurfacePlacement :窗口的摆放 (View一般有变化也要执行 layout,WMS在管理窗口这边肯定也要执行layout)
    1. fillClientWindowFramesAndConfiguration :将计算好的窗口尺寸返回给应用端

由于篇幅原因,本篇先介绍 createSurfaceControl 这个分支是如何创建 Surface 的。

3.3 了解“容器”和“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 。

容器类型的图层不能显示只能作为容器,只有 BufferStateLayer 才可以作为显示图层

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

这块的内容和后面的内容其实在【WindowContainer窗口层级-4-Surface树】也介绍过了,关于“容器”类型比如WindowState的Surface是如何创建就不再重复了,但是“Buff”类型Surface的创建还是需要再说一遍,比较是本篇分析的重点。看过并且熟悉这一流程的可以忽略 2.4 小节

3.4 Buff类型Surface的创建与挂载

3.4.1 流程概览

relayoutWindow的调用链如下:

WindowManagerService::relayoutWindow
   WindowManagerService::createSurfaceControl
      WindowStateAnimator::createSurfaceLocked -- 创建“Buff” 类型Layer
         WindowStateAnimator::resetDrawState   -- DRAW_PENDING
         WindowSurfaceController::init
            SurfaceControl.Builder::build
               SurfaceControl::init
      WindowSurfaceController::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
# WindowManagerService

   private 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”类型Surface
         Trace.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
    1. 通过 WindowStateAnimator::createSurfaceLocked 对 surfaceController 赋值,根据方法名猜测是创建了一个 Surface
    1. 通过 WindowSurfaceController::getSurfaceControl,给应用端 Surface 赋值

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

# WindowStateAnimator

   WindowSurfaceController 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_PENDING
      resetDrawState();
      ......
         // 重点* 2. 创建WindowSurfaceController
         mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), format,
                    flags, this, attrs.type);
      ......
      return mSurfaceController;
   }

这里有2个重点:

    1. 设置窗口状态为 DRAW_PENDING
    1. 创建 Surface

3.4.2 设置窗口状态–DRAW_PENDING

# WindowStateAnimator
   void resetDrawState() {
      // 设置windowState状态为DRAW_PENDING
      mDrawState = DRAW_PENDING;

      if (mWin.mActivityRecord == null) {
         return;
      }

      if (!mWin.mActivityRecord.isAnimating(TRANSITION)) {
         mWin.mActivityRecord.clearAllDrawn();
      }
   }

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

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

3.4.3 创建与挂载“Buff”类型Surface

继续回到主流程,看看 WindowSurfaceController 的构造方法

# WindowSurfaceController

   SurfaceControl mSurfaceControl;

   WindowSurfaceController(String name, int format, int flags, WindowStateAnimator animator,
            int windowType) {
      mAnimator = animator;
      // 1. 也会作为Surface的name
      title = name;

      mService = animator.mService;
      // 2. 拿到WindowState
      final 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);
      // 高版本都为BLAST
      if (useBLAST) {
         // 4. 重点* 设置为“Buff”图层
         b.setBLASTLayer();
      }
      // 触发build
      mSurfaceControl = b.build();
      Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }

分为4步:

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

makeSurface() 方法是如何构建Surface的需要移步【WindowContainer窗口层级-4-Surface树】看第二小节:2 容器类型的创建,就不重复介绍了。

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

在这里插入图片描述

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

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

# WindowSurfaceController

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

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

3.4.5 创建Surface小结

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

    1. WindowState 本身对应的是“容器”类型的 Surface ,在“addWindow流程”就创建了,而 relayoutWindow 创建的是一个“BufferStateLayer”类型的 Surface 这个也是被 copy 到应用层的Surface ,说明应用层的数据是被绘制在这个 Surface上 的
    1. “BufferStateLayer”类型 Surface 的创建会先创建一个 WindowSurfaceController 对象,然后内部会创建 SurfaceController 。从 WindowSurfaceController 这个类名也能看出来是针对 Window显 示的
    1. 不仅仅 Framework 层的层级树有容器概念,SurfaceFlinger 里的 Layer 树也有容器概念
    1. 我们在执行adb shell dumpsys activity containers 看到层级结构树,最底层的 WindowState 其实也是个容器,不是真正显示的地方。这个点从 “containers”也能理解,毕竟是容器树。

3.4.6 WindowState “容器”概念拓展

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

# WindowState
   public class WindowState extends WindowContainer<WindowState> implements
         WindowManagerPolicy.WindowState, InsetsControlTarget, InputTarget {

         }

那么什么场景下 WindowState 下还有孩子呢?答案是子窗口,子窗口的定义在 Window 类型里,具体的不在当前讨论,之前我一直有个误区,一直以为弹出的 Dialog 是子窗口,但是实际上并不是,我目前找到了一个比较常见的子窗口是 PopupWindow。
以在google电话应用打开一个菜单为例

在这里插入图片描述

对应的dump 为

在这里插入图片描述

看的到子窗口PopupWindow的WindowState是被挂载到Activity的WindowState下
对应的winscope trace为:

在这里插入图片描述
这里能看到 PopupWindow 也有一个容器图层和显示图层,容器图层挂载在 Activity 窗口容器图层下,和 Activity 下的窗口显示图层同级

4. 小结

本篇介绍了应用端发起 relayoutWindow 的逻辑,然后介绍了一些“容器”和“Buff”类型Surface的概念,知道了 relayoutWindow 流程主要是做2件事,本篇介绍了第一件事:创建Surface。下一篇开始分析窗口(Surface)的摆放流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2182363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

贝锐蒲公英工业物联方案:助力美的智慧楼宇全球布局

智慧楼宇正日益成为现代城市发展的基石&#xff0c;作为该领域的先锋&#xff0c;美的楼宇科技通过其创新的iBUILDING数字化平台和低碳技术&#xff0c;引领着智慧空间的可持续发展&#xff0c;并持续推动建筑及相关行业的数字化转型。 美的楼宇科技的解决方案融合了先进的楼宇…

六,MyBatis-Plus 扩展功能(逻辑删除,通用枚举,字段类型处理,自动填充功能,防全表更新与删除插件,MybatisX快速开发插件)

六&#xff0c;MyBatis-Plus 扩展功能&#xff08;逻辑删除&#xff0c;通用枚举&#xff0c;字段类型处理&#xff0c;自动填充功能&#xff0c;防全表更新与删除插件&#xff0c;MybatisX快速开发插件&#xff09; 文章目录 六&#xff0c;MyBatis-Plus 扩展功能&#xff08;…

51单片机学习第六课---B站UP主江协科技

DS18B20 1、基本知识讲解 2、DS18B20读取温度值 main.c #include<regx52.h> #include"delay.h" #include"LCD1602.h" #include"key.h" #include"DS18B20.h"float T; void main () {LCD_Init();LCD_ShowString(1,1,"temp…

领域偏移:协变量移位下的域自适应

现在我们将焦点转移到一种叫做协变量转移的扰动上。我们在一个分类或回归设置中工作&#xff0c;我们希望从x预测y&#xff0c;并假设p≈(y | x)和p∗(y | x)是相同的(标记函数在训练和测试之间不会改变) 假设 (Covariate Shift)。对于列车分布p~和检验分布p∗&#xff0c;我们…

安达发|纺织行业APS系统中的物料替代解决方案

在纺织行业中&#xff0c;物料替代是应对原材料短缺、成本波动和供应链不确定性的一种重要策略。高级计划与排程系统&#xff08;APS&#xff09;通过集成物料替代功能&#xff0c;可以帮助企业在保持生产效率的同时&#xff0c;灵活应对市场变化。本文将探讨纺织行业在APS系统…

Leetcode Hot 100 | 543.二叉树的直径 | 递归+优化

写法一 自己一开始直接写的&#xff0c;没考虑时间复杂度… class Solution {/*递归思路&#xff1a;不准进递归&#xff08;除非之后用简单例子验证一下&#xff09;将方法按照自己想要返回的值来补充其他的代码细节&#xff1b;用最值来模拟返回结果补充代码细节&#xff0…

Win10鼠标总是频繁自动失去焦点-非常有效-重启之后立竿见影

针对Win10鼠标频繁自动失去焦点的问题&#xff0c;可以尝试以下解决方案&#xff1a; 一、修改注册表&#xff08;最有效的方法-重启之后立竿见影&#xff09; 打开注册表编辑器&#xff1a; 按下WindowsR组合键&#xff0c;打开运行窗口。在运行窗口中输入“regedit”&#x…

什么是reactor以及其三种版本

写在前面 本文来看下什么是reactor以及其三种版本。 1&#xff1a;什么是reactor以及其三种版本 为了更好的理解什么是reactor&#xff0c;我们结合现实生活中的例子来看下。 翠花是个貌美如花的姑娘&#xff0c;人称赛东施&#xff0c;她的梦想是嫁给王子&#xff0c;可是天…

【机器学习-无监督学习】降维与主成分分析

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…

揭秘帕金森病:多因素交织下的“沉默杀手”

在老年人群中&#xff0c;帕金森病如同一位“沉默的杀手”&#xff0c;悄然侵袭着无数人的生活。它以其独特的静止性震颤、运动迟缓、肌强直和姿势平衡障碍等症状&#xff0c;让患者的生活质量大打折扣。那么&#xff0c;帕金森病究竟是如何得的呢&#xff1f;本文将带您深入探…

基于Springboot+Vue的基于协同过滤算法的个性化音乐推荐系统 (含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统中…

Android 13.0 系统wifi列表显示已连接但无法访问网络问题解决

1.前言 在13.0的系统rom产品定制化开发中,在wifi模块也很重要,但是在某些情况下对于一些wifi连接成功后,确显示已连接成功,但是无法访问互联网 的情况,所以实际上这时可以正常上网的,就是显示的不正常,所以就需要分析连接流程然后解决问题 如图所示: 2.系统wifi列表显示…

【Spring Boot 入门三】Spring Boot与数据库集成 - 构建数据驱动的应用

一、引言 在之前的文章中&#xff0c;我们已经对Spring Boot有了初步的认识&#xff0c;了解了如何构建第一个Spring Boot应用&#xff0c;以及如何通过配置文件来掌控应用的设置。这些知识为我们进一步探索Spring Boot与数据库的集成奠定了坚实的基础。 数据库是现代应用的核…

QSqlDatabase在多线程中的使用

Qt中多线程使用数据库_qt数据库管理类支持多数据库,多线程-CSDN博客 1. 代码&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QPushButton> #include <QSqlDatabase> #include <QSqlQuery> #include <QSqlError>…

【原创】基于websocket的实时文本转语音功能

功能实现&#xff0c;将长文本分段实时转成音频流&#xff0c;达到边转换边播放的效果&#xff0c;减少等待效果&#xff0c;像豆包通义千问chatgpt差不多也是这样实现的&#xff0c; 下面的效果展示 20240930_084035_哔哩哔哩_bilibili【原创】基于websocket的实时文本转语音…

Java 每日一刊(第18期):集合

文章目录 前言1. Java 集合框架概述1.1 Java 集合框架的定义和意义1.2 Java 集合框架的历史演进1.3 集合框架的基本组成部分1.4 Java 集合的优势1.5 Java 集合与数组的区别与关系 2. Java 集合框架的核心接口2.1 Collection 接口2.2 List 接口2.3 Set 接口2.4 Queue 接口2.5 Ma…

无人机在科研与教育领域的应用!

一、无人机在科研领域的应用 地理测绘与遥感&#xff1a; 无人机可以搭载相机或激光雷达等传感器&#xff0c;进行高分辨率的航拍&#xff0c;用于制作数字地形模型、生态环境监测、土地利用规划等。 在地理信息获取、地质勘察等领域&#xff0c;无人机能够高效地完成测量任…

Java中正则表达式(完整详解,附有案例+代码)

文章目录 三十三.正则表达式33.1 概述33.2 Test 三十三.正则表达式 33.1 概述 字符类 [abc]a、b 或 c&#xff08;简单类&#xff09;[^abc]任何字符&#xff0c;除了 a、b 或 c&#xff08;否定&#xff09;[a-zA-Z]a 到 z 或 A 到 Z&#xff0c;两头的字母包括在内&#x…

MQTT--EMQX入门+MQTTX使用

目录 1、什么是EMQX&#xff1f;1.1 EMQX介绍1.2 EMQX特点1.3 与物联网之间的关系以及主要的产品主要的产品 2、安装启动2.1 基本命令2.2 目录结构 3、MQTTX客户端3.1 连接配置 总结PS: 1、什么是EMQX&#xff1f; 首先你得有MQTT的知识&#xff0c;不认识MQTT的小伙伴可以先看…

JMeter对jdbc request以及foreach和loop controller的使用

Jmeter中jdbc request和foreach控制器 1. 使用variable name实现对数据库查询结果的遍历 在foreach controller中&#xff0c;注意要做variable name的关联(correlation), 否则没法取回这里的jdbc request返回的结果。这里的input variable prefix一定要和jdbc request中的var…