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

文章目录

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

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 的数据设置给窗口

在这里插入图片描述

WindowSurfacePlacer::performSurfacePlacement

# WindowSurfacePlacer// 控制是否需要继续执行 performSurfacePlacementLoop方法private boolean mTraversalScheduled;// 延迟layout就会+1private int mDeferDepth = 0;final void performSurfacePlacement(boolean force) {if (mDeferDepth > 0 && !force) {mDeferredRequests++;return;}// 最大次数循环为6次int loopCount = 6;do {// 设置为falsemTraversalScheduled = false;// 重点方法performSurfacePlacementLoop();// 移除Handler的处理mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);loopCount--;// 结束条件为 mTraversalScheduled 不为false 和 loopCount大于0 ,也就最多6次} while (mTraversalScheduled && loopCount > 0);mService.mRoot.mWallpaperActionPending = false;}

relayoutWindow 方法调用的是传递的参数是 true ,那第一个if是走不进去的,主要看后面的逻辑控制。

里面的 performSurfacePlacementLoop() 方法是重点,在分析这个方法之前,先确认下停止循环的2个条件:

  • 1、loopCount > 0 这个条件比较简单,这个循环最多执行6次,为什么是6咱也不知道,查了一下说是google工程师根据经验设置,如果执行6次循环还没处理好Surface那肯定是出现问题了。(debug发现一般就执行1次,最多看到执行2次的情况)
  • 2、mTraversalScheduled 这个变量但是执行循环的时候就设置为 false ,说明正常情况下执行一次就不需要再执行了。

这里也其实要注意,bool 类型默认是 false ,所以找到在是哪里将 mTraversalScheduled 设置为 true,其实就是找到了什么情况下需要执行 performSurfacePlacementLoop 方法。

继续下一步 performSurfacePlacementLoop 方法的内容。

# WindowSurfacePlacerprivate void performSurfacePlacementLoop() {......// 重点*1. 对所有窗口执行布局操作mService.mRoot.performSurfacePlacement();// 布局完成mInLayout = false;// 若需要布局,(Root检查每个DC是否需要)if (mService.mRoot.isLayoutNeeded()) {if (++mLayoutRepeatCount < 6) {// 重点*2. 布局次数小于6次,则需要再次请求布局requestTraversal();} else {Slog.e(TAG, "Performed 6 layouts in a row. Skipping");mLayoutRepeatCount = 0;}} else {mLayoutRepeatCount = 0;}   }

1、执行 RootWindowContainer::performSurfacePlacement 这个代表对屏幕进行一次 layout ,后续的分析都在这
2、如果“mService.mRoot.isLayoutNeeded()”满足就执行 requestTraversal ,这个方法会将 mTraversalScheduled 变量设置为 true
3、“mService.mRoot.isLayoutNeeded()” 的返回其实受 DisplayContent 下的 mLayoutNeeded 变量控制,有任意一个屏幕为 true 就说明还需要一次 layout

主流程等会再看,先看一下 WindowSurfacePlacer::requestTraversal 的逻辑。

# WindowSurfacePlacervoid requestTraversal() {if (mTraversalScheduled) {return;}// Set as scheduled even the request will be deferred because mDeferredRequests is also// increased, then the end of deferring will perform the request.// 还需要一次mTraversalScheduled = true;if (mDeferDepth > 0) {mDeferredRequests++;if (DEBUG) Slog.i(TAG, "Defer requestTraversal " + Debug.getCallers(3));return;}// 通过Handler触发mService.mAnimationHandler.post(mPerformSurfacePlacement);}

当前不必纠结与执行几次的逻辑,这里的东西还挺复杂的,还会涉及到延迟 layout 等待,当前这块以简单的场景看就好了。

给个不是很精确但是覆盖百分之95以上场景的结论:
performSurfacePlacement 就是一次窗口的 layout ,是不是还有再执行的条件控制在 DisplayContent 下的 mLayoutNeeded 变量控制。如果这个变量为 false 则说明这次 layout 还有事情没完成,还要再来一次。

具体几次6次8次的没必要过于纠结,但是需要注意 “mService.mRoot.isLayoutNeeded()”如果不满足,则就不会再次触发了,对这些有个印象即可,主要看 RootWindowContainer::performSurfacePlacement 方法。

layout操作–RootWindowContainer::performSurfacePlacement

# RootWindowContainer// 这个方法加上了tracevoid performSurfacePlacement() {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");try {performSurfacePlacementNoTrace();} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}}// 主要干活的还是这个void performSurfacePlacementNoTrace() {......// 1. 如果需要,则更新焦点if (mWmService.mFocusMayChange) {mWmService.mFocusMayChange = false;mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);}......// TraceTrace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");// 开启事务mWmService.openSurfaceTransaction();try {// 2.. 处理事务(执行窗口尺寸计算,surface状态变更等操作)applySurfaceChangesTransaction();} catch (RuntimeException e) {Slog.wtf(TAG, "Unhandled exception in Window Manager", e);} finally {// 关闭事务,做事务提交mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}......// 3. Activity 切换事务处理,// 条件满足也会将窗口状态设置为HAS_DRAW 流程checkAppTransitionReady(surfacePlacer);......// 再次判断是否需要处理焦点变化if (mWmService.mFocusMayChange) {mWmService.mFocusMayChange = false;mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,false /*updateInputWindows*/);}......// 4. 如果过程中size或者位置变化,则通知客户端重新relayouthandleResizingWindows();......// 5. 销毁不可见的窗口i = mWmService.mDestroySurface.size();if (i > 0) {do {i--;WindowState win = mWmService.mDestroySurface.get(i);win.mDestroying = false;final DisplayContent displayContent = win.getDisplayContent();if (displayContent.mInputMethodWindow == win) {displayContent.setInputMethodWindowLocked(null);}if (displayContent.mWallpaperController.isWallpaperTarget(win)) {displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;}win.destroySurfaceUnchecked();} while (i > 0);mWmService.mDestroySurface.clear();}......}

这个方法处理的事情非常多

  • 1、焦点相关
  • 2、applySurfaceChangesTransaction 这个是当前分析的重点,主要是处理窗口大小和Surface的
  • 3、checkAppTransitionReady 是处理 Activity 切换的事务
  • 4、如果这次 layout 有窗口尺寸改变了,就需要窗口进行 resize 操作
  • 5、销毁掉不需要的窗口

这个方法比较长,干的事也比较多,而且执行的频率也很高。(可以自己加个log或者抓trace看一下)

其他的流程目前不看,只关系 applySurfaceChangesTransaction 方法。
可以看到在执行 applySurfaceChangesTransaction 方法的前后都 SurfaceTransaction 的打开和关闭,那说明这个方法内部肯定是有 Surface 事务的处理。

后面的 RootWindowContainer::applySurfaceChangesTransaction 方法是 relayoutWindow 流程的核心,在看后面之前,先对前面这块比较混乱流程整理一个流程图:

在这里插入图片描述

RootWindowContainer::applySurfaceChangesTransaction

# RootWindowContainerprivate void applySurfaceChangesTransaction() {......// 正常情况就一个屏幕final int count = mChildren.size();for (int j = 0; j < count; ++j) {final DisplayContent dc = mChildren.get(j);dc.applySurfaceChangesTransaction();}......}

遍历每个屏幕执行 DisplayContent::applySurfaceChangesTransaction

# DisplayContentprivate final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();void applySurfaceChangesTransaction() {......// 置空mTmpUpdateAllDrawn.clear();......// 重点* 1. 执行布局,该方法最终会调用performLayoutNoTrace,计算窗口的布局参数performLayout(true /* initial */, false /* updateInputWindows */);pendingLayoutChanges = 0;Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyPostLayoutPolicy");try {mDisplayPolicy.beginPostLayoutPolicyLw();// 对所有窗口执行布局策略forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);mDisplayPolicy.finishPostLayoutPolicyLw();} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}......Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");try {// 重点* 2. 遍历所有窗口,主要是改变窗口状态设置为READY_TO_SHOW,当前逻辑不满足,不会执行最终设置forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}// 重点* 3. finishDrawing()流程,条件满足触发提交Surface到SurfaceFlinger prepareSurfaces();......}

这个方法其实有3个重点,但是因为当前分析的是窗口执行 relayoutWindow 过来的逻辑,窗口下的 View 应用端都还没进行绘制,所以后面2个重点内部都会因为条件不满足被 return 。
不过2个重点也是后面学习需要分析的流程,所以先留下个印象。

Framework的流程很复杂,基本上没有一行代码是多余的,如果每个代码都看,每个分支都认真分析,那可能只有AI能完成了,所以分析某个流程只关心流程中主要代码就可以了。

当前流程主要关注1个重点:

performLayout :最终会执行创建大小的计算。

layout——计算窗口大小

# DisplayContent// 标记是否需要执行layoutprivate boolean mLayoutNeeded;boolean isLayoutNeeded() {return mLayoutNeeded;}// 根据前面流程,如果为false则表示不会执行循环private void clearLayoutNeeded() {if (DEBUG_LAYOUT) Slog.w(TAG_WM, "clearLayoutNeeded: callers=" + Debug.getCallers(3));mLayoutNeeded = false;}void performLayout(boolean initial, boolean updateInputWindows) {// 加上traceTrace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");try {performLayoutNoTrace(initial, updateInputWindows);} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}}// 主要方法private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {// 判断是否需要布局,不需要则直接返回,内部是通过mLayoutNeeded判断if (!isLayoutNeeded()) {return;}// 将mLayoutNeeded设置为flaseclearLayoutNeeded();......// 重点* 1. 对所有顶级窗口进行布局forAllWindows(mPerformLayout, true /* traverseTopToBottom */);// 重点* 2. 处理子窗口的布局forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);......}

DisplayContent::performLayout 方法也是为了加 trace 所以还是得看 DisplayContent::performLayoutNoTrace 方法,主要就是2个forAllWindows,这个方法在之前 Activity 启动的流程讲过类似的,就是将按照第二个参数的顺序,从上到下或者从下至上遍历每个 Window 让其执行第一个参数的 lambda 表达式,所以只要看看具体的 lambda 表达式即可。

mPerformLayout 和 mPerformLayoutAttached

# DisplayContentprivate final Consumer<WindowState> mPerformLayout = w -> {// 如果当前窗口为子窗口则直接返回if (w.mLayoutAttached) {return;}// 先判断当前窗口是否会不可见final boolean gone = w.isGoneForLayout();// 如果窗口不是不可见的,或者窗口没有框架,或者窗口需要布局if (!gone || !w.mHaveFrame || w.mLayoutNeeded) {......// 重点*1. 调用DisplayPolicy::layoutWindowLwgetDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);......if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.getFrame()+ " mParentFrame=" + w.getParentFrame()+ " mDisplayFrame=" + w.getDisplayFrame());}};private final Consumer<WindowState> mPerformLayoutAttached = w -> {// 如果不是子窗口则返回if (!w.mLayoutAttached) {return;}if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame|| w.mLayoutNeeded) {......getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);w.mLayoutSeq = mLayoutSeq;if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()+ " mParentFrame=" + w.getParentFrame()+ " mDisplayFrame=" + w.getDisplayFrame());}};

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习】高斯网络的基本概念和应用领域以及在python中的实例

引言 高斯网络&#xff08;Gaussian Network&#xff09;通常指的是一个概率图模型&#xff0c;其中所有的随机变量&#xff08;或节点&#xff09;都遵循高斯分布 文章目录 引言一、高斯网络&#xff08;Gaussian Network&#xff09;1.1 高斯过程&#xff08;Gaussian Proces…

【JSP `page` 指令详解:构建高效的动态网页】

JSP page 指令详解&#xff1a;构建高效的动态网页 在 JavaServer Pages (JSP) 中&#xff0c;<% page %> 指令用于配置 JSP 页面的一些关键属性。这些属性控制着页面的行为和生成的 Servlet 的特性&#xff0c;例如字符编码、是否启用会话、缓冲区大小等。合理使用 page…

鸿蒙OpenHarmony【轻量系统芯片移植】内核移植

移植芯片架构 芯片架构的移植是内核移植的基础&#xff0c;在OpenHarmony中芯片架构移植是可选过程&#xff0c;如果当前OpenHarmony已经支持对应芯片架构则不需要移植操作&#xff0c;在“liteos_m/arch”目录下可看到当前已经支持的架构&#xff0c;如表1&#xff1a; 表1 …

羽毛球关键点检测系统源码分享

羽毛球关键点检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer…

openSSL 如何降版本

文章目录 前言openSSL 如何降版本1. 卸载2. 安装新的openssl版本3. 验证 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c;实在白嫖的话&…

Linux IO模型(多路复用)

【1】Linux IO模型&#xff1a;IO多路复用 场景假设二 假设妈妈有三个孩子&#xff0c;分别不同的房间里睡觉&#xff0c;需要及时获知每个孩子是否醒了&#xff0c;如何做&#xff1f; 1.一直在一个房间呆着&#xff1a;看不到其他两个孩子 2.每个房间不停的看&#xff1a;可以…

WebGL系列教程五(使用索引绘制彩色立方体)

目录 1 前言2 立方体3 开始绘制3.1 声明顶点和颜色3.2 使用索引绘制3.3 效果3.4 完整代码 4 总结 1 前言 上一讲我们讲了如何绘制彩色的三角形&#xff0c;这一讲我们来说如何绘制立方体。为什么几乎所有的WebGL教程总是从开始绘制三角形开始&#xff0c;因为三角形是最小的面&…

服务器模型 Reactor 和 Proactor

Proactor 具体流程如下&#xff1a; 处理器发起异步操作&#xff0c;并关注 IO 完成事件&#xff1b;事件分离器等待操作完成事件&#xff1b;分离器等待过程中&#xff0c;内核并行执行实际的 IO 操作&#xff0c;并将结果存储入用户自定义的缓冲区&#xff0c;最后通知事件分…

【C++】C++ STL 探索:List使用与背后底层逻辑

C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现 本文将通过模拟实现List&#xff0c;从多个角度深入…

60 道 MySQL 精选面试题

基础 #1、关系型和非关系型数据库的区别&#xff1f; 关系型数据库的优点 容易理解&#xff0c;因为它采用了关系模型来组织数据。可以保持数据的一致性。数据更新的开销比较小。支持复杂查询&#xff08;带 where 子句的查询&#xff09; 非关系型数据库&#xff08;NOSQL…

微服务-nacos

nacos-注册中心 启动 服务注册到nacos

【Unity踩坑】创建新项目后提示编译错误要进入安全模式

在创建了新项目后&#xff08;比如URP&#xff0c;AR&#xff0c;VR&#xff09;&#xff0c;首次打开时提示有编译错误&#xff0c;要进入安全模式。 脚本是项目模板自带的&#xff0c;不会有问题。这时需要先选择进入安全模式&#xff0c;然后关闭项目&#xff0c;重新打开就…

9月12号作业

主要更改的代码 void Widget::read_solt() { QByteArray msg socket->readAll();//接受信息 if(QString::fromLocal8Bit(msg)msg2||msg3QString::fromLocal8Bit(msg)||msg6QString::fromLocal8Bit(msg)) { QListWidgetItem *listItem new QListWidgetItem(QString::fromL…

网络视频流解码显示后花屏问题的分析

问题描述 rtp打包的ps视频流发送到客户端后显示花屏。 数据分析过程 1、用tcpdump抓包 tcpdump -i eth0 -vnn -w rtp.pcap 2、用wireshark提取rtp的payload 保存为record.h264文件 3、用vlc播放器播放 显示花屏 4、提取关键帧 用xxd命令将h264文件转为txt文件 xxd -p…

2 创建会计科目表

定义解释 在SAP系统中&#xff0c;会计科目表是财务系统的基础数据之一&#xff0c;对于企业的财务核算和财务管理至关重要 定义&#xff1a;会计科目表是SAP系统中用于定义和分类总账科目的集合&#xff0c;它包含了所有需要在财务系统中记录和管理的会计科目。这些科目按照…

工厂安灯系统在设备管理中的重要性

在现代制造业中&#xff0c;设备管理是确保生产效率和产品质量的关键环节。随着工业4.0的推进&#xff0c;越来越多的企业开始采用智能化的设备管理系统&#xff0c;其中安灯系统作为一种有效的管理工具&#xff0c;逐渐受到重视。安灯系统最初源于日本的丰田生产方式&#xff…

内存管理篇-23 二级页表的创建过程-上

二级页表的填充过程&#xff1a;一级页表是4096项&#xff08;用虚拟地址高12位&#xff09;&#xff0c;二级页表是256项&#xff08;用虚拟地址中间10位&#xff0c;每个项能代表一个物理页&#xff0c;因此4KB*2561MB&#xff09;。因此&#xff0c;每个二级页表映射1MB的空…

Java stream使用与执行原理

stream简介 Stream: A sequence of elements supporting sequential and parallel aggregate operations stream为sequential即单线程串行操作&#xff0c;parallelStream支持并行操作&#xff0c;本文只讨论sequential的stream。 stream常用操作 Datastatic class Course {pr…

HarmonyOS应用开发( Beta5.0)HOS-用户认证服务:面部识别

介绍 User Authentication Kit&#xff08;用户认证服务&#xff09;提供了基于用户在设备本地注册的人脸和指纹来认证用户身份的能力。 用户向应用/系统服务请求访问某些个人数据或执行某些敏感操作时&#xff0c;应用/系统服务将调用系统用户身份认证控件对用户身份进行认证…

MySQL之库和表操作

目录 一&#xff1a;对库的操作 1.创建数据库 2.查看数据库列表 3.显示创建数据库的语句 4.删除数据库 5.字符集与校验集 6.确认当前所处的数据库 7.修改数据库 8.备份和恢复 9.查看连接情况 二:对表的操作 1.创建表 2.查看表 3.删除表 4.修改表 接下来的日…