Android T 窗口动画(本地动画)显示流程其二——添加流程

693 阅读39分钟

动画添加流程

以添加窗口的动画为例,窗口添加的流程见Android T WMS窗口添加流程其三——服务端代码详解(窗口状态刷新) 这里我们从WindowStateAnimator.commitFinishDrawingLocked方法开始跟踪代码流程

修改窗口状态和判断窗口类型

WindowStateAnimator.commitFinishDrawingLocked() 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java

	// This must be called while inside a transaction.
	boolean commitFinishDrawingLocked() {
	    //如果开启了DEBUG_STARTING_WINDOW_VERBOSE调试模式,并且窗口的类型是TYPE_APPLICATION_STARTING,则打印日志
	    if (DEBUG_STARTING_WINDOW_VERBOSE && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
	        Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState=" + drawStateToString());
	    }
	    
	    //如果绘制状态不是COMMIT_DRAW_PENDING和READY_TO_SHOW,则返回false
	    if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
	        return false;
	    }
	    
	    ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s", mSurfaceController);
	    
	    //设置绘制状态为READY_TO_SHOW
	    mDrawState = READY_TO_SHOW;
	    
	    boolean result = false;
	    final ActivityRecord activity = mWin.mActivityRecord;
	    
	    //根据条件进入performShowLocked()流程
	    if (activity == null || activity.canShowWindows() || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
	        result = mWin.performShowLocked();
	    }
	    
	    return result;
	}

这个方法主要就是做了两件事:

  1. mDrawState = READY_TO_SHOW将窗口的状态修改为READY_TO_SHOW
  2. 根据条件进入到WindowState.performShowLocked()流程
  • activity == null 如果ActivityRecord为空,这种情况可以理解为不依赖Activity的窗口,比如常见的悬浮窗
  • activity.canShowWindows() 这个方法大概是说:只有当所有窗口都已绘制完成,并且没有正在进行父级窗口的应用过渡动画,并且没有非默认颜色的窗口存在时,返回true
  • mWin.mAttrs.type == TYPE_APPLICATION_STARTING 窗口类型为启动窗口,启动窗口就是StartingWindow,应用启动时出现的窗口,常见的就是Splash screen ,许多应用都会定义自己的SplashActivity 即使非这三种情况,最终也会调用到performShowLocked()

上述Demo仅仅只是通过addView添加窗口,因此ActivityRecord为空,属于activity == null这种情况,我们这里直接走performShowLocked()

进入窗口动画流程

WindowState.performShowLocked() 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java

    // This must be called while inside a transaction.
    boolean performShowLocked() {
    	......
		//获取WindowStateAnimator.mDrawState
        final int drawState = mWinAnimator.mDrawState;
        //这里判断(drawState 状态为HAS_DRAWN 或者READY_TO_SHOW)且ActivityRecord不为空
        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
        	//窗口类型不为启动窗口
            if (mAttrs.type != TYPE_APPLICATION_STARTING) {
                mActivityRecord.onFirstWindowDrawn(this);
            } else {
                mActivityRecord.onStartingWindowDrawn();
            }
        }
		//如果当前mDrawState的状态不为READY_TO_SHOW ,则直接返回
        if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
            return false;
        }
        ......
        //走入窗口动画流程
        mWinAnimator.applyEnterAnimationLocked();
        
        // Force the show in the next prepareSurfaceLocked() call.
        mWinAnimator.mLastAlpha = -1;
        ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
        //设置mDrawState的状态为HAS_DRAWN
        mWinAnimator.mDrawState = HAS_DRAWN;
        mWmService.scheduleAnimationLocked();
        ......
        return true;
    }

mWinAnimator.applyEnterAnimationLocked();进入动画流程,之后mDrawState更新为HAS_DRAWN。

确定transit的值调用后续动画流程

WindowStateAnimator.applyEnterAnimationLocked()

   void applyEnterAnimationLocked() {
        // If we are the new part of a window replacement transition and we have requested
        // not to animate, we instead want to make it seamless, so we don't want to apply
        // an enter transition.
        if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
            return;
        }

		//1.根据mEnterAnimationPending变量确定transit的值
        final int transit;
        if (mEnterAnimationPending) {
            mEnterAnimationPending = false;
            transit = WindowManagerPolicy.TRANSIT_ENTER;
        } else {
            transit = WindowManagerPolicy.TRANSIT_SHOW;
        }

        // We don't apply animation for application main window here since this window type
        // should be controlled by ActivityRecord in general. Wallpaper is also excluded because
        // WallpaperController should handle it.
        //2.根据条件调用applyAnimationLocked()
        if (mAttrType != TYPE_BASE_APPLICATION && !mIsWallpaper) {
            applyAnimationLocked(transit, true);
        }

        if (mService.mAccessibilityController.hasCallbacks()) {
            mService.mAccessibilityController.onWindowTransition(mWin, transit);
        }
    }
  1. 根据mEnterAnimationPending变量确定transit的值。 如果这个变量为真,那么会执行一个进入动画,否则会执行一个显示动画。同时,这段代码还确保了transit变量始终被赋予一个有效值,即WindowManagerPolicy.TRANSIT_ENTERWindowManagerPolicy.TRANSIT_SHOW。 在我们addWindow流程中mEnterAnimationPending会被赋值为true 在这里插入图片描述 因此,我们的transit = WindowManagerPolicy.TRANSIT_ENTER,即transit = 1 transit一般有下面这几种类型:
        /** Window has been added to the screen. */
        public static final int TRANSIT_ENTER = 1;
        /** Window has been removed from the screen. */
        public static final int TRANSIT_EXIT = 2;
        /** Window has been made visible. */
        public static final int TRANSIT_SHOW = 3;
        /** Window has been made invisible.
         * TODO: Consider removal as this is unused. */
        public static final int TRANSIT_HIDE = 4;
        /** The "application starting" preview window is no longer needed, and will
         * animate away to show the real window. */
        public static final int TRANSIT_PREVIEW_DONE = 5;
    
  2. 根据条件调用applyAnimationLocked()
  • mAttrType != TYPE_BASE_APPLICATION 窗口类型不是Activity
  • !mIsWallpape 当前窗口不是壁纸 大概就是说,Activity类型的窗口一般由ActivityRecord控制,壁纸类型由WallpaperController控制。我们这里窗口类型既不是Activity也不是壁纸,所以调用applyAnimationLocked(transit, true);

综上所述,调用的是applyAnimationLocked(1, true);

加载并启动动画

WindowStateAnimator.applyAnimationLocked()

    /**
     * Choose the correct animation and set it to the passed WindowState.
     * @param transit If AppTransition.TRANSIT_PREVIEW_DONE and the app window has been drawn
     *      then the animation will be app_starting_exit. Any other value loads the animation from
     *      the switch statement below.
     * @param isEntrance The animation type the last time this was called. Used to keep from
     *      loading the same animation twice.
     * @return true if an animation has been loaded.
     */
    boolean applyAnimationLocked(int transit, boolean isEntrance) {
    	//mWin.isAnimating(),检查窗口是否已经在执行动画,
    	//mAnimationIsEntrance == isEntrance,并且这个动画的类型是否与现在要启动的动画相同。
    	//如果条件满足,那么就只是让当前的动画继续运行,而不启动新的动画。
        if (mWin.isAnimating() && mAnimationIsEntrance == isEntrance) {
            // If we are trying to apply an animation, but already running
            // an animation of the same type, then just leave that one alone.
            return true;
        }
        
		//判断当前的窗口是否是输入法窗口。
        final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
        //如果是上次调用时的动画类型,并且当前窗口是输入法窗口
        if (isEntrance && isImeWindow) {
        	//调整输入法窗口的一些设置
            mWin.getDisplayContent().adjustForImeIfNeeded();
            //让WMS知道窗口布局已经改变,需要重新计算布局
            mWin.setDisplayLayoutNeeded();
            //请求WMS进行一次遍历,以重新计算和更新窗口布局
            mService.mWindowPlacerLocked.requestTraversal();
        }

        // Only apply an animation if the display isn't frozen.  If it is
        // frozen, there is no reason to animate and it can cause strange
        // artifacts when we unfreeze the display if some different animation
        // is running.
        //mWin.mToken.okToAnimate()会判断是否有冻结,屏幕是否开启
        if (mWin.mToken.okToAnimate()) {
        	//selectAnimation(mWin, transit)根据mWin(当前窗口)和transit的值来选择显示的动画,之后返回给anim变量
            int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
            int attr = -1;
            Animation a = null;
            //根据anim的值选择动画加载方式
            if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
            	//anim的值不为ANIMATION_NONE(-1)
                if (anim != DisplayPolicy.ANIMATION_NONE) {
                    Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#loadAnimation");
                    //loadAnimation方法加载与anim对应的动画,并将加载的动画赋值给变量a
                    a = AnimationUtils.loadAnimation(mContext, anim);
                    Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                }
            } else {
            	//根据transit的值来给attr赋值,我们这里transit是WindowManagerPolicy.TRANSIT_ENTER
                switch (transit) {
                    case WindowManagerPolicy.TRANSIT_ENTER:
                    	//attr=0x0
                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_EXIT:
                    	//attr=0x1
                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_SHOW:
                    	//attr=0x2
                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_HIDE:
                    	//attr=0x3
                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                        break;
                }
                if (attr >= 0) {
                //loadAnimationAttr(mWin.mAttrs, attr, TRANSIT_OLD_NONE)用来加载动画属性。
                    a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
                            mWin.mAttrs, attr, TRANSIT_OLD_NONE);
                }
            }
            //log打印,打开开关adb shell wm logging enable-text WM_DEBUG_ANIM
            if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
                ProtoLog.v(WM_DEBUG_ANIM, "applyAnimation: win=%s"
                        + " anim=%d attr=0x%x a=%s transit=%d type=%d isEntrance=%b Callers %s",
                        this, anim, attr, a, transit, mAttrType, isEntrance, Debug.getCallers(20));
            }
            if (a != null) {
                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
                //启动动画
                mWin.startAnimation(a);
                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                mAnimationIsEntrance = isEntrance;
            }
        } else if (!isImeWindow) {
            mWin.cancelAnimation();
        }

        if (!isEntrance && isImeWindow) {
            mWin.getDisplayContent().adjustForImeIfNeeded();
        }

		//检查指定的窗口容器是否正在进行动画
        return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
    }

其中根据条件if (anim != DisplayPolicy.ANIMATION_STYLEABLE)为true还是false,会判断后续动画是通过AnimationUtils.loadAnimation加载,还是通过AppTransition.loadAnimationAttr加载。 无论哪种方式加载动画,最终都会通过mWin.startAnimation(a);启动动画。 下面我们从上往下来讲讲这段代码中有一些关键部分。

根据窗口类型获取anim的返回值

int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit); 根据mWin(当前窗口)和transit的值来选择显示的动画,之后返回给anim变量 代码路径:frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java

int selectAnimation(WindowState win, int transit) {
        ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
        if (win == mStatusBar) {
            ......
        } else if (win == mNavigationBar) {
            ......
        } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
                || win == mExtraNavBarAlt) {
            ......
        }

        if (transit == TRANSIT_PREVIEW_DONE) {
            if (win.hasAppShownWindows()) {
                if (win.isActivityTypeHome()) {
                    // Dismiss the starting window as soon as possible to avoid the crossfade out
                    // with old content because home is easier to have different UI states.
                    return ANIMATION_NONE;
                }
                ProtoLog.i(WM_DEBUG_ANIM, "**** STARTING EXIT");
                return R.anim.app_starting_exit;
            }
        }

        return ANIMATION_STYLEABLE;
    }

win == mStatusBarwin == mNavigationBar判断我们的窗口是否是状态栏、导航栏 win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt || win == mExtraNavBarAlt其他的一些特定窗口,这些特定的窗口对象可能是代表不同的屏幕区域或组件,例如备用的状态栏、备用的导航栏、气候栏和额外的导航栏。

transit == TRANSIT_PREVIEW_DONE应用启动预览窗口是否已经完成,即transit的值为5。 win.hasAppShownWindows()当前窗口的应用是否已经显示过任何窗口。 win.isActivityTypeHome()当前窗口是主页。 如果窗口预览动画已经完成的状态为true,当前窗口的应用已经显示过至少一个窗口,并且当前窗口是主页,三个条件都满足时,那么不使用任何动画,直接返回ANIMATION_NONE

也就是说selectAnimation方法实际上主要就是判断两件事: 1.当前窗口是否属于特定窗口(状态栏、导航栏等) 2.transit的值是否为TRANSIT_PREVIEW_DONE,且当前窗口的应用已经显示过至少一个窗口(这个条件下满足当前窗口是主页时,返回ANIMATION_NONE) 不是上述情况就返回ANIMATION_STYLEABLE

结合WindowStateAnimator.applyAnimationLocked代码中的逻辑来看,根据selectAnimation方法的返回值来确定调用哪个方法加载动画: selectAnimation返回值为ANIMATION_NONE,则不加载动画。 selectAnimation返回值为ANIMATION_STYLEABLE,则通过AppTransition.loadAnimationAttr加载动画。 其他返回值,则通过AnimationUtils.loadAnimation加载动画。

我们这里既不为特定窗口,且transit的值不为5,因此直接返回ANIMATION_STYLEABLE 所以我们当前窗口动画走的是AppTransition.loadAnimationAttr流程加载动画

特定窗口的动画加载方式

AnimationUtils.loadAnimation主要用于加载XML文件中定义的常规动画,这些动画可以用于任何View对象。

a = AnimationUtils.loadAnimation(mContext, anim); 从Android资源中加载一个动画,a为null则表示没有动画

代码路径:frameworks/base/core/java/android/view/animation/AnimationUtils.java

    /**
     * Loads an {@link Animation} object from a resource
     *
     * @param context Application context used to access resources
     * @param id The resource id of the animation to load
     * @return The animation object referenced by the specified id
     * @throws NotFoundException when the animation cannot be loaded
     */
    public static Animation loadAnimation(Context context, @AnimRes int id)
            throws NotFoundException {

        XmlResourceParser parser = null;
        try {
        	//从给定的资源ID中获取一个动画资源。
            parser = context.getResources().getAnimation(id);
            //从解析器中创建一个Animation对象
            return createAnimationFromXml(context, parser);
        } catch (XmlPullParserException ex) {
            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
                    Integer.toHexString(id));
            rnf.initCause(ex);
            throw rnf;
        } catch (IOException ex) {
            NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" +
                    Integer.toHexString(id));
            rnf.initCause(ex);
            throw rnf;
        } finally {
        	//解析器(parser)不为null,则关闭它。确保资源适当地释放
            if (parser != null) parser.close();
        }
    }

这个方法的关键就是通过context.getResources().getAnimation(id),从给定的资源ID中获取一个动画资源。该方法返回一个XmlResourceParser对象,用于解析XML格式的动画资源。 再调用createAnimationFromXml(context, parser)从解析器中创建一个Animation对象

    private static Animation createAnimationFromXml(Context c, XmlPullParser parser)
            throws XmlPullParserException, IOException {

        return createAnimationFromXml(c, parser, null, Xml.asAttributeSet(parser));
    }
    
    @UnsupportedAppUsage
    private static Animation createAnimationFromXml(Context c, XmlPullParser parser,
            AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException {

        Animation anim = null;

        // Make sure we are on a start tag.
        int type;
        int depth = parser.getDepth();
        
		//使用一个循环来读取XML的各个节点。循环将继续,直到遇到一个结束标签或文档结束。
		//如果当前节点不是起始标签,则继续下一次循环。
		//如果是起始标签,检查标签的名称以确定要创建的动画类型。
        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
               && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            String  name = parser.getName();

			//如果标签是"set",则创建一个新的AnimationSet对象,
			//并递归地调用createAnimationFromXml函数来解析其子动画。
			//如果标签是"alpha"、"scale"、"rotate"、"translate"、"cliprect"或"extend",
			//则根据标签的名称创建相应的动画对象。
            if (name.equals("set")) {
                anim = new AnimationSet(c, attrs);
                createAnimationFromXml(c, parser, (AnimationSet)anim, attrs);
            } else if (name.equals("alpha")) {
                anim = new AlphaAnimation(c, attrs);
            } else if (name.equals("scale")) {
                anim = new ScaleAnimation(c, attrs);
            }  else if (name.equals("rotate")) {
                anim = new RotateAnimation(c, attrs);
            }  else if (name.equals("translate")) {
                anim = new TranslateAnimation(c, attrs);
            } else if (name.equals("cliprect")) {
                anim = new ClipRectAnimation(c, attrs);
            } else if (name.equals("extend")) {
                anim = new ExtendAnimation(c, attrs);
            } else {
                throw new RuntimeException("Unknown animation name: " + parser.getName());
            }
			
            if (parent != null) {
                parent.addAnimation(anim);
            }
        }
		//返回创建的动画
        return anim;

    }

这个方法就是根据应用侧定义XML,进行解析并创建动画。

最常见的应用侧调用补间动画的方式就是: 通过AnimationUtils.loadAnimation() 加载动画,然后我们的View控件调用startAnimation开启动画。 比如,先在res/anim目录下创建了一个exit.xml

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha android:fromAlpha="1.0"
        android:toAlpha="0"
        android:duration="1000"/>
</set>

可以通过loadAnimation加载XML

ImageView img_show = (ImageView) findViewById(R.id.img_show);
//加载前面定义的exit.xml
Animation animation = AnimationUtils.loadAnimation(this, R.anim.exit);
img_show.startAnimation(animation);

注:img_show为某个ImageView控件ID

动画资源ID attr

attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation 对应的是代码路径:frameworks/base/core/res/res/values/attrs.xml 在这里插入图片描述 这也对应了我们应用侧代码中定义的style

<style name="MyWindow">
    <item name="android:windowEnterAnimation">@anim/enter</item>
    <item name="android:windowExitAnimation">@anim/exit</item>
</style>

attr是资源的一个ID,一般来说,添加窗口时的动画通过android:windowEnterAnimation属性设置时,attr的值为0x0;移除窗口时的动画通过android:windowExitAnimation属性设置时,attr的值为0x1attr的值只与transit有关,后面调用loadAnimationAttr方法时会用到。

非特定窗口的动画加载方式

AppTransition.loadAnimationAttr主要用于加载应用级别的转场动画。

a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(mWin.mAttrs, attr, TRANSIT_OLD_NONE); mAppTransition是AppTransition对象,这个类是用于管理应用程序过渡动画的类,a为null则表示没有动画mWin.getDisplayContent().mAppTransition 获取一个窗口的显示内容中的应用程序过渡动画。 loadAnimationAttr(mWin.mAttrs, attr, TRANSIT_OLD_NONE); 它传递了三个参数:mWin.mAttrs(对应我们应用侧定义的窗口属性),attr(我们之前看到它必须大于或等于0),以及TRANSIT_OLD_NONE(一个标志或状态,表示没有旧的过渡动画) 代码路径:frameworks/base/services/core/java/com/android/server/wm/AppTransition.java

    //LayoutParams lp 通常用于定义视图的位置和大小等属性,对应我们应用侧定义的LayoutParams对象;
    //int animAttr 是动画属性,表示动画的类型或ID;
    //int transit 表示过渡效果。
   Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
        return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit);
    }

mTransitionAnimation是TransitionAnimation对象,通过AppTransition调用到TransitionAnimation的loadAnimationAttr方法 代码路径:frameworks/base/core/java/com/android/internal/policy/TransitionAnimation.java

    /** Load animation by attribute Id from specific LayoutParams */
    @Nullable
    public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
    	//定义了一个整数变量 resId 并初始化为 Resources.ID_NULL,用来存储动画资源的ID
        int resId = Resources.ID_NULL;
        Context context = mContext;
        //如果 animAttr 大于等于0,则调用 getCachedAnimations(lp) 方法获取动画缓存,并将返回的动画资源的ID存储到 resId 中
        if (animAttr >= 0) {
            AttributeCache.Entry ent = getCachedAnimations(lp);
            if (ent != null) {
                context = ent.context;
                resId = ent.array.getResourceId(animAttr, 0);
            }
        }
        //调用 updateToTranslucentAnimIfNeeded(resId, transit) 方法,更新具有透明效果的动画资源 resId
        resId = updateToTranslucentAnimIfNeeded(resId, transit);
        //检查 resId 是否有效。如果有效,则调用 loadAnimationSafely(context, resId, mTag) 方法加载动画,并返回加载的动画对象;
        //否则返回 null
        if (ResourceId.isValid(resId)) {
            return loadAnimationSafely(context, resId, mTag);
        }
        return null;
    }

loadAnimationAttr 的方法,它主要功能是加载和动画相关的资源,这里我们主要看看getCachedAnimations(lp)

代码路径:frameworks/base/core/java/com/android/internal/policy/TransitionAnimation.java

    private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
        if (mDebug) {
            Slog.v(mTag, "Loading animations: layout params pkg="
                    + (lp != null ? lp.packageName : null)
                    + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
        }
        //判断应用侧传递的参数 lp 是否为 null,以及 lp.windowAnimations 是否不为0,否则直接返回 null
        if (lp != null && lp.windowAnimations != 0) {
            // If this is a system resource, don't try to load it from the
            // application resources.  It is nice to avoid loading application
            // resources if we can.
            //根据 lp.packageName 获取包名,如果 lp.packageName 为 null,则使用默认包名 DEFAULT_PACKAGE。
            String packageName = lp.packageName != null ? lp.packageName : DEFAULT_PACKAGE;
            //调用 getAnimationStyleResId(lp) 方法获取动画资源的ID,并将其赋值给变量 resId
            int resId = getAnimationStyleResId(lp);
            //检查 resId 的高8位是否为0x01。如果是,则将包名重置为默认包名 DEFAULT_PACKAGE。
            if ((resId & 0xFF000000) == 0x01000000) {
                packageName = DEFAULT_PACKAGE;
            }
            if (mDebug) {
                Slog.v(mTag, "Loading animations: picked package=" + packageName);
            }
            //调用 AttributeCache.instance().get() 方法获取动画资源,并返回结果。如果获取失败,则返回 null。
            return AttributeCache.instance().get(packageName, resId,
                    com.android.internal.R.styleable.WindowAnimation);
        }
      
  • 先来看看获取动画资源ID的方法 getAnimationStyleResId(lp)

        /** Returns window animation style ID from {@link LayoutParams} or from system in some cases */
        public int getAnimationStyleResId(@NonNull LayoutParams lp) {
        	//把应用侧定义动画赋值给resID
            int resId = lp.windowAnimations;
            if (lp.type == LayoutParams.TYPE_APPLICATION_STARTING) {
                // Note that we don't want application to customize starting window animation.
                // Since this window is specific for displaying while app starting,
                // application should not change its animation directly.
                // In this case, it will use system resource to get default animation.
                resId = mDefaultWindowAnimationStyleResId;
            }
            return resId;
        }
    

    这个方法实际上就是把应用侧设置的动画给resId。 我们demo中的代码,应用侧赋值动画给LayoutParams的windowAnimationsmLayoutParams.windowAnimations = R.style.MyWindow;,系统侧赋值给resIdint resId = lp.windowAnimations;

  • 再来看看return AttributeCache.instance().get(packageName, resId, com.android.internal.R.styleable.WindowAnimation); 代码路径:frameworks/base/core/java/com/android/internal/policy/AttributeCache.java

      public Entry get(String packageName, int resId, int[] styleable) {
          return get(packageName, resId, styleable, UserHandle.USER_CURRENT);
      }
      
      public Entry get(String packageName, int resId, int[] styleable, int userId) {
            synchronized (this) {
                Package pkg = mPackages.get(packageName);
                ArrayMap<int[], Entry> map = null;
                Entry ent = null;
                //包名不为空
                if (pkg != null) {
                	//从map中获取资源
                    map = pkg.mMap.get(resId);
                    //map不为空,找到对应的Entry对象,且不为空就返回这个对象
                    if (map != null) {
                        ent = map.get(styleable);
                        if (ent != null) {
                            return ent;
                        }
                    }
                //包名为空
                } else {
                    Context context;
                    try {
                    	//创建一个context
                        context = mContext.createPackageContextAsUser(packageName, 0,
                                new UserHandle(userId));
                        if (context == null) {
                            return null;
                        }
                    } catch (PackageManager.NameNotFoundException e) {
                        return null;
                    }
                    //创建Package
                    pkg = new Package(context);
                    mPackages.put(packageName, pkg);
                }
    
    			//若map为空,创建map
                if (map == null) {
                    map = new ArrayMap<>();
                    pkg.mMap.put(resId, map);
                }
    
    			//创建Entry对象,并使用obtainStyledAttributes方法从Package的上下文中获取给定资源ID和风格属性数组的属性。
    			//然后,它将新的Entry对象添加到缓存中。
                try {
                    ent = new Entry(pkg.context,
                            pkg.context.obtainStyledAttributes(resId, styleable));
                    map.put(styleable, ent);
                } catch (Resources.NotFoundException e) {
                    return null;
                }
    
                return ent;
            }
      }
    

    用于获取指定包名、资源ID、风格属性数组和用户ID的Entry对象的函数。如果找不到指定的包,为该包创建一个新的Package对象。如果找不到指定的资源,它将尝试创建一个新的Entry对象并存储在map中。 关键变量解读: mPackages.put(packageName, pkg);中的mPackages指的是private final LruCache<String, Package> mPackages = new LruCache<>(CACHE_SIZE); mPackages是一个缓存,就是用来存储新的Package对象。

    pkg.mMap.put(resId, map);中的mMap指的是private final SparseArray<ArrayMap<int[], Entry>> mMap = new SparseArray<>(); mMap是一个映射,将资源ID映射到另一个映射,这个映射将资源风格属性数组(styleable)映射到Entry对象。

调用启动动画

mWin.startAnimation(a);用于启动动画,其中参数a为前面调用loadAnimationAttr方法后的值,即应用侧传递的动画。具体流程看后续的WindowState.startAnimation(),初始化动画参数并通过AnimationAdapter保存动画

检查动画

return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);用户检查指定的窗口容器是否正在进行动画 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    final boolean isAnimating(int flags, int typesToCheck) {
        return getAnimatingContainer(flags, typesToCheck) != null;
    }

flags 用于确定要检查的动画类型和范围。 typesToCheck 用于确定哪些类型的动画需要检查。 方法内部调用 getAnimatingContainer 方法来获取正在进行动画的窗口容器,并根据返回值判断是否存在符合条件和目标标志的动画。 如果返回值为 true,则说明存在符合条件的动画;如果返回值为 false,则说明不存在符合条件的动画。

初始化动画参数并通过LocalAnimationAdapter保存动画

WindowState.startAnimation() 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowState.java

    void startAnimation(Animation anim) {

        // If we are an inset provider, all our animations are driven by the inset client.
        //Insets类型的动画由InsetsSourceProvider相关处理,这个类用于控制窗口动画的边界和剪裁。
        //这个接口定义了如何从窗口边界中获取动画的边界信息,以及如何从窗口剪裁中获取动画的剪裁信息。
        if (mControllableInsetProvider != null) {
            return;
        }

		//获取显示信息(DisplayInfo),可能包括应用的宽度和高度等信息。
        final DisplayInfo displayInfo = getDisplayInfo();
        //通过获取到的信息初始化动画
        anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
                displayInfo.appWidth, displayInfo.appHeight);
        //设置动画持续时间,MAX_ANIMATION_DURATION为10s
        anim.restrictDuration(MAX_ANIMATION_DURATION);
        //按照当前窗口动画的缩放比例来设置缩放动画的持续时间
        anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
        //创建一个新的本地动画适配器(LocalAnimationAdapter),把初始化的anim设置到其中,该类用于管理动画的特定细节。
        final AnimationAdapter adapter = new LocalAnimationAdapter(
                new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
                        0 /* windowCornerRadius */),
                //SurfaceAnimationRunner是本地窗口动画真正的实现类
                mWmService.mSurfaceAnimationRunner);
        //通过使用前面的adapter启动动画
        startAnimation(getPendingTransaction(), adapter);
        //最终从WindowContainer.scheduleAnimation()方法
        //调用到WindowAnimator.scheduleAnimation()方法安排或触发动画的执行
        commitPendingTransaction();
    }
    
    private void startAnimation(Transaction t, AnimationAdapter adapter) {
        startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION);
    }

startAnimation(Animation anim),前面把应用定义的动画传递到了anim,然后对其进行了参数设置,创建了一个AnimationAdapter对象,把动画包装到了WindowAnimationSpec中,最后通过startAnimation传递这个AnimationAdapter的对象。 WindowAnimationSpec的作用主要是定义窗口动画的各种属性和行为。这个类包含了一些字段,这些字段定义了动画的类型、持续时间、插值器等。 当WMS需要为一个窗口启动动画时,它会创建一个WindowAnimationSpec对象,并将这个对象传递给SurfaceAnimationRunner。然后,SurfaceAnimationRunner会根据这个WindowAnimationSpec对象的属性来创建和执行动画。 commitPendingTransaction();方法会最终调用到WindowAnimator.scheduleAnimation()方法,协调动画与窗口的显示。

传递启动动画需要的参数

WindowContainer.startAnimation() 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
                        @AnimationType int type) {
        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
    }
    
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
                        @AnimationType int type,
                        @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
        startAnimation(t, anim, hidden, type, animationFinishedCallback,
                null /* adapterAnimationCancelledCallback */, null /* snapshotAnim */);
    }
    
    /**
     * Starts an animation on the container.
     *
     * @param anim                       The animation to run.
     * @param hidden                     Whether our container is currently hidden. TODO This should use isVisible at
     *                                   some point but the meaning is too weird to work for all containers.
     * @param type                       The type of animation defined as {@link AnimationType}.
     * @param animationFinishedCallback  The callback being triggered when the animation finishes.
     * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
     *                                   cancel call to the underlying AnimationAdapter.
     * @param snapshotAnim               The animation to run for the snapshot. {@code null} if there is no
     *                                   snapshot.
     */
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
                        @AnimationType int type,
                        @Nullable OnAnimationFinishedCallback animationFinishedCallback,
                        @Nullable Runnable animationCancelledCallback,
                        @Nullable AnimationAdapter snapshotAnim) {
        ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",
                this, type, anim);

        // TODO: This should use isVisible() but because isVisible has a really weird meaning at
        // the moment this doesn't work for all animatable window containers.
        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
    }

从WindowState的startAnimation方法逐步调用到WindowContainer的startAnimation方法

  • 入参含义 t:这是一个对象,用于描述一系列的窗口操作,例如移动、调整大小、绘制等。这些操作在WMS中排队,并在适当的时机应用到窗口上。 anim:这是对动画进行封装的类。它包含了一些关于如何开始、更新和结束动画的信息。传递的就是前面WindowState.startAnimation()中创建的LocalAnimationAdapter对象。 hidden:这个布尔值表示窗口是否隐藏。如果窗口是隐藏的,那么就不会显示动画。 type:这个整数代表了动画的类型。这里我们传递的是ANIMATION_TYPE_WINDOW_ANIMATION,即图层上显示的动画类型window_animation。 animationFinishedCallbackanimationCancelledCallback:这两个是回调函数,分别在动画完成和动画取消时被调用。 snapshotAnim:这个参数是给定动画的快照。如果参数为null,那么就表示没有快照。 从WindowContainer的startAnimation方法调用中,我们可以看出这里animationFinishedCallbackanimationCancelledCallbacksnapshotAnim传递过来的均为null

  • 代码含义 关键的代码只有这一句mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback, animationCancelledCallback, snapshotAnim, mSurfaceFreezer); 这行代码调用了SurfaceAnimator的startAnimation方法来启动动画。SurfaceAnimator的作用主要是控制窗口动画,它是窗口动画的中控,通过操控mLeash对象来实现窗口的大小、位置、透明度等动画属性的改变。这个方法需要一系列参数,包括上面解释的所有参数,还有一个SurfaceFreezer对象mSurfaceFreezer,它可以在动画开始时冻结窗口的更新,以防止在动画过程中窗口的内容闪烁。 mSurfaceAnimator和mSurfaceFreezer是在WindowContainer的构造方法中初始化的

    class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
        implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
        InsetsControlTarget {
        ......
    	WindowContainer(WindowManagerService wms) {
    	     mWmService = wms;
    	     mTransitionController = mWmService.mAtmService.getTransitionController();
    	     mPendingTransaction = wms.mTransactionFactory.get();
    	     mSyncTransaction = wms.mTransactionFactory.get();
    	     mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
    	     mSurfaceFreezer = new SurfaceFreezer(this, wms);
    	 }
    	 ......
    }
    

创建leash并通过LocalAnimationAdapter启动动画

SurfaceAnimator.startAnimation() 代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java

    /**
     * Starts an animation.
     *
     * @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the
     *             component responsible for running the animation. It runs the animation with
     *             {@link AnimationAdapter#startAnimation} once the hierarchy with
     *             the Leash has been set up.
     * @param hidden Whether the container holding the child surfaces is currently visible or not.
     *               This is important as it will start with the leash hidden or visible before
     *               handing it to the component that is responsible to run the animation.
     * @param animationFinishedCallback The callback being triggered when the animation finishes.
     * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
     *                                   cancel call to the underlying AnimationAdapter.
     * @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no
     *                     snapshot.
     */
    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable Runnable animationCancelledCallback,
            @Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {
        //开始新的动画之前,取消之前的动画
        //参数含义:t(是一个事务对象),true(表示动画正在重新启动),和true(表示向前取消)
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        //初始化参数,把WindowContainer.startAnimation中传递的参数赋值给对应变量
        mAnimation = anim;
        mAnimationType = type;
        mSurfaceAnimationFinishedCallback = animationFinishedCallback;
        mAnimationCancelledCallback = animationCancelledCallback;
        //获取当前窗口的SurfaceControl
        final SurfaceControl surface = mAnimatable.getSurfaceControl();
        //没有surface,则取消当前的动画
        if (surface == null) {
            Slog.w(TAG, "Unable to start animation, surface is null or no children.");
            cancelAnimation();
            return;
        }
        //调用SurfaceFreezer中takeLeashForAnimation()获取mLeash,但是SurfaceFreezer中没有被初始化,所以这里的mLeash还是为null
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
        	//创建mLeash
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);
            //创建动画“leash”后执行的一些操作,包括重置图层、重新分配图层以及重置Surface的位置
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        //处理动画开始时进行一些设置和准备工作
        mAnimatable.onLeashAnimationStarting(t, mLeash);
        if (mAnimationStartDelayed) {
            ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);
            return;
        }
        //将leash传给AnimationAdapter,执行动画
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
        if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            mAnimation.dump(pw, "");
            ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw);
        }
        //获取一个快照,并使用该快照来执行动画,我们这里snapshotAnim为null,因此不涉及
        if (snapshotAnim != null) {
            mSnapshot = freezer.takeSnapshotForAnimation();
            if (mSnapshot == null) {
                Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable);
                return;
            }
            mSnapshot.startAnimation(t, snapshotAnim, type);
        }
    }

入参就是前面WindowContainer.startAnimation中传递的参数。

获取当前窗口的surface

final SurfaceControl surface = mAnimatable.getSurfaceControl(); mAnimatable是Animatable接口的对象,WindowContainer实现了Animatable接口。 在WindowContainer构造方法中初始化mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms); 而SurfaceAnimator的构造方法是

    SurfaceAnimator(Animatable animatable,
            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,
            WindowManagerService service) {
        mAnimatable = animatable;
        mService = service;
        mStaticAnimationFinishedCallback = staticAnimationFinishedCallback;
        mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);
    }

也就是说实际上是把this赋值给了mAnimatable,因此mAnimatable就代表了当前的窗口。

获取Leash

mLeash = freezer != null ? freezer.takeLeashForAnimation() : null; 根据freezer是否为null来确定mLeash的值,我们这里freezer是从WindowContainer.startAnimation方法中传递过来的mSurfaceFreezer,这个变量在WindowContainer的构造方法中初始化mSurfaceFreezer = new SurfaceFreezer(this, wms);,因此mSurfaceFreezer不为null,即freezer不为nullfreezer != null true,所以走freezer.takeLeashForAnimation() 代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceFreezer.java

	SurfaceControl mLeash;
    /**
     * Used by {@link SurfaceAnimator}. This "transfers" the leash to be used for animation.
     * By transferring the leash, this will no longer try to clean-up the leash when finished.
     */
    SurfaceControl takeLeashForAnimation() {
        SurfaceControl out = mLeash;
        mLeash = null;
        return out;
    }

mLeash在SurfaceFreezer类中并没有初始化,因此我们的mLeash值为null,所以out的值同样为null,最终SurfaceAnimator类中的mLeash获取到的值为null

创建Leash

mLeash为null,使用createAnimationLeash方法创建Leash

            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);

入参含义 mAnimatable:当前窗口。 surface:当前窗口的surface。 t:一个事务对象,用于执行一系列操作。 type:动画类型。 mAnimatable.getSurfaceWidth()mAnimatable.getSurfaceHeight():窗口surface尺寸的参数。 0 /* x */0 /* y */:坐标位置 hidden:一个布尔值,表示是否隐藏。 mService.mTransactionFactory:一个事务工厂对象,用于创建新的事务。

SurfaceAnimator.createAnimationLeash() 代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java

    static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
            Transaction t, @AnimationType int type, int width, int height, int x, int y,
            boolean hidden, Supplier<Transaction> transactionFactory) {
        /* log add start*/
        Slog.i("WindowManager:","createAnimationLeash type = " + animationTypeToString(type) , new Exception());
        /* log add end*/
        ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);
        //通过SurfaceControl.Builder创建leash
        final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
                .setParent(animatable.getAnimationLeashParent())
                .setName(surface + " - animation-leash of " + animationTypeToString(type))
                // TODO(b/151665759) Defer reparent calls
                // We want the leash to be visible immediately because the transaction which shows
                // the leash may be deferred but the reparent will not. This will cause the leashed
                // surface to be invisible until the deferred transaction is applied. If this
                // doesn't work, you will can see the 2/3 button nav bar flicker during seamless
                // rotation.
                .setHidden(hidden)
                .setEffectLayer()
                .setCallsite("SurfaceAnimator.createAnimationLeash");
        //通过前面的SurfaceControl.Builder创建leash
        final SurfaceControl leash = builder.build();
        //其他属性设置
        t.setWindowCrop(leash, width, height);
        t.setPosition(leash, x, y);
        t.show(leash);
        t.setAlpha(leash, hidden ? 0 : 1);
        //当前窗口的surface重新绑定到新创建的leash上
        t.reparent(surface, leash);
        return leash;
    }

前面说过animatable为Animatable接口对象,WindowContainer为该接口的实现类,即animatable表示当前窗口,就是我们图层里面的test-window,这个方法主要就是把创建好的leash图层加入到test-window和它父亲节点WindowToken之间。 在这里插入图片描述 下面我们解读一下,leash是如何创建并加入其中的

通过SurfaceControl.Builder创建leash

  • animatable.makeAnimationLeash() 代码路径: frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

        public Builder makeAnimationLeash() {
            return makeSurface().setContainerLayer();
        }
    

    创建一个图层作为容器layer

  • setParent(animatable.getAnimationLeashParent()) 这段代码我们分成两个部分来看,即setParent()getAnimationLeashParent() 1.setParent() 代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

            /**
             * Set a parent surface for our new SurfaceControl.
             *
             * Child surfaces are constrained to the onscreen region of their parent.
             * Furthermore they stack relatively in Z order, and inherit the transformation
             * of the parent.
             *
             * @param parent The parent control.
             */
            @NonNull
            public Builder setParent(@Nullable SurfaceControl parent) {
                mParent = parent;
                return this;
            }
    

    这个段代码很简单,就是给当前SurfaceControl设置一个父SurfaceControl。 2.getAnimationLeashParent() 代码路径: frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

        @Override
        public SurfaceControl getAnimationLeashParent() {
            return getParentSurfaceControl();
        }
        
        /*
         * @return The SurfaceControl parent for this containers SurfaceControl.
         *         The SurfaceControl must be valid if non-null.
         */
        @Override
        public SurfaceControl getParentSurfaceControl() {
            final WindowContainer parent = getParent();
            if (parent == null) {
                return null;
            }
            return parent.getSurfaceControl();
        }
    
        /**
         * @return The SurfaceControl for this container.
         * The SurfaceControl must be valid if non-null.
         */
        @Override
        public SurfaceControl getSurfaceControl() {
            return mSurfaceControl;
        }
    

    简单来说,就是获取当前窗口父SurfaceControl。

    那么合起来setParent(animatable.getAnimationLeashParent())的意思就是,把当前新创建的SurfaceControl(leash)的父亲设置为当前窗口父亲的SurfaceControl。 即此时leash图层和当前窗口test-window的父亲均是WindowToken,两人还在当兄弟。 简图如下: 在这里插入图片描述

  • setEffectLayer() 代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

            /**
             * Indicate whether an 'EffectLayer' is to be constructed.
             *
             * An effect layer behaves like a container layer by default but it can support
             * color fill, shadows and/or blur. These layers will not have an associated buffer.
             * When created, this layer has no effects set and will be transparent but the caller
             * can render an effect by calling:
             *  - {@link Transaction#setColor(SurfaceControl, float[])}
             *  - {@link Transaction#setBackgroundBlurRadius(SurfaceControl, int)}
             *  - {@link Transaction#setShadowRadius(SurfaceControl, float)}
             *
             * @hide
             */
            public Builder setEffectLayer() {
                mFlags |= NO_COLOR_FILL;
                //清空缓冲区设置
                unsetBufferSize();
                return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK);
            }
    

    设置为EffectLayer。它是一种特殊类型的SurfaceControl层,它默认表现得像一个容器层,但可以支持颜色填充、阴影和/或模糊效果。 这个EffectLayer主要就是用于实现一些视觉效果。 默认的注释里面也说明可以使用这些方法来渲染一个效果: Transaction#setColor(SurfaceControl, float[]):使用给定的颜色数组设置该层的颜色。 Transaction#setBackgroundBlurRadius(SurfaceControl, int):设置背景模糊的半径。 Transaction#setShadowRadius(SurfaceControl, float):设置阴影的半径。

final SurfaceControl leash = builder.build(); 最后通过build()方法创建leash

当前窗口的surface重新绑定到新创建的leash上

t.reparent(surface, leash); 这里的surface指的就是从前面传递的当前窗口的SurfaceControl。 代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

        /**
         * Re-parents a given layer to a new parent. Children inherit transform (position, scaling)
         * crop, visibility, and Z-ordering from their parents, as if the children were pixels within the
         * parent Surface.
         *
         * @param sc The SurfaceControl to reparent
         * @param newParent The new parent for the given control.
         * @return This Transaction
         */
        @NonNull
        public Transaction reparent(@NonNull SurfaceControl sc,
                @Nullable SurfaceControl newParent) {
            //检查传入的SurfaceControl对象是否满足某些预设条件
            checkPreconditions(sc);
            long otherObject = 0;
            if (newParent != null) {
            	//检查新父对象是否被释放。如果已经被释放,那么它会抛出异常。
                newParent.checkNotReleased();
                //新父对象不为null且未被释放,那么将新父对象的Native对象赋值给otherObject。
                otherObject = newParent.mNativeObject;
            }
            //传入了三个参数:1.当前对象的Native对象 2.被重新设置父对象的SurfaceControl的Native对象 3.新父对象的Native对象。
            //用于实现重新设置父对象的具体操作。
            nativeReparent(mNativeObject, sc.mNativeObject, otherObject);
            //把被重新设置父对象的SurfaceControl和新父对象存储到mReparentedSurfaces这个map中。
            mReparentedSurfaces.put(sc, newParent);
            return this;
        }

@NonNull SurfaceControl sc: 表示要被重新设置父对象的SurfaceControl对象。这个参数不能为null。 @Nullable SurfaceControl newParent: 表示新的父SurfaceControl对象。可以为null,表示没有新的父对象。 这个方法主要就是把当前窗口的SurfaceControl的父亲,修改为leashmReparentedSurfaces是ArrayMap对象,以键值对的形式临时存储父子关系,key值存储SurfaceControl对象,value为其父SurfaceControl对象。 此时mReparentedSurfaces的键值对为: key:Surface(name=8aabddb test-window)/@0x1038e9a(当前窗口的SurfaceControl) value:Surface(name=Surface(name=8aabddb test-window)/@0x1038e9a - animation-leash of window_animation)/@0x38e37a8(动画图层leash) 即此时leash图层变成了当前窗口test-window图层的父亲,如简易图所示: 曾经我们是兄弟,如今我是你爸爸~ 在这里插入图片描述

需要注意的是,test-window和WindowToken容器之前的关系并未改变,创建leash图层的过程只是改变的是surface之间的关系。 即leash图层是test-window图层的父亲,WindowToken的图层是leash图层的父亲,但是test-window的父亲仍然是WindowToken。 实际关系情况如下图所示: 在这里插入图片描述 所以WindowToken和leash实际上是表面父子,leash和test-window也是表面父子

leash的surface调整

mAnimatable.onAnimationLeashCreated(t, mLeash); 入参是一个事务(用于操作窗口系统的底层API)和一个SurfaceControl对象(表示一个可以控制和操作Surface的接口) 把创建好的mLeash传递到onAnimationLeashCreated方法中,做一些Surface调整操作。 该方法实现在WindowContainer中。 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    void reassignLayer(Transaction t) {
        final WindowContainer parent = getParent();
        if (parent != null) {
            parent.assignChildLayers(t);
        }
    }
    
    void resetSurfacePositionForAnimationLeash(Transaction t) {
        t.setPosition(mSurfaceControl, 0, 0);
        final SurfaceControl.Transaction syncTransaction = getSyncTransaction();
        if (t != syncTransaction) {
            // Avoid restoring to old position if the sync transaction is applied later.
            syncTransaction.setPosition(mSurfaceControl, 0, 0);
        }
        mLastSurfacePosition.set(0, 0);
    }

    @Override
    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
        mLastLayer = -1;
        mAnimationLeash = leash;
        reassignLayer(t);

        // Leash is now responsible for position, so set our position to 0.
        resetSurfacePositionForAnimationLeash(t);
    }

这段代码主要用于在创建新的leash时,重置动画目标的位置,并初始化一些动画相关的状态。同时,可能还用于重新分配或者设置子容器的图层。 首先,在新的动画(leash)被创建时被调用。在这个方法中,首先将mLastLayer(可能表示上一个图层或者上一个动画目标)设置为-1,然后保存传入的leashmAnimationLeash(后面removeLeash流程中会用到mAnimationLeash)。 之后,调用reassignLayer(t)方法,这个方法获取这个视图的父容器,如果父容器存在,那么就调用父容器的assignChildLayers(t)方法(用于调整其所有child的z-order)。 最后,为了确保leash现在位置的控制,调用resetSurfacePositionForAnimationLeash(t)方法将Surface的位置重置为(0,0),重置界面元素的位置以便进行动画。 注:Z-order也被称为深度顺序(depth order)或Z轴顺序,它用于确定图层(Layers)在屏幕上的堆叠顺序。简单来说,Z-order就是图层在Z轴上的位置,Z轴位置越低,图层越在底层,Z轴位置越高,图层越在顶层。

处理动画开始时进行一些设置和准备工作

mAnimatable.onLeashAnimationStarting(t, mLeash); 入参同样是一个事务(用于操作窗口系统的底层API)和一个SurfaceControl对象(表示一个可以控制和操作Surface的接口) onLeashAnimationStarting方法是在ActivityRecord中实现的。 代码路径:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

    @Override
    public void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {
        if (mAnimatingActivityRegistry != null) {
        	//1.将正在启动或者有动画效果的Activity添加到列表中,以便于管理和控制这些Activity的动画效果。
            mAnimatingActivityRegistry.notifyStarting(this);
        }

        // If the animation needs to be cropped then an animation bounds layer is created as a
        // child of the root pinned task or animation layer. The leash is then reparented to this
        // new layer.
        //2.否需要创建一个动画边界层
        if (mNeedsAnimationBoundsLayer) {
        	//设置临时矩形为空
            mTmpRect.setEmpty();
            //调用方法检查当前的活动转移是否在任务内部。
            //如果是,则获取任务的边界到临时矩形mTmpRect。如果不是,则获取RootTask的边界。
            if (getDisplayContent().mAppTransitionController.isTransitWithinTask(
                    getTransit(), task)) {
                task.getBounds(mTmpRect);
            } else {
                final Task rootTask = getRootTask();
                if (rootTask == null) {
                    return;
                }
                // Set clip rect to root task bounds.
                rootTask.getBounds(mTmpRect);
            }
            //创建动画边界层
            mAnimationBoundsLayer = createAnimationBoundsLayer(t);

            // Crop to root task bounds.
            //设置leash的层为0
            //leash将被放置在Z轴的最底层,如果有其他层级的SurfaceControl对象,它们将会覆盖在leash之上。
            t.setLayer(leash, 0);
            //并设置AnimationBoundsLayer的层为上一个层的值,保证leash在AnimationBoundsLayer下面
            t.setLayer(mAnimationBoundsLayer, getLastLayer());

            // Reparent leash to animation bounds layer.
            //重新将leash的父节点设置为动画边界层。
            t.reparent(leash, mAnimationBoundsLayer);
        }
    }

    private SurfaceControl createAnimationBoundsLayer(Transaction t) {
        ProtoLog.i(WM_DEBUG_APP_TRANSITIONS_ANIM, "Creating animation bounds layer");
        final SurfaceControl.Builder builder = makeAnimationLeash()
        		//给AnimationBoundsLayer设置父节点为Leash的父节点
        		//即把动画边界层的父节点设置为windowToken
                .setParent(getAnimationLeashParent())
                .setName(getSurfaceControl() + " - animation-bounds")
                .setCallsite("ActivityRecord.createAnimationBoundsLayer");
        final SurfaceControl boundsLayer = builder.build();
        t.show(boundsLayer);
        return boundsLayer;
    }

这个方法其实主要就是做了两件事: 1.根据mAnimatingActivityRegistry的值判断,是否需要把有动画效果的Activity添加到列表中 2.根据mNeedsAnimationBoundsLayer的值判断,否需要创建一个动画边界层

createAnimationBoundsLayer就是创建了一个SurfaceControl。 getLastLayer()用于返回当前窗口的最高(或最后)层级。假设我们有一个窗口管理系统中,窗口的层级从0开始编号。当一个新窗口创建时,它可能被赋予层级0。然后,如果这个新窗口被另一个窗口覆盖,那么新窗口的层级可能会更新为1,依此类推。 通过使用AnimationBoundsLayer,可以定义一个矩形区域,该区域可以作为动画的边界。当动画开始时,它只在该定义的区域内显示,不会超出这个边界。AnimationBoundsLayer的主要作用是限制动画的显示区域,以确保动画不会影响到应用程序的其他部分。

将leash传给LocalAnimationAdapter,执行动画

mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback); mAnimation是AnimationAdapter接口的对象,调用其startAnimation方法,传递mLeash(动画)、t(事务)、type(动画类型)和mInnerAnimationFinishedCallback(回调函数)。 mAnimation的值是前面WindowContainer.startAnimation传递的anim,这个anim实际上就是WindowState.startAnimation中创建的LocalAnimationAdapter对象,因此接口AnimationAdapter的方法startAnimation在LocalAnimationAdapter中实现。 代码路径:frameworks/base/services/core/java/com/android/server/wm/LocalAnimationAdapter.java

    @Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));
    }
  • mAnimator是SurfaceAnimationRunner对象,会根据这个WindowAnimationSpec对象的属性来创建和执行动画。

  • mSpec是WindowAnimationSpec对象,也就是窗口动画的各种属性和行为。 这两个参数都是在LocalAnimationAdapter构造方法中初始化的

        LocalAnimationAdapter(AnimationSpec spec, SurfaceAnimationRunner animator) {
            mSpec = spec;
            mAnimator = animator;
        }
    

    在前面说过的WindowState.startAnimation()流程中调用的

            final AnimationAdapter adapter = new LocalAnimationAdapter(
                    new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
                            0 /* windowCornerRadius */),
                    mWmService.mSurfaceAnimationRunner);
    

    WindowAnimationSpec构造方法对一下参数进行了初始化

        public WindowAnimationSpec(Animation animation, Point position, boolean canSkipFirstFrame,
                float windowCornerRadius)  {
            this(animation, position, null /* rootTaskBounds */, canSkipFirstFrame, ROOT_TASK_CLIP_NONE,
                    false /* isAppAnimation */, windowCornerRadius);
        }
    
        public WindowAnimationSpec(Animation animation, Point position, Rect rootTaskBounds,
                boolean canSkipFirstFrame, int rootTaskClipMode, boolean isAppAnimation,
                float windowCornerRadius) {
            mAnimation = animation;
            if (position != null) {
                mPosition.set(position.x, position.y);
            }
            mWindowCornerRadius = windowCornerRadius;
            mCanSkipFirstFrame = canSkipFirstFrame;
            mIsAppAnimation = isAppAnimation;
            mRootTaskClipMode = rootTaskClipMode;
            if (rootTaskBounds != null) {
                mRootTaskBounds.set(rootTaskBounds);
            }
        }
    

    这里从代码中我们可以看到mWindowCornerRadius的值是0mRootTaskBounds的值是nullmRootTaskClipMode的值是ROOT_TASK_CLIP_NONE

  • () -> finishCallback.onAnimationFinished(type, this))当动画完成时,这个函数会被调用,并执行finishCallback.onAnimationFinished(type, this)

这里LocalAnimationAdapter.startAnimation方法的作用实际上就是播放完动画后,通过回调函数移除动画,这里我们关注播放流程。

通过SurfaceAnimationRunner启动动画

SurfaceAnimationRunner.startAnimation() 代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java

	void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
            Runnable finishCallback) {
        synchronized (mLock) {
            //创建RunningAnimation对象,把传递的参数赋值给RunningAnimation
            final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                    finishCallback);
            //判断一个窗口动画是否应用了扩展动画,通过查代码发现requiresEdgeExtension(a)的默认值为false
            //requiresEdgeExtension(a)为true的情况涉及ExtendAnimation,这里我们不讨论
            boolean requiresEdgeExtension = requiresEdgeExtension(a);

			//requiresEdgeExtension为true
            if (requiresEdgeExtension) {
				......
            }

			//requiresEdgeExtension为false
            if (!requiresEdgeExtension) {
            	//mPendingAnimations是一个ArrayMay
            	//将创建的runningAnim动画对象添加到mPreProcessingAnimations映射中,使用animationLeash作为键
                mPendingAnimations.put(animationLeash, runningAnim);
                //mAnimationStartDeferred判断动画是否延迟启动
                //mPreProcessingAnimations.isEmpty()判断动画是否为空
                if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
                	//通过Choreographer请求Vsync信号,接收到Vsync信号回调this::startAnimations
                	//即使用postFrameCallback在下一帧绘制完成后回调this::startAnimations
                    mChoreographer.postFrameCallback(this::startAnimations);
                }

                // Some animations (e.g. move animations) require the initial transform to be
                // applied immediately.
                applyTransformation(runningAnim, t, 0 /* currentPlayTime */);
            }
        }
    }

requiresEdgeExtensiontrue的情况涉及ExtendAnimation用于扩展动画效果。 这里我们只关注requiresEdgeExtensionfalse的情况,下面我们来说下这个方法中所涉及的关键代码

创建RunningAnimation对象

final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash, finishCallback);

    private static final class RunningAnimation {
        final AnimationSpec mAnimSpec;
        final SurfaceControl mLeash;
        final Runnable mFinishCallback;
        ValueAnimator mAnim;

        @GuardedBy("mCancelLock")
        private boolean mCancelled;

        RunningAnimation(AnimationSpec animSpec, SurfaceControl leash, Runnable finishCallback) {
            mAnimSpec = animSpec;
            mLeash = leash;
            mFinishCallback = finishCallback;
        }
    }

从代码中可以看出RunningAnimation只是存储前面传递的a(窗口动画的各种属性和行为)、animationLeash(动画)和finishCallback(回调函数)

动画的播放条件

if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty())我们主要关注这个条件中的mAnimationStartDeferred变量,这个变量主要是用来控制动画的播放时机的mPreProcessingAnimations是个ArrayMay,这个只在前面requiresEdgeExtensiontrue时逻辑中有涉及,这里不讨论。 在SurfaceAnimationRunner类中设置mAnimationStartDeferred的值的方法有两个,deferStartingAnimations()continueStartingAnimations()

    /**
     * Defers starting of animations until {@link #continueStartingAnimations} is called. This
     * method is NOT nestable.
     *
     * @see #continueStartingAnimations
     */
    void deferStartingAnimations() {
        synchronized (mLock) {
            mAnimationStartDeferred = true;
        }
    }

    /**
     * Continues starting of animations.
     *
     * @see #deferStartingAnimations
     */
    void continueStartingAnimations() {
        synchronized (mLock) {
            mAnimationStartDeferred = false;
            if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) {
                mChoreographer.postFrameCallback(this::startAnimations);
            }
        }
    }

这两个方法,是在前面窗口添加流程中handleAppTransitionReady()方法里调用

	void handleAppTransitionReady() {
        ......
        mService.mSurfaceAnimationRunner.deferStartingAnimations();
        try {
            ......
        } finally {
            mService.mSurfaceAnimationRunner.continueStartingAnimations();
        }

       ......
    }

从代码中可以看出这个方法,最后通过调用continueStartingAnimations(),把mAnimationStartDeferred设置为了false 我们在结合前面startAnimation中的这段代码来看

                mPendingAnimations.put(animationLeash, runningAnim);
                if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) {
                    mChoreographer.postFrameCallback(this::startAnimations);
                }

可以推测出,deferStartingAnimations() 方法用来将动画的启动时间推迟到 continueStartingAnimations() 方法被调用。 这意味着,当你调用deferStartingAnimations() 后,所有新的动画不会立即开始,它们会被放入待处理列表(在 mPendingAnimations 中)。 continueStartingAnimations() 方法则用于恢复动画的启动。当这个方法被调用时,动画的启动不再被推迟,所有待处理的动画会开始播放。 这两个方法通常用于动画的复杂控制,例如,在数据加载或其他后台任务完成之前,你可能希望延迟动画的播放,直到这些任务完成。 简单来说,这两个方法的主要作用是控制何时开始播放动画。

启动动画并监听动画的播放和结束

mChoreographer.postFrameCallback(this::startAnimations); 通过Choreographer的postFrameCallback方法在下一帧绘制完成后回调this::startAnimations,其实就是通过请求和接收 Vsync 信号播放动画。 实际调用的就是SurfaceAnimationRunner中的startAnimations方法

   private void startAnimations(long frameTimeNanos) {
       synchronized (mLock) {
           if (!mPreProcessingAnimations.isEmpty()) {
               // We only want to start running animations once all mPreProcessingAnimations have
               // been processed to ensure preprocessed animations start in sync.
               // NOTE: This means we might delay running animations that require preprocessing if
               // new animations that also require preprocessing are requested before the previous
               // ones have finished (see b/227449117).
               return;
           }
           //启动所有待处理的动画
           startPendingAnimationsLocked();
       }
       //用于设置电源管理器的功率增强
       mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
   }

mPreProcessingAnimations是个ArrayMay,这个只在前面requiresEdgeExtensiontrue时逻辑中有涉及,这里不讨论。 这个方法的作用是:当所有预处理的动画都处理完毕后,它会启动所有待处理的动画。同时,它还设置了一个电源管理器的功率增强。 这里我们主要关注startPendingAnimationsLocked();

	@GuardedBy("mLock")
    private void startPendingAnimationsLocked() {
        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
        	//开启动画列表中的每个动画
            startAnimationLocked(mPendingAnimations.valueAt(i));
        }
        //清理动画列表
        mPendingAnimations.clear();
    }

    @GuardedBy("mLock")
    private void startAnimationLocked(RunningAnimation a) {
    	//创建了一个ValueAnimator(属性动画)
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        // Animation length is already expected to be scaled.
        anim.overrideDurationScale(1.0f);//设置动画的持续时间比例为1.0,就是把动画将按照其原始速度运行。
        anim.setDuration(a.mAnimSpec.getDuration());//设置动画的持续时间为a.mAnimSpec的持续时间。
        // 为动画(anim)添加一个更新监听器。当动画的属性值发生变化时,会触发这个监听器的回调函数。
        anim.addUpdateListener(animation -> {
        	//使用mCancelLock对象作为锁,确保同一时间只有一个线程能够执行以下代码块,避免并发访问和修改数据造成的问题。
            synchronized (mCancelLock) {
            	//检查a对象的mCancelled属性,如果mCancelled为false,表示动画没有被取消,则继续执行下面的代码。
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();//获取动画的持续时间。
                    long currentPlayTime = anim.getCurrentPlayTime();//获取动画当前的播放时间。
                    //如果当前播放时间超过了持续时间,将当前播放时间设置为持续时间。
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    //根据动画的当前时间点获取相应的变换效果,并将这些效果应用到指定的Surface对象(leash图层)上,包括位置、透明度、裁剪和圆角等。
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            // Transaction will be applied in the commit phase.
            //执行下一帧动画
            scheduleApplyTransaction();
        });

		//为anim添加一个监听器。监听器是AnimatorListenerAdapter的一个实例,需要重写onAnimationStart和onAnimationEnd两个方法作为动画开始和结束的回调。
        anim.addListener(new AnimatorListenerAdapter() {
        	//当动画开始播放时,这个方法会被调用。
            @Override
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        // TODO: change this back to use show instead of alpha when b/138459974 is
                        // fixed.
                        //设置a.mLeash的透明度为1(完全不透明)。
                        mFrameTransaction.setAlpha(a.mLeash, 1);
                    }
                }
            }

			//当动画结束时,这个方法会被调用。
            @Override
            public void onAnimationEnd(Animator animation) {
                synchronized (mLock) {
                	//从mRunningAnimations这个映射(或字典)中移除与a.mLeash相关的动画。
                    mRunningAnimations.remove(a.mLeash);
                    synchronized (mCancelLock) {
                        if (!a.mCancelled) {

                            // Post on other thread that we can push final state without jank.
                            //调用回调函数
                            mAnimationThreadHandler.post(a.mFinishCallback);
                        }
                    }
                }
            }
        });
        //将当前创建的ValueAnimator赋值给RunningAnimation中的ValueAnimator
        a.mAnim = anim;
        //将动画与相关的对象(如动画的状态、回调等)添加到mRunningAnimations这个映射中
        //键为a.mLeash(leash图层),值为RunningAnimation对象
        mRunningAnimations.put(a.mLeash, a);

		//开始动画
        anim.start();
        //判断是否可以跳过第一帧
        if (a.mAnimSpec.canSkipFirstFrame()) {
            // If we can skip the first frame, we start one frame later.
            //如果我们可以跳过第一帧,我们将在一帧之后开始
            //当前播放时间设置为Choreographer的帧间隔纳秒值除以毫秒值
            anim.setCurrentPlayTime(mChoreographer.getFrameIntervalNanos() / NANOS_PER_MS);
        }

        // Immediately start the animation by manually applying an animation frame. Otherwise, the
        // start time would only be set in the next frame, leading to a delay.
        //执行一帧动画
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

这个方法的整体流程就是

  1. 创建ValueAnimator对象anim,设置基本属性和监听器
  2. 将当前动画添加到mRunningAnimations列表中(后续移除动画逻辑中,取消动画流程用到)
  3. anim.start()播放动画,先执行anim.addListener中的onAnimationStart
  4. 通过anim.doAnimationFrame(mChoreographer.getFrameTime())播放一帧动画。
  5. 之后anim.addUpdateListener监听到动画属性发生变化,调用applyTransformation根据动画的当前时间点获取相应的变换效果,再使用scheduleApplyTransaction调用下一帧动画。
  6. 最后动画播放结束时,执行anim.addListener中的onAnimationEnd,通过mAnimationThreadHandler.post(a.mFinishCallback);执行回调函数收尾。

我们来看看这个代码中的一些关键方法。

  • applyTransformation(a, mFrameTransaction, currentPlayTime);

        private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
            a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
        }
    

    a.mLeash就是leash图层,这个mLeashmAnimSpec是前面SurfaceAnimationRunner.startAnimation方法中创建RunningAnimation时保存的。 mAnimSpec是AnimationSpec接口对象,apply()方法是在WindowAnimationSpec实现的,把leash图层传递了过去。 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowAnimationSpec.java

        @Override
        public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
            //从线程局部变量mThreadLocalTmps中获取一个TmpValues对象,并将其存储在局部变量tmp中
            final TmpValues tmp = mThreadLocalTmps.get();
            //清除tmp.transformation中的所有变换
            tmp.transformation.clear();
            //根据当前时间戳获取一个动画变换,并将其存储在tmp.transformation中
            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;
            //ROOT_TASK_CLIP_NONE用来表示不进行任何剪裁的
            if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
                if (tmp.transformation.hasClipRect()) {//检查tmp.transformation是否有剪裁矩形(ClipRect)
                    //设置窗口的剪裁区域并标记cropSet为true
                    final Rect clipRect = tmp.transformation.getClipRect();
                    accountForExtension(tmp.transformation, clipRect);
                    t.setWindowCrop(leash, clipRect);
                    cropSet = true;
                }
            } else {
                //设置一个临时矩形,mRootTaskBounds在WindowAnimationSpec构造方法中初始化
                mTmpRect.set(mRootTaskBounds);
                if (tmp.transformation.hasClipRect()) {//检查tmp.transformation是否有剪裁矩形
                    //取临时矩形和剪裁矩形的交集,形成新矩阵存储至mTmpRect
                    mTmpRect.intersect(tmp.transformation.getClipRect());
                }
                //设置窗口的剪裁区域并标记cropSet为true
                accountForExtension(tmp.transformation, mTmpRect);
                t.setWindowCrop(leash, mTmpRect);
                cropSet = true;
            }
    
            // We can only apply rounded corner if a crop is set, as otherwise the value is meaningless,
            // since it doesn't have anything it's relative to.
            //如果设置了剪裁(由之前的cropSet标记),并且动画有圆角设置,且窗口的圆角半径大于0
            if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) {
                //则设置界面元素的圆角
                t.setCornerRadius(leash, mWindowCornerRadius);
            }
    

    这个方法的作用是根据给定的动画(leash)和变换信息更新界面元素的属性,包括其位置、透明度、剪裁和圆角。

  • scheduleApplyTransaction()

        private void scheduleApplyTransaction() {
            if (!mApplyScheduled) {
                mChoreographer.postCallback(CALLBACK_TRAVERSAL, mApplyTransactionRunnable,
                        null /* token */);
                mApplyScheduled = true;
            }
        }
    

    通过标志位mApplyScheduled判断是否调用mChoreographer对象的postCallback方法,这个方法用于在UI线程中安排一个回调。mApplyTransactionRunnable这是一个Runnable对象,它定义了当回调被触发时要执行的任务。之后把标志位mApplyScheduled置为true,表示事务已经被调度。 这个mApplyTransactionRunnable其实就是SurfaceAnimationRunner中定义的

        private final Runnable mApplyTransactionRunnable = this::applyTransaction;
        
        private void applyTransaction() {
        //将当前的事务设置为动画事务
        mFrameTransaction.setAnimationTransaction();
        //调用了mChoreographer对象的getVsyncId方法来获取vsync的ID,
        //使用这个ID来设置mFrameTransaction的帧时间线vsync。
        //这是为了确保事务在正确的vsync时间点被应用。
        mFrameTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
        //提交之前设置的事务。
        mFrameTransaction.apply();
        //将mApplyScheduled标志位设置为false。
        mApplyScheduled = false;
    }
    
  • mAnimationThreadHandler.post(a.mFinishCallback); 这个a.mFinishCallback,实际上就是前面LocalAnimationAdapter.startAnimation方法中传递的() -> finishCallback.onAnimationFinished(type, this),调用这个函数开始动画的结束流程,具体看后续的移除流程。

立即应用一次动画变换

applyTransformation(runningAnim, t, 0 /* currentPlayTime */); 同样是调用到了WindowAnimationSpec.apply方法 对当前动画(runningAnim)应用一个变换(可能是缩放、旋转或平移等,0是当前的播放时间。这个方法是立即应用初始变换,为了让动画可以立即看到效果。

通过WindowAnimator协调动画和窗口的显示

我们回到WindowState.startAnimation()方法中的最后调用了commitPendingTransaction(); 调用到了WindowContainer的commitPendingTransaction() 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    public void commitPendingTransaction() {
        scheduleAnimation();
    }

    /**
     * Trigger a call to prepareSurfaces from the animation thread, such that pending transactions
     * will be applied.
     */
    void scheduleAnimation() {
        mWmService.scheduleAnimationLocked();
    }

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    void scheduleAnimationLocked() {
        mAnimator.scheduleAnimation();
    }

最终调用到了WindowAnimator.scheduleAnimation() 代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java

    void scheduleAnimation() {
        if (!mAnimationFrameCallbackScheduled) {
            //mAnimationFrameCallbackScheduled 设置为 true,表示动画帧回调已经安排
            mAnimationFrameCallbackScheduled = true;
            //每一帧被绘制时,回调mAnimationFrameCallback
            mChoreographer.postFrameCallback(mAnimationFrameCallback);
        }
    }

这个方法的主要作用是确保动画帧回调被正确地安排,以便在每一帧绘制时执行,可以确保动画在每一帧都被调用,从而平滑地更新和显示动画。

WindowAnimator构造方法

mAnimationFrameCallback是在WindowAnimator构造方法中初始化的,而WindowAnimator构造方法是在WindowManagerService构造方法中初始化的

    WindowAnimator(final WindowManagerService service) {
        mService = service;
        mContext = service.mContext;
        mPolicy = service.mPolicy;
        mTransaction = service.mTransactionFactory.get();
        //使用runWithScissors方法,确保Runnable对象在正确的线程上执行,并提供了超时机制来处理长时间运行的操作
        //创建一个Choreographer实例,并赋值给mChoreographer,处理动画和渲染更新
        service.mAnimationHandler.runWithScissors(
                () -> mChoreographer = Choreographer.getSfInstance(), 0 /* timeout */);

        //实现Choreographer.FrameCallback接口的doFrame方法
        //入参为frameTimeNs 表示当前帧的时间戳,以纳秒为单位
        mAnimationFrameCallback = frameTimeNs -> {
            synchronized (mService.mGlobalLock) {
                //mAnimationFrameCallbackScheduled设置为false,表示该回调不再被安排
                mAnimationFrameCallbackScheduled = false;
                //从Choreographer对象中获取当前垂直同步(VSync)的ID
                final long vsyncId = mChoreographer.getVsyncId();
                //执行实际的动画逻辑
                animate(frameTimeNs, vsyncId);
                //mNotifyWhenNoAnimation:表示在没有动画时,是否通知某些东西
                //mLastRootAnimating:表示当前是否有正在进行的顶层(root层)动画
                //即!mLastRootAnimating表示没有正在进行的顶层动画,这个在animate方法中会赋值
                if (mNotifyWhenNoAnimation && !mLastRootAnimating) {
                    //唤醒所有等待在mService.mGlobalLock上的线程
                    mService.mGlobalLock.notifyAll();
                }
            }
        };
    }

执行实际的动画逻辑

animate(frameTimeNs, vsyncId);

    private void animate(long frameTimeNs, long vsyncId) {
        if (!mInitialized) {
            return;
        }

        // Schedule next frame already such that back-pressure happens continuously.
        //调用下一帧动画
        scheduleAnimation();

        //获取RootWindowContainer根容器
        final RootWindowContainer root = mService.mRoot;
        //更新当前时间为给定的frameTimeNs除以毫秒TimeUtils.NANOS_PER_MS
        mCurrentTime = frameTimeNs / TimeUtils.NANOS_PER_MS;
        //运行窗口更新一个标志位
        mBulkUpdateParams = 0;
        //表示显示方向的更新是否完成
        root.mOrientationChangeComplete = true;
        if (DEBUG_WINDOW_TRACE) {
            Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
        }

        ProtoLog.i(WM_SHOW_TRANSACTIONS, ">>> OPEN TRANSACTION animate");
        //开启surface事务
        mService.openSurfaceTransaction();
        try {
            // Remove all deferred displays, tasks, and activities.
            //处理所有延迟移除的一些容器
            root.handleCompleteDeferredRemoval();

            //创建accessibilityController
            final AccessibilityController accessibilityController =
                    mService.mAccessibilityController;
            //更新每个显示内容的窗口
            final int numDisplays = mDisplayContentsAnimators.size();
            for (int i = 0; i < numDisplays; i++) {
                final int displayId = mDisplayContentsAnimators.keyAt(i);
                final DisplayContent dc = root.getDisplayContent(displayId);
                // Update animations of all applications, including those associated with
                // exiting/removed apps.
                //实际就是调用了WindowContainer中的forAllWindows(mUpdateWindowsForAnimator, true /* traverseTopToBottom */);
                //该方法根据traverseTopToBottom决定遍历的顺序,来对所有的容器执行callback回调函数。
                dc.updateWindowsForAnimator();
                //显示Surface
                dc.prepareSurfaces();
            }

            for (int i = 0; i < numDisplays; i++) {
                final int displayId = mDisplayContentsAnimators.keyAt(i);
                final DisplayContent dc = root.getDisplayContent(displayId);
                
                //检查App中的所有窗口是否都已绘制,并在需要时显示它们。
                dc.checkAppWindowsReadyToShow();
                //如果accessibilityController有回调,则绘制放大的区域边界。
                if (accessibilityController.hasCallbacks()) {
                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId,
                            mTransaction);
                }
            }

            //取消当前的动画
            //就是通过mChoreographer移除mAnimationFrameCallback
            cancelAnimation();

            //绘制水印
            if (mService.mWatermark != null) {
                mService.mWatermark.drawIfNeeded();
            }

        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        }
        
        //检查根窗口容器是否有待处理的布局更改,特别是与壁纸相关的更改
        final boolean hasPendingLayoutChanges = root.hasPendingLayoutChanges(this);
        //方法的开头已经将mBulkUpdateParams置为0,root.mOrientationChangeComplete设置为true
        //root.copyAnimToLayoutParams() 根据当前的系统状态(如屏幕方向更改、窗口冻结和壁纸动作)来检查是否需要更新状态。
        final boolean doRequest = (mBulkUpdateParams != 0 || root.mOrientationChangeComplete)
                && root.copyAnimToLayoutParams();
        //满足hasPendingLayoutChanges和doRequest之一,就需要重新请求布局
        if (hasPendingLayoutChanges || doRequest) {
            //requestTraversal()是属于WMS.relayoutWindow中的流程
            mService.mWindowPlacerLocked.requestTraversal();
        }

        //检查根窗口是否正在进行动画
        final boolean rootAnimating = root.isAnimating(TRANSITION | CHILDREN /* flags */,
                ANIMATION_TYPE_ALL /* typesToCheck */);
        //如果根窗口正在进行动画,并且之前没有进行过动画
        if (rootAnimating && !mLastRootAnimating) {
            Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
        }
        //如果根窗口不再进行动画,并且之前正在进行动画
        if (!rootAnimating && mLastRootAnimating) {
            mService.mWindowPlacerLocked.requestTraversal();
            Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "animating", 0);
        }
        //当前动画进行状态变为,上一次动画进行状态,以便下次使用
        mLastRootAnimating = rootAnimating;

        //判断TRANSITION 当前窗口 和 CHILDREN 当前窗口的子节点的
        //ANIMATION_TYPE_APP_TRANSITION 应用切换、
        //ANIMATION_TYPE_SCREEN_ROTATION 屏幕旋转和
        //ANIMATION_TYPE_RECENTS 最近任务
        //等这些动画类型是否正在进行
        final boolean runningExpensiveAnimations =
                root.isAnimating(TRANSITION | CHILDREN /* flags */,
                        ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_SCREEN_ROTATION
                                | ANIMATION_TYPE_RECENTS /* typesToCheck */);
                                
        //mRunningExpensiveAnimations,表示上一次的RunningExpensiveAnimations的值,
        //如果runningExpensiveAnimations为true,
        //并且mRunningExpensiveAnimations为false,
        //则暂停任务快照的持久化并设置早期的唤醒开始
        if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
            // Usually app transitions put quite a load onto the system already (with all the things
            // happening in app), so pause task snapshot persisting to not increase the load.
            mService.mTaskSnapshotController.setPersisterPaused(true);
            mTransaction.setEarlyWakeupStart();
        //如果runningExpensiveAnimations为false,
        //并且mRunningExpensiveAnimations为true,
        //则恢复任务快照的持久化和设置早期的唤醒结束
        } else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) {
            mService.mTaskSnapshotController.setPersisterPaused(false);
            mTransaction.setEarlyWakeupEnd();
        }
        //把当前的runningExpensiveAnimations赋值给mRunningExpensiveAnimations
        //以便下次使用
        mRunningExpensiveAnimations = runningExpensiveAnimations;

        //将当前事务合并到全局事务中
        SurfaceControl.mergeToGlobalTransaction(mTransaction);
        //关闭当前的事务
        mService.closeSurfaceTransaction("WindowAnimator");
        ProtoLog.i(WM_SHOW_TRANSACTIONS, "<<< CLOSE TRANSACTION animate");

        //如果标记为需要移除被替换的窗口
        //则从根窗口容器中移除这些窗口,并重置该标记
        if (mRemoveReplacedWindows) {
            root.removeReplacedWindows();
            mRemoveReplacedWindows = false;
        }

        //分发和处理待处理事件的作用。
        //这个方法根据事件的类型(如任务出现、任务消失、任务信息改变等)来调用相应的处理方法。
        mService.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
        //执行在PrepareSurfaces后需要执行的一些任务
        executeAfterPrepareSurfacesRunnables();

        if (DEBUG_WINDOW_TRACE) {
            Slog.i(TAG, "!!! animate: exit"
                    + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
                    + " hasPendingLayoutChanges=" + hasPendingLayoutChanges);
        }
    }

动画移除流程

Android T 窗口动画(本地动画)显示流程其三——移除流程