当前位置: 首页 > news >正文

【Android14 ShellTransitions】(八)播放动画

在这里插入图片描述

书接上回,话说当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流程。


http://www.mrgr.cn/news/61263.html

相关文章:

  • 【Spring Boot 应用开发】-04-01 自动配置-数据源-连接池
  • ClickHouse-CPU、内存参数设置
  • 代理模式和适配器模式有什么区别
  • 非PHP开源内容管理系统(CMS)一览
  • PL/SQL语言的数据库交互
  • Python 通过命令行在 unittest.TestCase 中运行单元测试
  • 基于边缘计算的智能门禁系统架构设计分析
  • 【JIT/极态云】技术文档--数据处理
  • 线上虚拟3D画展有何优势?
  • DRC-20开发指南:新一代区块链代币标准
  • LeetCode HOT100系列题解之课程表(9/100)
  • 什么是crm软件?最全百科
  • 掌握机器学习中的偏差与方差:模型性能的关键
  • 【pyvimomi脚本实践01】使用 Python 连接 VMware ESXi 并获取主机信息
  • 【ROS2】cv_bridge:ROS图像消息和OpenCV的cv::Mat格式转换库
  • 深度学习(七)深度强化学习:融合创新的智能之路(7/10)
  • pyinstaller 打包python GUI程序避坑指南
  • 串联两个检测模型
  • 金山C++一面-面经总结
  • HTB:Analytics[WriteUP]
  • [Linux进程控制]进程创建|进程终止|进程等待
  • 练习pandas的基础操作,有题目有详解有源码
  • 使用不可靠来源的住宅代理的风险
  • 怎么提取视频里的音频?关于提取视频里音频的几种方法
  • Gartner发布生成式AI的信任、风险和安全管理创新指南:生成式AI的三类风险—内容异常、数据安全和AI应用安全
  • 从理论到实践,精准测试的初创之路