【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的操作重置了。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值