【Android14 ShellTransitions】(七)Transition就绪

Transition.onTransactionReady的内容比较长,我们挑重点的部分逐段分析(跳过的地方并非不重要,而是我柿子挑软的捏)。

1 窗口绘制状态的流转以及显示SurfaceControl

注意我们这里的SurfaceControl特指的是WindowSurfaceController的mSurfaceControl,如果对这个不是很了解的,可以回顾一下之前写的关于SurfaceControl的文章:

【基础】2、Surface的创建【Android 12】 - 掘金 (juejin.cn)

接着分析代码:

在这里插入图片描述

先跳过Transition.commitVisibleActivities,看到首先是将Transition.mState置为STATE_PLAYING,这意味着动画马上就要执行了。

然后是为Transition的两个成员变量mStartTransaction以及mFinishTransaction赋值,mFinishTransaction不用多说,看到mStartTransaction被赋值为传参transaction,传参即我们上一篇分析中的在SyncGroup.finishNow创建的一个Transaction,局部变量merged:

在这里插入图片描述

一个“start transaction”和一个“finish transaction”,我按照个人的理解,举个例子说明一下,如果我们从ActivityA上启动了一个ActivityB:

1)、对于ActivityA来说,它相关的SurfaceControl(准确一点说则是WindowSurfaceController.mSurfaceControl)需要在动画结束的时候再隐藏,如果它在动画开始前就隐藏,那么就无法看到ActivityA的动画效果了(向右平移退出或者淡出之类的动画)。

2)、对于ActivityB来说,它相关的SurfaceControl需要在动画开始的时候就显示出来,如果它在动画开始的时候还没有显示,那么同样也无法看到ActivityB的动画效果了(向右平移进入或者淡入之类的动画)。

从以上分析可知,ActivityA和ActivityB相关的SurfaceControl可见性变化的时机是不同的,那么这个行为通过一次Transacton.apply是无法做到的,所以就需要两个Transaction,即“start transaction”和“finish transaction”。“start transaction”在动画开始前调用apply,用于在动画开始执行前提前将ActivityB进行显示,“finish transaction”则是在动画结束的时候调用apply,用于在动画结束的时候再将ActivityA隐藏。

最重要的是要弄清楚“start transaction”和“finish transaction”这两个Transaction调用apply方法的时机,在以后的Transition流程中会分析到。

再来看Transition.commitVisibleActivities方法的内容:

在这里插入图片描述

如该方法的注释所说,当前Transition已经准备好执行动画了,这里先让“start transaction”把相关需要显示的SurfaceControl显示出来。

Transition.mParticipants是参与动画的WindowContainer集合,那么这个方法就是遍历这个集合:

1)、调用ActivityRecord.commitVisibility设置相关ActivityRecord的为可见。

2)、调用ActivityRecord.commitFinishDrawing进一步设置相关SurfaceControl为可见。

ActivityRecord.commitVisibility方法内容比较多,主要是用来ActivityRecord的可见性,即其成员变量mVisible,除此之外还有很多别的逻辑,但是和我们要分析的Transition内容无关,只需要知道这里设置了ActivityRecord的可见性即可,不去多说。我们主要看下ActivityRecord.commitFinishDrawing:

在这里插入图片描述

很简单,为每一个child,即WindowState调用commitFinishDrawing方法:

在这里插入图片描述

1)、调用WindowStateAnimator.commitFinishDrawingLocked方法,继续将窗口对应的WindowStateAnimator的mDrawState,即绘制状态进行流转。

2)、调用WindowStateAnimator.prepareSurfaceLocked,设置SurfaceControl的可见性。

这两个方法都比较重要,我们接下来分别进行分析。

1.1 窗口绘制状态的流转

SurfaceControl最终的显示和窗口的绘制状态密切相关,所以我感觉这里有必要看一下WindowStateAnimator.mDrawState这个状态是如何切换的,并且我自己对这个窗口的绘制状态也是不求甚解,也希望借着这个机会了解一下。

先分析一下代码,回头再试着总结一下。

·1.1.1 WindowStateAnimator.commitFinishDrawingLocked

在这里插入图片描述

首先将WindowState.mDrawState设置为READY_TO_SHOW。

然后如果当前WindowStateAnimator相关的WindowState满足以下条件之一,则继续调用WindowStateAnimator.performShowLocked:

1)、没有对应的ActivityRecord,即是一个非Activity窗口:

activity == null

2)、有对应的ActivityRecord,并且此时已经可以显示窗口了:

activity.canShowWindows()

在这里插入图片描述

重要是就是看这个ActivityRecord的mSyncState是不是SYNC_STATE_WAITING_FOR_DRAW,如果是这个值,那么就说明这个ActivityRecord是处于动画中的。

但是有一个问题是,ActivityRecord的mSyncState是不会被设置为SYNC_STATE_WAITING_FOR_DRAW的,只有WindowState才会,那岂不是每次走到这里判断ActivityRecord是否drawn,都将一直是true。

3)、是一个TYPE_APPLICATION_STARTING类型的窗口,即SplashScreen或者Snapshot:

mWin.mAttrs.type == TYPE_APPLICATION_STARTING

总而言之,如果这个WindowState满足了绘制了条件,那么将继续调用WindowState.performShowLocked。

1.1.2 WindowState.performShowLocked

在这里插入图片描述

如果WindowStateAnimator.mDrawState不是READY_TO_SHOW,那么返回false,否则将其置为HAS_DRAWN,并且返回true,这将使得我们可以下一步继续调用WindowStateAnimator.prepareSurfaces方法。

从WindowStateAnimator.commitFinishDrawingLocked以及WindowState.performShowLocked这两个方法都能看到,窗口的绘制状态是循序渐进的,必须是状态A -> 状态B -> 状态C,不存在状态A直接到状态C之类的。

1.1.3 窗口绘制状态小结

首先是mDrawState在WindowStateAnimator的定义,以及几个取值:

在这里插入图片描述

结合着Activity启动的一般流程,我大致总结一下:

1)、NO_SURFACE:当没有Surface的就置为这个状态。

这个很好理解,一般窗口销毁相关的流程,会将WindowStateAnimator.mDrawState设置为NO_SURFACE,比如:

WindowState.removeImmediately

-> WindowStateAnimator.destroySurfaceLocked

-> WindowStateAnimator.destroySurface

此阶段没有窗口,也没有Surface。

2)、DRAW_PENDING:当Surface被创建之后,窗口被添加但还没有开始绘制之前,就会置为这个状态。在这个时期,Surface是隐藏的。这表明Surface正等待应用程序绘制窗口的内容。

当窗口被添加,接着App侧开始走measure、layout以及draw流程,在draw之前,会将窗口在WMS侧进行relayout,经过:

WMS.relayoutWindow

-> WMS.createSurfaceControl

-> WindowStateAnimator.createSurfaceLocked

-> WindowStateAnimator.resetDrawState

会将WindowStateAnimator.mDrawState设置为DRAW_PENDING。

这个流程我们也很熟悉,即之前分析创建WindowSurfaceController的SurfaceControl的流程。

此阶段窗口被添加但还没绘制出来,SurfaceControl也是隐藏的。

3)、COMMIT_DRAW_PENDING:当窗口的绘制操作完成,但是这个Surface还没有显示出来之前,状态会设置为此值。这个Surface会在下次layout过程中显示出来。

当窗口绘制完成,App侧调用ViewRootImpl.reportDrawFinished后,就会调用IWindowSession的对端,经过:

Session.finishDrawing

-> WMS.finishDrawingWindow

-> WindowState.finishDrawing

-> WindowStateAnimator.finishDrawingLocked

会将WindowStateAnimator.mDrawState设置为COMMIT_DRAW_PENDING。

此阶段窗口已经绘制完成,但是Surface由于一些原因还不能显示。

4)、READY_TO_SHOW:这个状态标识窗口的绘制操作已经提交,但Surface还没有真正显示。在一组窗口(例如属于同一个应用的多个窗口)准备显示时,系统会使用这个状态来延迟显示Surface,直到所有相关窗口都准备好一起显示。

首先我们看到在动画的流程中,窗口的绘制状态被设置为READY_TO_SHOW的流程为:

Transition.onTransactionReady

-> Transition.commitVisibleActivities

-> ActivityRecord.commitFinishDrawing

-> WindowState.commitFinishDrawing

-> WindowStateAnimator.commitFinishDrawingLocked

结合注释,我个人的理解是,绘制状态被置为READY_TO_SHOW,表明此窗口已经绘制完了,可以准备显示它的SurfaceControl了,但是它的SurfaceControl需要等待和其它的SurfaceContrl一起显示,或者说等待动画走到特定阶段才能显示,因此我们这里推迟其SurfaceControl的显示时间,将窗口的绘制状态设置为READY_TO_SHOW。

如果不考虑和其它窗口一起显示,那么我想在这一步就可以将绘制状态设置为HAS_DRAWN了,即READY_TO_SHOW这个状态值是不必要的。

5)、HAS_DRAWN:当窗口首次在屏幕上显示时,就会设置为此状态。

这个值在WindowState.performShowLocked方法中被设置,紧跟着WindowStateAnimator.commitFinishDrawingLocked方法。

严谨一点的话注释的说法其实是不准确的,当窗口绘制状态被设置为HAS_DRAWN的时候,只是说明SurfaceControl接下来可以显示了,但是SurfaceControl仍然没有显示,屏幕上是看不见的。

6)、总结一下,从以上分析可知,这些状态值不只涉及了窗口的绘制流程,还涉及了SurfaceControl的显示流程:

  • NO_SURFACE:没窗口,也没SurfaceControl。
  • DRAW_PENDING:有窗口,但没开始绘制。有SurfaceControl,但不能显示。
  • COMMIT_DRAW_PENDING,窗口刚刚绘制完,SurfaceControl还不能显示。
  • READY_TO_SHOW:窗口已经绘制完了,SurfaceControl可以显示了,但没必要,再等等。
  • HAS_DRAWN:窗口已经绘制完了,SurfaceControl也可以显示了。

1.2 显示SurfaceControl

回到WindowState.commitFinishDrawing,在调用WindowStateAnimator.commitFinishDrawingLocked将窗口的绘制状态走完后,接下来就是调用WindowStateAnimator.prepareSurfaceLocked来显示SurfaceControl了。

注意这个方法被调用的地方有两处:

在这里插入图片描述

还有一处调用的地方在WindowState.prepareSurfaces,这个是更通用的流程,但是动画流程下,则稍微不同,即我们分析的这个流程。

看代码:

在这里插入图片描述

我们只看和显示SurfaceControl相关的部分:

1)、如果窗口不在屏幕上,则调用WindowStateAnimator.hide -> WindowSurfaceController.hide来隐藏SurfaceControl。

2)、如果窗口在屏幕上,那么进一步判断窗口的绘制状态,只有窗口的绘制状态为HAS_DRAWN,才能继续调用WindowSurfaceController.showRobustly来显示SurfaceControl:

在这里插入图片描述

关键的就那一句,调用Transaction.show来显示相关SurfaceControl,但是要注意的是这里并没有调用Transaction.apply,所以这个时候窗口还是没有显示。

窗口的最终显示则是和这个传参Transaction对象有关,这个Transaction对象则是之前说的”start transaction“,那么这个Transaction的apply方法的调用时机则是跟Transition的流程相关,以后的分析会看到。

2 计算动画目标

这一节的内容是调用Transition.calculateTargets来计算动画的目标:

在这里插入图片描述

Transition的成员变量mTargets定义为:

在这里插入图片描述

之前收集到的动画参与者提升后的最终的动画目标,也就是说最终执行动画的主体并非是之前收集到的动画参与者,而是这一步用动画参与者计算得到的动画目标。

Transition.calculateTargets的内容为:

在这里插入图片描述

大致的内容为:

1)、创建一个Transition.Targets类型的局部变量targets,来收集动画目标。

2)、遍历Transition.mParticipants,从Transition.mChanges中取出对应的ChangeInfo对象放到Transition.Targets.mArray中,但是跳过WindowState类型的动画参与者,以及跳过那些根据ChangeInfo.hasChanged得出前后没有发生变化的动画参与者。

3)、调用Transition.tryPromote尝试提升targets中保存的动画目标的级别。

我们这一节主要来看下这个Transition.tryPromote。

”promote“,提升的动画目标在WindowContainer层级结构中的级别,这个逻辑之前在AppTransitionController.getAnimationTargets也用到了,思想都是类似的。比如一个Task中有两个ActivityRecord,并且这两个ActivityRecord要分别执行一段动画,也就是动画执行的主体是ActivityRecord。如果这两个ActivityRecord刚好都想向左平移同样的距离,那么我们就不需要为这两个ActivityRecord分别应用一段平移的动画,而是直接将这个平移的动画应用到它们共同的父容器Task上,并且实现的效果是一样的。这也就是”promote“的含义,动画的目标主体从ActivityRecord”提升“到了更高一级的Task上。

接着看代码,Transition.tryPromote。

2.1 Transition.tryPromote

在这里插入图片描述

主要逻辑为遍历Targets.mArray中的每一个ChangeInfo对象,调用Transition.canPromote方法来判断他们是否能够提升为父容器。

1)、如果不能,直接跳过该ChangeInfo对象,判断下一个。

2)、如果能,就说明提升成功。此外还要调用Transition.reportIfNotTop来继续判断它是否是organized(我的理解就是这个WindowContainer是否是系统开机后自动创建的,不是需要的时候再去创建的)。如果不是,那么将当前WindowContainer对应的ChangeInfo从局部变量targets中移除,然后把它的父WindowContainer对应的ChangeInfo加如到targets中。如果是,那么在不移除当前WindowContainer对应的ChangeInfo的前提下,把它的父WindowContainer对应的ChangeInfo加如到targets中。这里应该是针对organized的WindowContainer的特殊处理,确保organized的WindowContainer的变化也能够报告到WMShell那边。

因此重点其实是Transition.canPromote逻辑。

2.2 Transition.canPromote

在这里插入图片描述

感觉这段代码还是比较重要的,我们逐行分析。

2.2.1 片段1

在这里插入图片描述

1)、对应WindowContainer.canCreateRemoteAnimationTarget方法,目前只有TaskDisplayArea、TaskFragment以及ActivityRecord会返回true,其它类型的WindowContainer都会返回false,也就是说父容器不是这几类的WindowContainer将无法得到提升,那么目前只有这几种提升:WindowState到ActivityRecord,ActivityRecod到TaskFragment,TaskFragment到TaskFragment(因为TaskFragment存在嵌套,比如Home类型的TaskFragment),以及TaskFragment到TaskDisplayArea。另外从Transition.calculateTargets的逻辑我们看到了执行动画的target至少是WindowToken这一级的,并且看收集的逻辑,似乎也没有看到过直接收集WindowState的,因此实际上提升只存在以下几种情况:

  • ActivityRecod到TaskFragment。
  • TaskFragment到TaskFragment。
  • TaskFragment到TaskDisplayArea。

2)、如果找不到父WindowContainer对应的ChangeInfo,则不提升,返回false。

3)、如果父WindowContainer有ChangeInfo,但是此时的状态和收集开始时的状态没有变化,则不提升,返回false。

2.2.2 片段2

在这里插入图片描述

1)、如果当前要提升的WindowContainer是Wallpaper类型的,则不提升,返回false。

2)、如果当前WindowContainer前后的父WindowContainer不一致,即发生reparent了,则不提升,返回false。

2.2.3 片段3

在这里插入图片描述

遍历父WindowContainer的所有子WindowContainer:

1)、如果姊妹WindowContainer在Transition.mChanges中找不到一个对应的ChangeInfo对象,或者有这么一个ChangeInfo对象,但是该ChangeInfo对象不在Targets.mArray中,这种情况一共可以理解为这个姊妹WindowContainer没有参与到本次动画,那么还需要继续判断:

----1.1)、如果该姊妹WindowContainer可见,那么就不提升,直接返回false,当前WindowContainer无法提升到父WindowContainer。毕竟该姊妹WindowContainer是没有参与到动画中的,并且是可见的,如果你提升了,那后续动画执行的时候用户不是会看到该姊妹WindowContainer跟着一起动了嘛,这肯定是不对的。

----1.2)、如果该姊妹WindowContainer不可见,那么就跳过对这个WindowContainer的检查。不可见的姊妹WindowContainer对于本次动画也没有太大影响,即使跟着一起进行动画用户也看不到,直接跳过检查下一个姊妹WindowContainer就好了。

2)、如果姊妹WindowContainer从Transition.mChanges中能找到一个对应的ChangeInfo对象,并且该ChangeInfo对象也在局部变量targets中,那么认为该姊妹WindowContainer也参与了本次动画,那么分别为他们的TransitionMode调用Transition.reduceMode方法来看它们动画的大方向是否是一致的,首先是根据ChangeInfo.getTransitMode拿到各自的TransitionMode:

在这里插入图片描述

TransitionMode定义在TransitionInfo中:

在这里插入图片描述

看到Transition模式其实就是定义在WindowManager中的Transition类型的子集。

ChangeInfo.getTransitMode的内容也比较简单:

TRANSIT_CHANGE:收集阶段的可见性和Transition就绪阶段的可见性没有发生变化。

TRANSIT_OPEN:存在发生了变化,且当前可见,即从无到有。

TRANSIT_CLOSE:存在发生了变化,且当前不可见,即从有到无。

TRANSIT_TO_FRONT:存在没有发生变化,且当前可见,说明从后台移动到了前台,从不可见变为了可见。

TRANSIT_TO_BACK:存在没有发生变化,且当前不可见,说明从前台移动到了后台,从可见变为了不可见。

再根据Transition.reduceMode的逻辑:

在这里插入图片描述

  • TRANSIT_TO_BACK和TRANSIT_CLOSE是一类的。
  • TRANSIT_TO_FRONT和TRANSIT_OPEN是一类的。
  • TRANSIT_CHANGE单独一类。

如果动画的大方向是一致的,那么即使TRANSIT_TO_BACK和TRANSIT_CLOSE的动画有点差别,但是为了大局考虑,各别同志也不是不能适当调整一下来实现集体上的一致。

如果动画的大方向都不一致,那么它们中的无论哪个肯定都是不能提升为它们的父容器的。比如TaskA想向左平移,TaskB想向右平移,那么如果擅自提升为父容器TaskDisplayArea,不管TaskDisplayArea向左还是向右平移肯定都不合适,这种矛盾就属于不可调和了,那父容器TaskDisplayArea就不用管了,也就是别提升了,让冲突的TaskA和TaskB自己玩去吧。

3)、最后总结一下检查姊妹WindowContainer的这段逻辑,其实就是检查所有的姊妹WindowContainer中,有没有和当前WindowContainer冲突的姊妹WindowContainer,至于是否冲突则看是否满足了以下条件之一:

  • 检查所有没有参与动画的姊妹WindowContainer,看能否找到一个可见的。
  • 检查所有参与了动画的姊妹WindowContainer,看能否找到一个动画的大方向和当前WindowContainer不一致。

只要找到了这么一个姊妹WindowContainer,我们就无法提升动画的主体。

3 构建TransitionInfo对象

在这里插入图片描述

这一节我感觉其实没有什么好说的,大概介绍一下TransitionInfo以及它的内部类Change。

在这里插入图片描述

1)、TransitionInfo,实现了Parcelable,结合注释,用来收集WMCore这边的Transition信息,用来同步给WMShell的TransitionPlayer。成员变量大概有这些:

在这里插入图片描述

2)、TransitionInfo.Change,同样实现了Parcelable,代表了WindowContainer在一个Transition期间的变化。看其成员变量,保存的信息还是挺多的,还有一个RunningTaskInfo的对象:

在这里插入图片描述

再结合Transition.calculateTransitionInfo方法,很明显就大概能弄懂这两个类的作用:

1)、TransitionInfo,对应一个Transition对象,用来收集WMShell感兴趣的Transition的信息,后续同步给WMShell。

2)、TransitionInfo.Change,对应一个Transition.ChangeInfo对象,用来收集WMShell感兴趣的Transition.ChangeInfo的信息,后续同步给WMShell。

顺便一提,google为啥不将Transition中ChangeInfo的命名为”Change“,将TransitionInfo中的Change命名为”ChangeInfo“呢,强迫症犯了。

4 Transition移动到PLAYING状态

在这里插入图片描述

其实在Transition.onTransactionReady方法的开头已经将Transition.mState状态置为STATE_PLAYING,这里又调用了一个TransitionController.moveToPlaying方法,看下是干啥的:

在这里插入图片描述

其实也非常简单:

1)、开始动画了,意味着当前Transition已经不能收集了,所以将TransitionController.mCollectingTransition置空。特别的,如果有其它Transition在排队,那么就继续将TransitionController.mCollectingTransition赋值为排队队列队首的那个Transition,我播我的动画,你收集你的WindowContainer,互不干扰。

2)、将当前Transition添加到TransitionController.mPlayingTransitions:

在这里插入图片描述

一个当前处于playing状态的Transition的队列,也就是说playing的Transition可以有多个。

5 切换到WMShell:onTransitionReady

在这里插入图片描述

在Transition.onTransactionReady方法的最后,调用了ITransitionPlayer.onTransitionReady方法将切换到了WMShell:

在这里插入图片描述

切换到WMShell意味着Transition就绪阶段已经结束,正式进入Transition的playing阶段,Transitions.TransitionPlayerImpl.onTransitionReady就是我们下一篇文章的起点。

最后稍微看一下调用ITransitionPlayer.onTransitionReady方法之前调用的Transition.buildFinishTransaction方法:

在这里插入图片描述

传入的Transaction对象为Transition.mFinishTransaction,如该方法的注释所说,这里对”finish transaction“的操作保证了动画结束后,所有的”reparent“操作或者是Layer的变化将会得到重置,特别是Layer的几何信息(位置、缩放、旋转这些)。如果你的Layer在动画结束的时候在Layer的这些信息上的确有变化,那就要注意不要让这个方法把你对Layer的操作重置了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

标准IO(Ubuntu)

标准IO 什么是标准IO 头文件stdio.h 用的最多的两个输入输出函数printf和scanf 针对终端的输入输出函数 数据输入到特定的文件需要的条件 需要知道指定文件的文件名和位置 输入到文件中的内容 指定文件 fopen函数 FILE *fopen(const char *pathname, const char *mode) 功能…

pnpm install安装失败

ERR_PNPM_META_FETCH_FAIL GET https://registry.npmjs.org/commitlint%2Fcli: request to https://registry.npmjs.org/commitlint%2Fcli failed, reason: connect ETIMEDOUT 2606:4700::6810:123:443 1. 检查网络连接 确保你的网络连接正常并且没有被防火墙或代理服务器阻止…

Linux shell编程学习笔记64:vmstat命令 获取进程、内存、虚拟内存、IO、cpu等信息

0 前言 在系统安全检查中,通常要收集进程、内存、IO等信息。Linux提供了功能众多的命令来获取这些信息。今天我们先研究vmstat命令。 1.vmstat命令的功能、用法、选项说明和注意事项 1.1 vmstat命令的功能 vmstat是 Virtual Meomory Statistics(虚拟内…

【Java数据结构】初始线性表之一:链表

为什么要有链表 上一节我们描述了顺序表:【Java数据结构】初识线性表之一:顺序表-CSDN博客 并且进行了简单模拟实现。通过源码知道,ArrayList底层使用数组来存储元素。 由于其底层是一段连续空间,当在ArrayList任意位置插入或者…

NGFW和防火墙的区别?

NGFW(Next Generation Firewall,下一代防火墙)和FW(Firewall,防火墙)在网络安全领域都扮演着重要角色,但它们在功能、性能和应用场景上存在显著的区别。以下是NGFW和FW之间的主要区别&#xff1…

数据库作业九

1、安装redis,启动客户端、验证。 2、string类型数据的命令操作: (1) 设置键值: (2) 读取键值: ​ (3) 数值类型自增1: ​ (4&am…

NFT项目的第三方功能及接口

NFT项目涉及到第三方功能及接口的情况非常常见,因为NFT项目本身的功能通常是有限的,需要通过与第三方功能和接口的整合来实现更丰富的功能和更好的用户体验。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 以下…

【LeetCode】十六、并查集

文章目录 1、并查集Union Find2、并查集find的优化:路径压缩 Quick find3、并查集union的优化:权重标记4、leetcode200:岛屿数量解法一:DFS 1、并查集Union Find 并查集,一种树形的数据结构,处理不相交的两…

如何在excel表中实现单元格满足条件时整行变色?

可以试试使用条件格式: 一、条件格式 所谓“自动变色”就要使用条件格式。 先简单模拟数据如下, 按 B列数字为偶数 为条件,整行标记为蓝色背景色。 可以这样设置: 先选中1:10行数据,在这里要确定一下名称栏里显示…

DNS查询过程

DNS(域名系统,Domain Name System)是一个用于将域名和IP地址相互映射的系统。当你在浏览器中输入一个网址时,浏览器会通过DNS查询过程来找到对应的IP地址,以便能够连接到目标服务器。其查询过程一般通过以下步骤&#…

Apple Vision Pro 和其商业未来

机器人、人工智能相关领域 news/events (专栏目录) 本文目录 一、Vision Pro 生态系统二、Apple Vision Pro 的营销用例 随着苹果公司备受期待的进军可穿戴计算领域,新款 Apple Vision Pro 承载着巨大的期望。 苹果公司推出的 Vision Pro 售…

MySQL数据库慢查询日志、SQL分析、数据库诊断

1 数据库调优维度 业务需求:勇敢地对不合理的需求说不系统架构:做架构设计的时候,应充分考虑业务的实际情况,考虑好数据库的各种选择(读写分离?高可用?实例个数?分库分表?用什么数据库?)SQL及索引:根据需求编写良…

Qt窗口程序整理汇总

到今日为止,通过一个个案例的实验,逐步熟悉了 Qt6下 窗体界面开发的,将走过的路,再次汇总整理。 Qt Splash样式的登录窗https://blog.csdn.net/castlooo/article/details/140462768 Qt实现MDI应用程序https://blog.csdn.net/cast…

数据库课设---学生宿舍管理系统(sql server+C#)

1.引言 1.1 内容及要求 设计内容:设计学生宿舍管理系统。 设计要求: (1)数据库应用系统开发的需求分析,写出比较完善系统功能。 (2)数据库概念模型设计、逻辑模型设计以及物理模型设计。 …

【数学建模】——多领域资源优化中的创新应用-六大经典问题解答

目录 题目1:截取条材 题目 1.1问题描述 1.2 数学模型 1.3 求解 1.4 解答 题目2:商店进货销售计划 题目 2.1 问题描述 2.2 数学模型 2.3 求解 2.4 解答 题目3:货船装载问题 题目 3.1问题重述 3.2 数学模型 3.3 求解 3.4 解…

JS爬虫实战之极验四代

极验四代滑块验证码 一、目标网站说明二、流程步骤1. 逆向步骤一般分为:2. 接口确认1- 确认流程2- 获取verify的参数3- 构建requests验证verify的参数4- 锁定secode参数的作用 ok,让我们去获取verify接口中的响应!!! 3…

el-table表格操作列错行处理

解决方法&#xff1a; <style>::v-deep .el-table th.el-table__cell > .cell {white-space: nowrap !important;} </style>

【C++航海王:追寻罗杰的编程之路】智能指针

目录 1 -> 为什么需要智能指针&#xff1f; 2 -> 内存泄漏 2.1 ->什么是内存泄漏&#xff0c;以及内存泄漏的危害 2.2 -> 内存泄漏分类 2.3 -> 如何避免内存泄漏 3 -> 智能指针的使用及原理 3.1 -> RAII 3.2 -> 智能指针的原理 3.3 -> std…

Kafka Producer发送消息流程之Sender发送线程和在途请求缓存区

文章目录 1. Sender发送数据1. 发送数据的详细过程&#xff1a;2. 关键参数配置 2. 在途请求缓存区 1. Sender发送数据 Sender线程负责将已经在RecordAccumulator中准备好的消息批次发送到Kafka集群。虽然消息在RecordAccumulator中是按照分区组织的&#xff0c;但Sender线程在…

【C++】类和对象的基本概念与使用

本文通过面向对象的概念以及通俗易懂的例子介绍面向对象引出类和对象。最后通过与之有相似之处的C语言中的struct一步步引出C中的类的定义方式&#xff0c;并提出了一些注意事项&#xff0c;最后描述了类的大小的计算方法。 一、什么是面向对象&#xff1f; 1.面向对象的概念 …