一、前言

原文链接:https://www.jianshu.com/p/d41fe965e9e6

android的WindowManagerService(简称wms)是系统框架一个非常庞大复杂的一个系统模块,它主要由三大块组成:wms数据结构,wms大遍历,wms的窗口动画

wms总体图.png

wms数据结构就是wms的所有WindowState(继承windowcontainer)集合的数据结构,比如有ActivityRecord(包含1个或者多个WindowState),比如有WindowState,其中ActivityRecord具体表现实例就是Activity,WindowState具体表现实例有状态栏、导航键、输入法等。

window.png

wms大遍历(performSurfacePlacement)就是对当前所有存在的window进行窗口大小计算和窗口绘制状态更新,最后把窗口Surface更新到surfaceflinger。

wms的窗口动画是其中一个比较重要的子功能,wms的窗口动画负责窗口间的切换动画的实现。

接下来我们从android动画原理开始来逐步介绍wms的窗口动画

二、android动画的一个demo

android动画主要有三种类型:view的动画、window的动画、画布对象的动画(ondraw里面的画图api)
首先我们来看一个android动画的简单实现的demo

Choreographer mChoreographer = Choreographer.getInstance();Animation mAnimation = null;public void start(Animation anim) {mAnimation = anim;scheduleAnimation();}private void scheduleAnimation() {mChoreographer.postFrameCallback(Choreographer.CALLBACK_ANIMTION, mUpdateRunnable, null);}private Runnable mUpdateRunnable = new Runnable() {@Overridepublic void run() {if (mAnimation != null) {long time = SystemClock.uptimeMillis();Transformation transform = new Transformation();//根据当前time计算transformboolean more = mAnimation.getTransformation(time, transform);//根据transform进行渲染,改变view的属性(大小、位置、透明度等)?改变窗口的属性(大小、位置、透明度等)?PERFORM_RENDER_WITH_TRANSFORMATION(transform);//通过time的计算可以计算出动画是否继续还是结束if (more) {scheduleAnimation();} else {mAnimation = null;}}}};

从这个例子可以看出android的动画就是借用Choreographer来通过vsync原理逐帧控制动画的播放(需要对Choreographer有一定的了解),中间update变量transform包含了动画的基本元素:Matrix、透明度,然后根据这两个元素对显示对象(view或者画布对象或者window?)进行当前时间的绘制,逐帧显示,最终用户看到的就是一个动画,从systrace可以看到

动画systrace.png

ValueAnimator属性动画的实现原理也是类似于这个demo的实现

三、WindowManagerService窗口动画机制

android的WindowManagerService窗口动画机制一直在优化进步,主要体现在:

1、在androidP以前的版本,主要是通过WindowAnimator主动画类中的mChoreographer来通过vsync原理逐帧控制窗口动画的播放
具体窗口的动画变化由WindowStateAnimator的stepAnimationLocked来控制,通过改变窗口的大小、位置、透明度(通过SurfaceControl代理实现对surfaceflinger的调用),来最终达到窗口动画的实现

wms历史版本动画时序图.png

有兴趣的可以去仔细研究下这部分代码的实现,虽然是历史版本的旧代码,但是这个对wms的学习理解有很大的帮助。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimator.java *//** Locked on mService.mWindowMap. */private void animateLocked(long frameTimeNs) {
这个方案有个很大的缺陷,那就是动画的所有实现的代码都包含在wms的主锁mGlobalLock里面,从动画主要方法的命名后缀locked可以得知,那么意味动画会跟wms其他所有流程抢CPU资源,就容易导致wms主锁的卡顿,在某些复杂的用户场景下,容易导致手机的卡顿,给用户带来糟糕的体验。

2、在androidP及之后的版本,google对窗口动画进行了重构,主要思想是通过ValueAnimator属性动画来播放窗口动画,把窗口动画播放从wms主锁脱离出来,这样动画就不会占用wms资源,从而达到优化系统框架运行速度的效果,同时把部分动画放到app远端播放(比如状态栏、导航键动画,比如多任务动画),达到系统和APP双端协调播放复杂的跨端动画效果

3、wms的新窗口动画主要分为两种类型,LocalAnimationAdapter和RemoteAnimationAdapter,分别实现了wms本地窗口动画和远程窗口动画。远程窗口动画机制,主要是为了实现android的两个新功能特意开发的机制,一个是从桌面点击app图标进入app的入场动画和app退出的出场动画,一个是在app界面,通过拖动底部指示条进入桌面的滑动效果动画,这两个动画效果最先是iphone实现的,google为了仿iphone的实现,所以开发了远程动画机制,最终能达到iphone的动画效果,提高了android手机的复杂动画效果

4、本文主要介绍android新动画的流程和实现的原理,主要介绍了LocalAnimationAdapter本地窗口动画实现原理

四、新动画机制Local窗口动画流程

本地窗口动画具体场景:可以在设置首页点击其中一项菜单,进入设置某项子菜单,然后就会有一个Local窗口动画的播放

LocalAnimationAdapter,字面意思就是wms本地窗口动画,该动画在SurfaceAnimationThread(线程名android.anim.lf)线程播放,注意看该类的注释
(本文剩余源码基于androidS原生源码)

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationThread.java *//*** Thread for running {@link SurfaceAnimationRunner} that does not hold the window manager lock.*/public final class SurfaceAnimationThread extends ServiceThread {
这个才是新动画机制的核心要义,不占用wms主锁,就不会占用wms的资源,这个已经是对wms很大的优化了,android历史版本因为wms锁卡顿的问题太多了

接下来我们通过阅读源码来分析LocalAnimationAdapter的实现流程,先看下整体的Local窗口动画时序图

Local窗口动画时序图.png

1、动画播放源头类AppTransitionController的方法handleAppTransitionReady
在一次wms大遍历(performSurfacePlacement)流程结束之后,就会检查app transition是否已经准备好,opening 的app准备好需要满足app的starting窗口是否已经displayed或者app的window是否已经alldrawn,只要满足其中一个条件,就说明app的窗口动画流程可以开始了了。

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */void handleAppTransitionReady() {mTempTransitionReasons.clear();//检查app transition是否已经准备好if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)|| !transitionGoodToGo(mDisplayContent.mChangingContainers,mTempTransitionReasons)) {return;}Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");
首先会获取当前需要opening和closing的app window列表(ActivityRecord类型)
/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */final ActivityRecord topOpeningApp =getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);final ActivityRecord topClosingApp =getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);
然后在applyAnimations方法里面对window列表进行遍历WindowContainer的动画applyAnimation方法的调用
/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,@TransitionOldType int transit, boolean visible, LayoutParams animLp,boolean voiceInteraction) {final int wcsCount = wcs.size();for (int i = 0; i < wcsCount; i++) {final WindowContainer wc = wcs.valueAt(i);final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();for (int j = 0; j < apps.size(); ++j) {final ActivityRecord app = apps.valueAt(j);if (app.isDescendantOf(wc)) {transitioningDescendants.add(app);}}wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);}}

2、在WindowContainer,会先收集getAnimationAdpater当前window的动画适配器

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,@TransitionOldType int transit, boolean isVoiceInteraction,@Nullable ArrayList<WindowContainer> sources) {final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,transit, enter, isVoiceInteraction);

如果是普通的窗口动画,比如app内部activity的切换,当前的场景是设置主菜单跳转子菜单,根据当前场景获取到具体的transit,transit=TRANSIT_OLD_ACTIVITY_OPEN,然后再结合enter为true或者false,可以最终可以找到设置主菜单的动画xml资源是activity_open_exit.xml,设置子菜单的动画xml资源是activity_open_enter.xml,在获取到具体xml资源名字后,通过AnimationUtils.loadAnimation方法把xml资源转成Animation对象。
之后就会创建一个WindowAnimationSpec对象,并把Animation对象作为构造方法的第一个参数传给了WindowAnimationSpec

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */final Animation a = loadAnimation(lp, transit, enter, isVoiceInteratction);AnimationAdapter adapter = new LocalAnimationAdapter(//创建了一个WindowAnimatonSpec对象作为LocalAnimationAdapter的初始化参数new WindowAnimationSpec(a, mTmpPoint, mTmpRect,getDisplayContent().mAppTransition.canSkipFirstFrame(),appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),getSurfaceAnimationRunner());

这里创建LocalAnimationAdapter对象的时候同时创建了一个WindowAnimatonSpec对象作为LocalAnimationAdapter的初始化参数,这个类WindowAnimatonSpec比较重要,是在后续窗口动画播放的时候具体的实现类,后面再分析
在获取到具体的Adaper对象之后,就开始执行startAnimation方法,这个方法里面主要调用了mSurfaceAnimator对象,来实现startAnimation

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,mSurfaceFreezer);

tip: WindowContainer这个类是wms的最重要类之一,它是所有window的基类,充分学习理解该类可以对wms的所有window的树状图有一定的理解

3、SurfaceAnimator类,字面上的意思就是window动画实现是交给它来实现surfacecontrol的动画(旧窗口动画是通过WindowSurfaceController控制surfacecontrol),该类的就是窗口动画的中控,它的主要作用是在startAnimation的时候,对要进行动画的surfacecontrol创建一个parent的surfacecontrol类型的mLeash对象,leash的翻译是用皮带系住的意思,相当于把要进行动画的surfacecontrol用皮带系住,通过操控mLeash对象来实现窗口的大小、位置、透明度等动画属性的改变。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,@AnimationType int type,@Nullable OnAnimationFinishedCallback animationFinishedCallback,@Nullable SurfaceFreezer freezer) {cancelAnimation(t, true /* restarting */, true /* forwardCancel */);mAnimation = anim;mAnimationType = type;mAnimationFinishedCallback = animationFinishedCallback;final SurfaceControl surface = mAnimatable.getSurfaceControl();mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;if (mLeash == null) {//重点关注这个mLeash对象,该对象是窗口动画专属surfacecontrol包装对象mLeash = createAnimationLeash(mAnimatable, surface, t, type,mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,0 /* y */, hidden, mService.mTransactionFactory);mAnimatable.onAnimationLeashCreated(t, mLeash);}mAnimatable.onLeashAnimationStarting(t, mLeash);mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);}

然后在动画结束之后,mLeash对象会走销毁的流程,同时动画的surfacecontrol进行reparent还原操作。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,boolean destroy) {boolean scheduleAnim = false;final SurfaceControl surface = animatable.getSurfaceControl();final SurfaceControl parent = animatable.getParentSurfaceControl();final boolean reparent = surface != null;if (reparent) {if (surface.isValid() && parent != null && parent.isValid()) {t.reparent(surface, parent);scheduleAnim = true;}}

窗口动画中控最终调用了动画的适配类LocalAnimationAdapter的startAnimation方法。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
4、LocalAnimationAdapter是窗口动画的适配类,继承自AnimationAdaper,LocalAnimationAdapter实现的是wms本地窗口动画,所以LocalAnimationAdapter可以理解成本地窗口动画的中转类。在startAnimation方法里面,调用了SurfaceAnimationRunner来最终实现动画的播放。
/*frameworks/base/services/core/java/com/android/wm/LocalAnimationAdapter.java */@Override    public void startAnimation(SurfaceControl animationLeash, Transaction t,@AnimationType int type, OnAnimationFinishedCallback finishCallback) {mAnimator.startAnimation(mSpec, animationLeash, t,() -> finishCallback.onAnimationFinished(type, this));}

5、SurfaceAnimationRunner是本地窗口动画真正的实现类,主要需要关注的方法是startAnimationLocked,首先这个方法已经通过mChoreographer切换到SurfaceAnimationThread线程来执行,然后创建了ValueAnimator属性动画对象,交由ValueAnimator属性动画对象的addUpdateListener方法来实现逐帧控制动画mLeash对象(surfacecontrol类型)的变化,具体的update方法的实现是在WindowAnimatonSpec类的apply方法里面。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */private void startAnimationLocked(RunningAnimation a) {final ValueAnimator anim = mAnimatorFactory.makeAnimator();anim.overrideDurationScale(1.0f);anim.setDuration(a.mAnimSpec.getDuration());anim.addUpdateListener(animation -> {synchronized (mCancelLock) {if (!a.mCancelled) {final long duration = anim.getDuration();long currentPlayTime = anim.getCurrentPlayTime();if (currentPlayTime > duration) {currentPlayTime = duration;}applyTransformation(a, mFrameTransaction, currentPlayTime);}}scheduleApplyTransaction();});………………a.mAnim = anim;mRunningAnimations.put(a.mLeash, a);//窗口动画最终调用了属性动画播放anim.start();anim.doAnimationFrame(mChoreographer.getFrameTime());}

再来看下apply方法的具体实现,通过之前以具体xml资源创建的mAnimation对象,根据当前时间片currentPlayTime获取到当前的tmp.transformation,对leash对象实现了Matrix(大小,位置),Alpha,Crop等transformation变化,再通过Transaction 交给surfaceflinger显示,从而实现了动画当前时间片的显示效果。对比旧动画机制,这个transformation变化是在WindowStateAnimator类里面实现的。为什么要重点关注这个方法呢?因为如果窗口动画出bug了(位置大小不对?透明度异常?),就可以在这个方法里面打印window的相关参数来初步定位原因。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimatonSpec.java */@Overridepublic void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {final TmpValues tmp = mThreadLocalTmps.get();tmp.transformation.clear();mAnimation.getTransformation(currentPlayTime, tmp.transformation);tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);t.setAlpha(leash, tmp.transformation.getAlpha());boolean cropSet = false;if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {if (tmp.transformation.hasClipRect()) {t.setWindowCrop(leash, tmp.transformation.getClipRect());cropSet = true;}} else {mTmpRect.set(mRootTaskBounds);if (tmp.transformation.hasClipRect()) {mTmpRect.intersect(tmp.transformation.getClipRect());}t.setWindowCrop(leash, mTmpRect);cropSet = true;}}

6、ValueAnimator类,从上面的介绍可以得知,窗口动画的最终本质就是一个ValueAnimator属性动画,理解了这一点,就相当于把窗口动画简单化了,最终的实现就类比于我们普通app的属性动画的实现(app属性动画的对象是view,窗口属性动画的对象是window),只不过整个流程比较复杂而已,但是最终的实现原理是一样的,殊途同归,这个才是android窗口动画机制的精髓所在。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */ValueAnimator anim = mAnimatorFactory.makeAnimator();anim.addUpdateListener(animation -> {applyTransformation(a, mFrameTransaction, currentPlayTime);});anim.start();

总结

本文只是讲解了WindowManagerService窗口动画之本地窗口动画的原生实现流程,主要是WindowManagerService的子类比较多,所以我们从动画的源头handleAppTransitionReady一步一步分析了它的整个流程,从整个流程来看,本地窗口动画的逻辑比较清晰,线路也比较单一,比较容易学习和理解,最终我们看到,窗口动画的原理就是一个属性动画,在动画update方法里面操控了窗口surface的属性变化,从而实现了窗口动画的逐帧播放。另外还有一个重点需要关注到,那就是wms的窗口动画不需要占用wms主锁,而且是单独线程,这样的设计也能在一定程度上优化系统卡顿的问题。

作者:努比亚技术团队
链接:https://www.jianshu.com/p/d41fe965e9e6

关注我获取更多知识或者投稿

WindowManagerService本地窗口动画相关推荐

  1. Android Framework 窗口子系统 (08)窗口动画之动画系统框架

    该系列文章总纲链接:专题分纲目录 Android Framework 窗口子系统 本章关键点总结 & 说明: 导图是不断迭代的,这里主要关注➕ 左上角 Android 窗口动画系统部分(因为导 ...

  2. framework 窗口动画缩放修改(Android 10)

    framework 窗口动画缩放修改(Android 10) 窗口缩放方法 // frameworks/base/services/core/java/com/android/server/wm/Wi ...

  3. Android窗口管理服务WindowManagerService计算窗口Z轴位置的过程分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/8570428 通过前面几篇文章的学习,我们知道了 ...

  4. 安卓窗口动画修改制作心得

    控制安卓窗口动画的代码是在framework-res.apk文件中的anim文件夹下,说一下整体的思路,打开anim文件编辑里面的xml文件,然后把他们再放回手机里,首先我们要把framework-r ...

  5. 怎样取消Windows 10的虚拟桌面切换动画和窗口动画

    怎样取消Windows 10的虚拟桌面切换动画和窗口动画 对于晕3D的人来说这是真的要命. 步骤: 在"这台电脑"上点击右键(如bai何在Win10桌面上显示"du这台电 ...

  6. android wms 窗口,Android6.0 WMS(十一) WMS窗口动画生成及播放

    上一篇我们我们分析到有VSync信号过来,最后会调用WindowAnimator的animateLocked函数来生成和播放动画,这篇我们我们主要从这个函数开始分析. animateLocked函数 ...

  7. win11窗口动画效果怎么设置?

    在win11系统中拖拽.最大最小化窗口操作都会有对应的动画,看起来有视觉效果,但是还有不少用户不知道怎么打开这个动画效果,下面就看小编来告诉大家具体的办法. win11窗口动画效果怎么设置? 1.首先 ...

  8. android窗口动画和过渡动画(activity和dialog)

    from:http://blog.sina.com.cn/s/blog_ba23fa6f0102v32g.html 窗口动画和过渡动画是指在窗口(activity或dialog)切换时的显示动画,窗口 ...

  9. 第十七篇:修改默认窗口动画缩放、过渡动画缩放的值

    (1)在开发者选项中可以去选择"窗口动画缩放"."过渡动画缩放"比例,有客户需求关闭这个动画过程. (2)可以直接在SettingsProvider下去修改默认 ...

最新文章

  1. 【Smooth】非线性优化
  2. 解决 VSCode 配置 tab 空格数 Dart 语言无效的问题
  3. 人工智能等新技术将加速物联网的普及
  4. 相干光通信系统的调制与解调
  5. 使用TensorFlow.js的AI聊天机器人六:生成莎士比亚独白
  6. 微信小程序的特点是什么?
  7. java9最新下载_java9 64位 官方下载_java9 64位 官方最新版_魅蓝下载
  8. 基于Vue和Spring Boot的在线视频播放系统 (模仿咪咕视频)
  9. 注册测绘师成绩查询,2020注册测绘师成绩公布
  10. 利用MATLAB绘制阶梯图(stairs函数)并获取所绘制的阶梯图的横纵坐标值
  11. linux抓取vlan数据包,Wireshark/Ethereal抓取数据包不显示vlan tag的解决方法
  12. element 源码学习五 —— Notice 系列组件学习
  13. dos下的for命令详解(zz)
  14. 十一新疆之旅中邂逅的一首诗《黄河,母亲之河》
  15. 支付宝转账又出新方法:悬浮条自动识别输入,避免失误尴尬
  16. 第一啪,第一啪电影网,第一啪电影网用的哪里的模板diyipa.cc
  17. PRI变换法原理解析及其matlab分析
  18. Python学习 Day31 DOM
  19. 如何实现类似锚链接的导航联动效果
  20. SAP MB51选择界面配置

热门文章

  1. 分析: 为什么红颜多薄命, 好人命不长, 坏人活千年
  2. 【Unity特效】人物脚底下光环阴影影子特效的实现
  3. 基于 Flink 流计算实现的股票交易实时资产应用
  4. Oracle Enterprise Linux(OEL)镜像下载地址大全
  5. exFAT 文件系统格式
  6. 新手必学 最完整的iTunes使用教程
  7. 【拥抱开源】十年之做C#屏幕截图工具全面开源
  8. C#软件开发实例.私人订制自己的屏幕截图工具(十)在截图中包含鼠标指针形状
  9. 移动卡无限GPRS上网方法
  10. 【博客497】k8s cgroup原理完整剖析