书接上回,话说当WMCore部分走到了Transition.onTransactionReady,计算完参与动画的目标,构建出TransitionInfo后,接下来就把这个包含了动画参与者的TransitionInfo发给了WMShell,然后就该播放动画了,这部分在WMShell。
1 TransitionPlayerImpl.onTransitionReady
TransitionPlayerImpl是ITransitionPlayer的本地实现,这里继续调用了Transitions.onTransitionReady方法,但要注意的是,这里并不是直接调用了Transitions.onTransitionReady,而是将这个调用操作放到了主线程中执行,那么这里多多少少会有一些延迟,更甚者,如果SystemUI主线程在做耗时操作,那么就会更晚走到Transitions.onTransitionReady,这影响的是这里的传参,Transaction类型的“t”,即之前的文章所提到的“start transaction”的apply的时间,严重点就会引发ANR,关于这一点当我们分析到“start transaction”被apply的时候再聊聊。
2 Transitions.onTransitionReady
这个方法也简单,主要看下这个局部变量activeIdx,之前在:
【Android14 ShellTransitions】(五)启动Transition这一节的内容涉及WMCore以及W - 掘金
这一文的分析中,主要是在Transitions.requestStartTransition中:
WMShell侧根据从WMCore侧传来的Transition对象的token,创建了一个与Transition对象一一对应的ActiveTransition对象,并且将这个传过来的token保存到了ActiveTransition.mToken中,然后将这个ActiveTransition对象保存到了Transitions.mPendingTransitions中。
现在回到Transitions.onTransitionReady,当WMCore侧再次把Transition的token发过来的时候,我们就可以根据这个token,从Transitions.mPendingTransitions中取到对应的ActiveTransition对象。
另外看到这里也把一些传参保存到了ActiveTransition的各个成员变量中,后续就直接从ActiveTransition中拿,而不是由Transitions保存,因为Transitions可能要同时处理多个Transition/ActiveTransition。
另外就如这里的注释所说,ActiveTransition发生了改变,“Move from pending to ready”,也就是说当ActiveTransition创建后就一直是pending的状态,当WMCore侧走到Transition.onTransactionReady,即Transition就绪的时候,再通知WMShell说WMCore这边就绪了,该你这边了,然后ActiveTransition就从等待状态切换到就绪状态(虽然没有一个状态值来表明ActiveTransition所处的状态)。
最后我们只分析最普通的单个Transition的情况,看到这里继续调用了Transitions.dispatchReady方法。
3 Transitions.dispatchReady
1)、为ActiveTransition分配一个Track,然后将该ActiveTransition添加到Track.mReadyTransitions。
看下Track类的定义:
Track用来播放动画,其成员变量mReadyTransitions保存了处于ready状态但是还没有轮到播放的ActiveTransition,它的存在说明了Track是可以收集多个ActiveTransition来并行播放动画的,成员变量mActiveTransition代表了正在播放动画的那个ActiveTransition。
2)、调用Transitions.setupStartState方法,该方法如其名字所表达的,用来设置动画初始状态的可见性、透明度和变换。
3)、继续调用Transitions.processReadyQueue。
这里看下Transitions.setupStartState方法:
这个方法是用来设置参与动画的一些SurfaceControl的初始状态的,其实也没啥好看的,唯一需要注意的是这里会对动画参与者的SurfaceControl进行一些设置,保证了一些动画的基本逻辑能够得到满足,就比如说对于TRANSIT_OPEN和TRANSIT_TO_FRONT类型的动画参与者,这里会强制它们在动画开始的时候显示,此外它会重置SurfaeControl的缩放和旋转等属性,如果你之前因为一些特殊需求改了SurfaceControl的缩放比例,那么就要注意这里不要让你的修改失效了。同样的对于TRANSIT_CLOSE和TRANSIT_TO_BACK类型的动画参与者,这里的逻辑则是保证了该动画参与者在动画结束后会被隐藏。
4 Transitions.processReadyQueue
1)、在我们的分析流程中,上一步Transitions.dispatchReady向Track.mReadyTransitions添加了就绪的ActiveTransition,所以这里Track.mReadyTransitions不为空。
2)、由于之前我们没有设置过Track.mActiveTranstion,那么这里我们会将Track.mReadyTransitions队首的那个ActiveTransition对象取出来,赋值给Track.mActiveTranstion,然后从Track.mReadyTransitions中移除,接着为ActiveTransition对象调用Transitions.playTransition,那么这个ActiveTransition会从ready状态变为playing状态,最后再调用一次Transitions.processReadyQueue。
3)、当再进入Transitions.processReadyQueue后,如果Track.mReadyTransitions仍然不为空,那么说明此前Track至少有两个ready的ActiveTransition,并且首个进入Transitions.processReadyQueue的那个ActiveTransition已经变为playing了,那么会尝试将剩下的这个ready的ActiveTransition和这个playing的ActiveTransition的动画合并,不过这部分我了解的很少,暂时跳过。
继续看Transitions.playTransition。
5 Transitions.playTransition
分为三个部分:
1)、Transitions.setupAnimHierarchy用来在动画开始前,将动画参与者reparent到共同的父Layer上,然后设置它们的Z轴层级。
2)、之前我们在Transitions.requestStartTransition中,尝试为每一个TransitionHandler调用handleRequest,来找到能处理当前ActiveTransition的那个TransitionHandler,如果能够找到,那么将这个TransitionHandler保存在ActiveTransition.mHandler中。这里便是调用这个TransitionHandler的startAnimation方法,让这个TransitionHandler来优先处理当前ActiveTransition,如果能够处理,那么就会返回true,这个ActiveTransition就算找到可以托付终生的handler了。这里也能看到,真正决定Transition被谁处理的是TransitionHandler.startAnimation,TransitionHandler.handleRequest只是给你一个优先处理的机会。
3)、如果TransitionHandler.startAnimation返回false,那还得继续调用Transitions.dispatchTransition来遍历Transitions.mHandlers中的所有TransitionHandler,继续找可以处理当前ActiveTransition的TransitionHandler。
接下来分别介绍。
5.1 Transitions.setupAnimHierarchy
Transitions.setupAnimHierarchy用来在动画开始前,将动画参与者reparent到一个共同的父Layer上,然后设置它们的Z轴层级。
我这里是从Launcher打开Message,截图为:
能看到在TaskDisplayArea下创建了一个名为“Transition Root:…”的Layer,作为动画参与者的共同parent。
然后是设置层级的逻辑,主要是根据:
- “global transit type”,即TransitionInfo的类型。
- “their transit mode”,即Change的mode。
- “their destination z-orde”,即Change的目标Z轴层级(如果有的话)。
举个例子,比如上面的从Launcher打开Message的例子:
1)、TransitionInfo的类型为TRANSIT_TO_FRONT。
2)、Launcher对应的Change的mode为TRANSIT_TO_BACK,Messsage对应的Change的mode为TRANSIT_TO_FRONT。
对于这个场景,我们更想突出的应该是Message的打开动画,因此Message对应的Change的Z轴层级应该调高,而Launcher对应的Change的Z轴层级则应该调低,就像代码里面标黄的那样。
动画参与者是两个Task,那么这里的zSplitLine就是3,numChanges是2,Message的Task先被添加,因此i=0,Launcher的Task的i=1。
最终算出给Message的Task#11设置的Z轴层级为5:
Launcher的Task#1设置的Z轴层级为2:
因此Message盖在了Launcher之上,突出的是Message的动画。
5.2 TransitionHandler.startAnimation
翻译一下注释:startAnimation用来启动一个过渡动画。对于某一个特定的Transition,如果该TransitionHandler的handleRequset方法返回一个non-null的值的话,那么该TransitionHandler的startAnimation将总是会被调用。否则,只有当排在该TransitionHandler之前的其它TransitionHandler都没有办法处理Transition的时候,该TransitionHandler的startAnimation才会被调用。
这段注释也即Transitions.playTransition方法内容的描述。
实现该接口的TransitionHandler子类有一二十个:
作用于不同场景下的过渡动画,这里只分析一个典型的DefaultTransitionHandler。
我们之前分析的场景是从Launcher启动Message,由于现在从Launcher启动App的动画一般都是自定义的,所以走的并不是DefaultTransitionHandler,而是RemoteTransitionHandler,所以为了想要DefaultTransitionHandler处理调起Message的动画(App切换),我这里通过adb命令从Launcher调起Message。
5.2.1 设置回调Runnable
设置一个Runnable,该Runnable将在动画结束的时候执行(后续分析会看到,这里先提一嘴),执行的则又是传参finishCallback的onTransitionFinished方法,再看我们分析的流程下,TransitionHandler.startAnimation方法被调用的两处地方,分别在Transitions.playTransition和Transitions.dispatchTransition,得知:
最终调用的是Transitions.onFinish方法。
5.2.2 加载动画
调用DefaultTransitionHandler.loadAnimation方法来加载动画样式:
如果对之前AppTransition和AppTransitionController中的动画逻辑有印象的话,那么看到这里应该会很熟悉,这里差不多就是AppTransition.loadAnimation,主要就是根据动画的类型,以及是否有自定义动画来选择动画的样式。
其中自定义动画一般由App通过ActivityOptions的各种makeXXXAnimation方法指定:
自定义动画还是少数,大部分情况下动画则是TRANSIT_OPEN、TRANSIT_TO_FRONT、TRANSIT_CLOSE以及TRANSIT_TO_BACK这几类,并且是没有自定义动画的,如我们分析的用adb调起Message App的场景,那么会继续调用TransitionAnimationHelper.loadAttributeAnimation来选择动画样式:
这里的逻辑一看就懂,分的也非常细,根据Wallpaper的可见性是否发生变化,是否有透明的App参与,参与者是Task还是Activity之类的,都有有相应的动画可以选择。比如我们分析的场景,启动的是Message App,那么TransitionInfo的type为TRANSIT_TO_FRONT,并且Message对应的Change的mode也为TRANSIT_TO_FRONT,那么最终选择的动画就是R.styleable.WindowAnimation_taskToFrontEnterAnimation。
最后要注意我们这里拿到的只是一个资源ID,最终还是要通过TransitionAnimation.loadDefaultAnimationAttr来将这个资源ID转化为Animation对象,大概过一下:
方法调用顺序为:
TransitionAnimation.loadDefaultAnimationAttr
-> TransitionAnimation.loadAnimationAttr
-> TransitionAnimation.loadAnimationAttr
-> TransitionAnimation.loadAnimationSafely
继续调用了AnimationUtils.loadAnimation方法:
方法调用顺序为:
AnimationUtils.loadAnimation
-> AnimationUtils.createAnimationFromXml
-> AnimationUtils.createAnimationFromXml
看到最终是调用了AnimationUtils.createAnimationFromXml方法,从指定的package中根据对应资源ID来解析相应的xml文件,然后将xml文件中的不同的标签解析成对应的Animation类型,这里看到有多个标签,“alpha”、“scale”、“rotate”和“translate”等等。
比如我们刚刚提到的,在我们分析的场景下,选取的动画资源ID为R.styleable.WindowAnimation_taskToFrontEnterAnimation。
它声明在”frameworks/base/core/res/res/values/attrs.xml“中:
定义在”frameworks/base/core/res/res/values/styles.xml“:
然后它的动画对应的就是”frameworks/base/core/res/res/anim/task_open_enter.xml“:
标签为”translate“,那么会解析为一个TranslateAnimation动画对象。
最后有一点要注意的就是,由于在我们分析的流程下,我们没有自定义动画,因此加载的是默认包名:
private static final String DEFAULT_PACKAGE = "android";
“android“下的动画,即google的原生动画。
如果我们有自定义动画,比如我用Activity.overridePendingTransition自定义Activity的进入和退出动画,就像:
mPendingEnterRes = R.anim.edge_extension_right;
mPendingExitRes = R.anim.alpha_0;
overridePendingTransition(mPendingEnterRes, mPendingExitRes);
那么后续动画使用的就是我在”R.anim.edge_extension_right“和”R.anim.alpha_0“中定义的动画:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false">
<alpha android:interpolator="@android:interpolator/linear" android:duration="10000" android:fillBefore="true" android:fillAfter="true"
android:startOffset="0" android:fromAlpha="1" android:toAlpha="1" android:fillEnabled="true"/>
<scale android:interpolator="@android:interpolator/linear" android:duration="10000" android:startOffset="0"
android:fromXScale="0.5" android:toXScale="0.5" android:fromYScale="1" android:toYScale="1"/>
</set>
这些动画的资源文件保存在”app\src\main\res\anim\alpha_0.xml“:
但是要注意的是我这里的自定义动画并不属于远程动画,因为我没有指定一个RemoteTransition,看log,最终处理动画的还是DefaultTransitionHandler:
像Launcher这样的调用ActivityOptions.makeRemoteAnimation传入一个RemoteAnimationAdapter和RemoteTransition的才是:
log为:
这里不对远程动画进行展开。
5.2.3 构建作用于leash的animator
DefaultTransitionHandler.startAnimation创建了一个局部变量animations,用来收集动画参与者的animator,当我们在第二步通过DefaultTransitionHandler.loadAnimation为动画参与者创建了动画后,接下来就是调用DefaultTransitionHandler.buildSurfaceAnimation来创建相应的animator:
我们主要关注创建的这个ValueAnimator。
1)、调用ValueAnimator.addUpdateListener为这个ValueAnimator添加一个监听每一帧进行更新的ValueAnimator.AnimatorUpdateListener,在每一帧到来的时候,调用DefaultTransitionHandler.applyTransformation来对leash进行更新。
2)、DefaultTransitionHandler.applyTransformation这个方法也很简单,这里不再贴代码,主要是根据当前动画的播放时间来计算Transformation,然后对相应的leash(动画参与者的SurfaceControl)进行设置,从而完成每一帧的SurfaceControl更新。
3)、调用ValueAnimator.addUpdateListener为这个ValueAnimator添加一个监听动画生命周期的AnimatorListenerAdapter,这里主要是在动画正常结束或者异常取消的时候,调用传参finishCallback的run方法,这将调用DefaultTransitionHandler.startAnimation的传参finishCallback的onTransitionFinished方法,最终将调用Transitions.onFinish方法,这个我们之前也有提过。
4)、构建了这个ValueAnimator,并且设置完动画监听器后,就把这个ValueAnimator对象添加到DefaultTransitionHandler.startAnimation创建的这个局部变量animations中。
5.2.4 应用”start transaction“
这一步很简单,即调用”start transaction“的Transaction.apply方法,但是我们需要着重强调一下。
1)、回看:
【Android14 ShellTransitions】(七)Transition就绪ShellTransitions - 掘金
在1.2小节显示SurfaceControl中,由于这里我们使用的Transaction是通过WindowContainer.getSyncTransaction方法得到的,因此这个Transaction中收集到的Surface的数据何时会被应用,是由Transition动画流程决定的。
2)、再根据:
【Android14 ShellTransitions】(六)SyncGroup完成BLASTSyncEngine Sy - 掘金
后续当SyncGroup完成后,在SyncGroup.finishNow中,所有动画参与者的SyncTransaction都会被合并到一个名为merge的Transaction对象中,接着这个Transaction对象会被传入Transition.onTransactionReady中, 也就是我们所说的”start transaction“。
3)、后续走到WMShell的Transitions.onTransitionReady后,这个”start transaction“被保存在了ActiveTransition.mStartT中。
4)、直到此时,构建完animation以及animator,马上就要开始动画的时候,”start transaction“才会被apply。
这是很长的一段路,比如我们从Launcher启动Message的流程,Message的Surface真正显示的时间点并不是WindowSurfaceController.showRobustly,而是动画即将开始之前的这里。不过这似乎是没办法的事,如果Surface过早显示,然后由于SystemUI主线程卡顿,结果在Surface显示两三秒后又开始动画了,那不是更奇怪。
现在再回看分析TransitionPlayerImpl.onTransitionReady时我提到的,如果SystemUI主线程卡顿,那么从Binder线程切换到主线程就需要非常多的时间,也就是动画流程被延迟了,那么这也将极大的延迟”start transaction“被应用的时间,导致”start transaction“的内容无法被及时提交到SurfaceFlinger。
比如在我们分析的Launcher启动Messag场景中,”start transaction“中包含着对Message的Surface进行Transaction.show的设置,”start transaction“被延迟应用就意味着Message的Surface被延迟显示,如果超过5s还没应用,那么就有可能会引发无焦点窗口的ANR(在上层WMS窗口焦点已经切换到Message,但是在SF层Message的Layer由于没有显示因此在InputDispatcher侧无法作为焦点窗口),这种情况下一般还会伴随着以下log:
”sent“花费的时间不到1s,但是”finished“的时候总共用时5s以上,当时有这个log不能说一定是SystemUI主线程卡顿,只能说嫌疑比较大。
因此这类ANR问题,发生的瓶颈不在于App绘制速度,而在于SystemUI主线程卡顿。由于现在ShellTransitions的动画播放部分放到SystemUI进程处理,而SystemUI进程本身也有很多常驻窗口要显示,因此这对SystemUI的性能提出了过高的要求。
如果没有了解现在的动画流程的话,应该也想不到SystemUI主线程卡顿是此类ANR问题的原因吧,哈哈哈…(google你是真爱折腾啊,某种植物!)
5.2.5 开始动画
分析了那么久,正菜终于上了,其实也很简单,就调用了Animator.start方法来启动动画而已,一直以来都是这么干的,没啥好讲的,这就好比在厨房洗切炒叮铃咣啷的,一顿操作猛如虎,上桌一看吃红薯。
我这里把动画的生命周期打印了调用堆栈,大概看下:
1)、onAnimationStart:
2)、onAnimationEnd:
当动画结束的时候,回调的是Transitions.onFinish方法。
5.3 Transitions.dispatchTransition
在分析Transitions.onFinish之前,我们还剩最后一点关于Transitions.dispatchTransition的内容没讲:
根据之前Transitions.playTransition的内容,如果ActiveTransition.mHandler无法处理当前ActiveTransition的话,那么我们就要给Transitions.mHandlers中的其它TransitionHandler一个机会,看它们能不能处理这个ActiveTransition了,内容也很简单,不再赘述,唯一需要注意的是这里遍历的顺序和TransitionHandler添加的顺序相反:
如我这里的log,标蓝色的是添加的log,标黄的是遍历的log,可以看到DefaultMixedHandler是最后一个添加的,但是遍历的时候是先调用的DefaultMixedHandler的startAnimation方法。
6 Transitions.onFinish
这个方法的内容也不多:
1)、这里是”finish transaction“被apply的地方。
2)、调用WindowOrganizer.finishTransition,最终重新回到系统系统WMCore走Transition的finish流程。
3)、由于当前Transition的动画已经结束,那么继续调用Transitions.processReadyQueue来重新看看有没有准备好的Transition可以开始播放动画的。
下一篇文章继续分析Transition的finish流程。