Android U WMS: 屏幕旋转动画(5) 执行动画

1,708 阅读21分钟

Android U WMS : 旋转动画(4) 准备动画数据 计算出的旋转动画的数据,如下

{
    id=13 
    t=CHANGE 
    f=0x0 
    // track id
    trk=0 
    r=[0@Point(0, 0)] 
    
    // TransitionInfo#mChanges
    c=[
        {
            WCT{RemoteToken{4220415 Task{d77d5e9 #26 type=standard A=10206:com.awesome.helloworld}}} 
            m=CHANGE 
            f=NONE 
            p=WCT{RemoteToken{fec6a3d DefaultTaskDisplayArea@10996965}} 
            leash=Surface(name=Task=26)/@0xe4347ff 
            sb=Rect(0, 0 - 1280, 1840) 
            eb=Rect(0, 0 - 1840, 1280) 
            d=0 
            r=0->1:0
        },

        {
            WCT{RemoteToken{fec6a3d DefaultTaskDisplayArea@10996965}} 
            m=CHANGE 
            f=NONE 
            p=WCT{RemoteToken{3f0e1d2 Display{#0 state=ON size=1840x1280 ROTATION_90}}} 
            leash=Surface(name=DefaultTaskDisplayArea)/@0x46effc9 
            sb=Rect(0, 0 - 1280, 1840) 
            eb=Rect(0, 0 - 1840, 1280) 
            d=0 
            r=0->1:-1
        },

        {
            WCT{RemoteToken{3f0e1d2 Display{#0 state=ON size=1840x1280 ROTATION_90}}} 
            m=CHANGE 
            f=IS_DISPLAY 
            leash=Surface(name=WindowedMagnification:0:31)/@0x5399049 
            sb=Rect(0, 0 - 1280, 1840) 
            eb=Rect(0, 0 - 1840, 1280) 
            d=0 
            r=0->1:-1 
            snapshot=Surface(name=RotationLayer)/@0x48338b1
        }
    ]
}

重点解释下本文会涉及到的几个重要数据

  1. 动画目标 DisplayContent 是没有 parent 的。
  2. 动画目标 DisplayContent 的 leash 是它的 child WindowedMagnification:0:31,而不是自己的 surface。
  3. sb 是 start bounds 的意思,eb 是 end bounds 的意思,它们代表旋转前后的 bounds。
  4. 旋转参数,例如 r=0->1:-1,其中 0->1 表示旋转方向从 0 到 1,最后一个数据 -1 表示旋转动画的方式。
  5. snapshot 代表的截图层的 surface。

transiton ready

Android U WMS : 旋转动画(4) 准备动画数据 计算出的旋转动画的数据之后,然后通知 WMShell transition ready,并传递的数据,如下

// Transitions.java

// 参数 t 是 start transaction
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
        @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
    info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
    
    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
            transitionToken, info);
    
    // 根据 token,从 mPendingTransitions 找到索引
    final int activeIdx = findByToken(mPendingTransitions, transitionToken);
    if (activeIdx < 0) {
        throw new IllegalStateException("Got transitionReady for non-pending transition "
                + transitionToken + ". expecting one of "
                + Arrays.toString(mPendingTransitions.stream().map(
                        activeTransition -> activeTransition.mToken).toArray()));
    }
    
    // 根据索引获取 ActiveTransition,并保存 WMCore 传入的参数
    final ActiveTransition active = mPendingTransitions.remove(activeIdx);
    active.mInfo = info;
    active.mStartT = t;
    active.mFinishT = finishT;
    
    if (activeIdx > 0) {
        Log.i(TAG, "Transition might be ready out-of-order " + activeIdx + " for " + active
                + ". This is ok if it's on a different track.");
    }
    
    if (!mReadyDuringSync.isEmpty()) {
        // ...
    } else {
        // 分发 transition
        dispatchReady(active);
    }
}

根据 Android U WMS : 屏幕旋转动画(1)#WMShell 处理 transition 请求 的分析,当 WMShell 收到请求时,会创建 ActiveTransition ,并保存到 mPendingTransitions。

此时,WMShell 收到 transition ready 请求,根据 transition token,从 mPendingTransitions 中匹配到 ActiveTransition,然后开始分发这个 transition,如下

// Transitons.java

boolean dispatchReady(ActiveTransition active) {
    final TransitionInfo info = active.mInfo;

    if (info.getType() == TRANSIT_SLEEP || active.isSync()) {
        // ...
    }

    // 1. 根据 track id 获取/创建 Track
    final Track track = getOrCreateTrack(info.getTrack());
    // active 保存到 Track 的 ready queue 的队尾
    track.mReadyTransitions.add(active);
    
    for (int i = 0; i < mObservers.size(); ++i) {
        mObservers.get(i).onTransitionReady(
                active.mToken, info, active.mStartT, active.mFinishT);
    }


    if (info.getRootCount() == 0 && !KeyguardTransitionHandler.handles(info)) {
        // ...
    }
    
    final int changeSize = info.getChanges().size();
    boolean taskChange = false;
    boolean transferStartingWindow = false;
    boolean allOccluded = changeSize > 0;
    for (int i = changeSize - 1; i >= 0; --i) {
        final TransitionInfo.Change change = info.getChanges().get(i);
        // true
        taskChange |= change.getTaskInfo() != null;
        // false
        transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT);
        if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
            // false
            allOccluded = false;
        }
    }
    if (!taskChange && transferStartingWindow && changeSize == 2
            || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
            && allOccluded)) {
        // ...
    }

    // 2. 使用 start transaction 或者 finish transaction 初始化 surface 状态
    setupStartState(active.mInfo, active.mStartT, active.mFinishT);
    
    // 如果 Track 的 ready queue 至少有两个 transition 等待执行,
    // 那么不执行当前的 transition,因为下一个需要执行的是 ready queue 中队首的 transition
    // TODO: 这里应该加一个 log,表明为何 transition 没有执行
    if (track.mReadyTransitions.size() > 1) {
        // There are already transitions waiting in the queue, so just return.
        return true;
    }

    // 3. 处理 track 的 ready queue
    processReadyQueue(track);
    return true;
}

分发 transition 的过程

  1. 根据 track id 创建/获取 Track,然后使用 Track#mReadyTransitions( 别名为 ready queue ) 保存当前要分发的 transition。Track 与 transition 是一对多的关系,Track 中的多个 transition 可以依次(FIFO)执行,也可以合并后执行。当然,系统也支持多 Track 并行执行。对于当前分析的屏幕就旋转动画来说,会创建 Track,并且 Track 中也只保存一个 transition。
  2. 使用 start transaction 或者 finish transaction 初始化 leash surface 状态。例如,设置可见性,透明度,等等。参考【初始化 leash 状态
  3. 处理 Track 的 ready queue 中的 transition。

初始化 leash 状态

// Transitions.java

    /**
     * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
     */
    private static void setupStartState(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
        
        // 只有 TRANSIT_OPEN, TRANSIT_TO_FRONT,TRANSIT_KEYGUARD_GOING_AWAY 是 opening type
        boolean isOpening = isOpeningType(info.getType());
        
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            
            final TransitionInfo.Change change = info.getChanges().get(i);
            
            if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
                continue;
            }
            
            // 获取 Change 的 leash 和 transition mode
            final SurfaceControl leash = change.getLeash();
            final int mode = info.getChanges().get(i).getMode();

            if (mode == TRANSIT_TO_FRONT
                    && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
                    || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
                // ...
            }

            // 不能独立做动画的目标,对 leash 做一些基本操作
            // Don't move anything that isn't independent within its parents
            if (!TransitionInfo.isIndependent(change, info)) {
                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
                    // 对 leash 执行操作
                    // t 是 start transaction
                    t.show(leash);
                    t.setMatrix(leash, 1, 0, 0, 1);
                    t.setAlpha(leash, 1.f);
                    t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
                }
                continue;
            }

            if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                // ...
            } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
                // ...
            } else if (isOpening && mode == TRANSIT_CHANGE) {
                // ...
            }
        }
    }

对于屏幕旋转动画来说,初始化 leash 状态,只做了一件事,那就是对不能独立做动画的目标,在 start transaction 中对 leash 进行操作,例如,显示,设置透明度和位置,等等。不过对 leash 的操作,似乎对旋转动画来收,并没有什么用。

来看下,如何判断动画目标,是否可以独立于 parent 做动画,如下

// TransitionInfo.java

    /**
     * Indication that `change` is independent of parents (ie. it has a different type of
     * transition vs. "going along for the ride")
     */
    public static boolean isIndependent(@NonNull TransitionInfo.Change change,
            @NonNull TransitionInfo info) {
        
        // 没有 parent ,可以独立做动画
        // If the change has no parent (it is root), then it is independent
        if (change.getParent() == null) return true;

        // 非可见性的改变,是不能独立做动画
        // non-visibility changes will just be folded into the parent change, so they aren't
        // independent either.
        if (change.getMode() == TRANSIT_CHANGE) return false;

        // 走到这里,表示动画目标的 mode 是可见性改变

        TransitionInfo.Change parentChg = info.getChange(change.getParent());
        while (parentChg != null) {
            // 动画目标是可见性改变,同时 parent 也是可见性改变,也不能独立做动画
            // If the parent is a visibility change, it will include the results of all child
            // changes into itself, so none of its children can be independent.
            if (parentChg.getMode() != TRANSIT_CHANGE) return false;

            // 走到这里,表示 parent 是非可见性改变,也就是 mode 为 TRANSIT_CHANGE

            // parent 是非可见性改变,但是 parent 没有 parent 了,那么可以独立做动画
            // If there are no more parents left, then all the parents, so far, have not been
            // visibility changes which means this change is independent.
            if (parentChg.getParent() == null) return true;

            // 获取 parent 的 parent 信息,继续遍历
            parentChg = info.getChange(parentChg.getParent());
        }

        // 默认是不能独立做动画
        return false;
    }

判断一个动画目标,是否能独立于 parent 做动画,有很多种情况。这里,我们只讨论,旋转动画的几个动画目标,是否能独立于 parent 做动画

  1. Task 和 TaskDisplayArea,都不能独立于 parent 做动画,因为它们的 mode 都为 TRANSIT_CHANGE。
  2. DisplayContent 能独立于 parent 做动画,因为它根本就没有 parent。

对于这个设计理念,我暂时也没有完全明白。不过没关系,我们只要针对不同类型的动画,具体分析即可。一旦掌握了更多动画的执行过程,这里的设计理念,应该就会慢慢明白。

处理 ready queue 中的 transition

对 transition 的所有动画目标的 leash surface 做了初始化后,现在处理 Track 的 ready queue 中的 transition,如下

// Transitions.java

    void processReadyQueue(Track track) {
        
        // 刚刚,已经向 ready queue 中保存过 transition
        if (track.mReadyTransitions.isEmpty()) {
            // ...
        }

        // 获取队首的 transition,此时不为 null
        final ActiveTransition ready = track.mReadyTransitions.get(0);
        if (track.mActiveTransition == null) {
            // 移除队首的 transition
            track.mReadyTransitions.remove(0);
            
            // Track#mActiveTransition 保存正在执行的 transition
            track.mActiveTransition = ready;

            if (ready.mAborted) {
                // ...
            }

            // 执行 transition
            playTransition(ready);
            
            // 继续执行 ready queue 中的 transition
            // Attempt to merge any more queued-up transitions.
            processReadyQueue(track);
            return;
        }

        // ...
    }

根据前面的分析,Track 的 ready queue 中,只保存了一个屏幕旋转动画的 transition。因此,这里可以获取到 transition,并调用 playTransition() 执行,如下

// Transitions.java

    private void playTransition(@NonNull ActiveTransition active) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
        
        for (int i = 0; i < mObservers.size(); ++i) {
            mObservers.get(i).onTransitionStarting(active.mToken);
        }

        // 初始化动画层级
        setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);

        // 在 request start transition 时,没有 TransitionHandler 对请求感兴趣
        // 因此,这里无法直接把 transition 发送给感兴趣的 TransitionHandler 来执行动画
        if (active.mHandler != null) {
            // ...
        }

        // 把 Transition 分发给所有 TransitionHandler,谁感兴趣,谁就处理
        active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
                active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
    }

执行 transition

  1. 初始化动画层级。每一个动画目标的 leash,都不会在原本的层级上执行动画。因此,在动画执行前,需要将动画目标的 leash,直接/间接地 reparent 到 transition root leash 上。参考【初始化动画层级
  2. 寻找 TransitionHandler,并让它执行动画。对于屏幕旋转动画来说,由 DefaultTransitionHandler 来执行。

初始化动画层级

// Transitions.java

    /**
     * Reparents all participants into a shared parent and orders them based on: the global transit
     * type, their transit mode, and their destination z-order.
     */
    // 参数 t 是 start transaction
    // finishT 是 finish transaction
    private static void setupAnimHierarchy(@NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
        
        // 获取 transition type,旋转动画是 TRANSIT_CHANGE
        final int type = info.getType();
        
        //只有 TRANSIT_OPEN,TRANSIT_TO_FRONT,TRANSIT_KEYGUARD_GOING_AWAY 才是 opending type
        final boolean isOpening = isOpeningType(type);
        // 只有 TRANSIT_CLOSE, TRANSIT_TO_BACK 才是 closing type
        final boolean isClosing = isClosingType(type);

        // 1.在 start transaction 中 show transition root leash
        for (int i = 0; i < info.getRootCount(); ++i) {
            t.show(info.getRoot(i).getLeash());
        }
        
        final int numChanges = info.getChanges().size();

        // 定义 Z 轴分割线的高度
        // 要做动画的目标,设置的 Z 序要高于分割线,否则低于分割线的 Z 序
        // Put animating stuff above this line and put static stuff below it.
        final int zSplitLine = numChanges + 1;

        // changes should be ordered top-to-bottom in z
        for (int i = numChanges - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            final SurfaceControl leash = change.getLeash();
            final int mode = change.getMode();


            // 2. 不能独立于 parent 做动画的动画目标,不执行层级初始化
            if (!TransitionInfo.isIndependent(change, info)) {
                continue;
            }


            // DisplayConten 动画目标是没有 parent 的
            boolean hasParent = change.getParent() != null;
            final int rootIdx = TransitionUtil.rootIndexFor(change, info);
            // 3. 如果动画目标没有 parent,在 start transaction 中,对动画目标的 leash 做了两件事
            if (!hasParent) {
                // reparent 到 transition root leash 上
                t.reparent(leash, info.getRoot(rootIdx).getLeash());
                // 设置相对位置坐标
                t.setPosition(leash,
                        change.getStartAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
                        change.getStartAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
            }

            final int layer;
            // Put all the OPEN/SHOW on top
            if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
                // ...
            } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                // ...
            } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
                // ...
            } else { // CHANGE or other
                if (isClosing || TransitionUtil.isOrderOnly(change)) {
                    // ...
                } else {
                    // Put above CLOSE mode.
                    layer = zSplitLine + numChanges - i;
                }
            }
            // 3. start transction 中,设置动画目标 leash 的 layer
            t.setLayer(leash, layer);
        }
    }

初始化动画层级

  1. 在 start transcation 中 show transition root leash。
  2. 对于不能独立于 parent 做动画的动画目标,是不需要执行动画层级初始化。根据前面的分析,对于屏幕旋转动画来说,只有 DisplayContent 能独立做动画。那么,接下来的两步,都是对 DisplayContent 的 leash 进行操作的。
  3. 由于动画目标 DisplayContent 没有 parent,因此,在 start transcation 中,把它的 leash reparent 到 transition root leash 上,并设置了相对坐标的位置。
  4. 在 start transaction 中,设置 DisplayContent leash 在 transition root leash 上的 layer,这个值是 5。

现在一切就绪了,马上就要开始执行动画了,我想读者一定与我一样,想知道最终的层级是怎样的?虽然,我在文章里把每个 surface 的层级都描述的很清楚,但是用图来表达或许会更清晰,如下

final.png

执行旋转动画

现在,一切就绪,由 DefaultTransitionHandler 来执行旋转动画,如下

// DefaultTransitionHandler.java

    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
            @NonNull SurfaceControl.Transaction startTransaction,
            @NonNull SurfaceControl.Transaction finishTransaction,
            @NonNull Transitions.TransitionFinishCallback finishCallback) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
                "start default transition animation, info = %s", info);
        
        // ...

        // animations 是用来保存需要执行的所有动画
        // 这个参数到处传递,用来收集所有要执行的动画
        final ArrayList<Animator> animations = new ArrayList<>();
        // DefaultTransitionHandler#mAnimations 是以 transition token 为 KEY,
        // 保存 transition 要执行的所有的动画
        mAnimations.put(transition, animations);

        // 任意一个动画执行完成的回调
        final Runnable onAnimFinish = () -> {
            
            // 检测是否所有动画都执行完毕
            if (!animations.isEmpty()) return;

            // 走到这里,代表所有动画都执行完毕

            // 根据 transition token 清理数据
            mAnimations.remove(transition);
            // 4. 回调到 Transition#onFinish()
            // 注意,参数都是为 null
            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
        };

        // ...

        // 1. 根据 surface layer 从低到高的顺序,依次处理动画目标
        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
            final TransitionInfo.Change change = info.getChanges().get(i);
            
            // ...

            final boolean isTask = change.getTaskInfo() != null;
            final int mode = change.getMode();
            boolean isSeamlessDisplayChange = false;

            // 1.1 处理 DiplayContent 动画目标
            if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
                if (info.getType() == TRANSIT_CHANGE) {
                    // 解析动画的类型
                    final int anim = getRotationAnimationHint(change, info, mDisplayController);
                    // 判断是否执行无缝旋转动画
                    isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
                    // 如果不执行无缝旋转动画,也不执行 jumpcut 动画,那么启动旋转动画
                    if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
                        startRotationAnimation(startTransaction, change, info, anim, animations,
                                onAnimFinish);
                        
                        // 标记正在执行旋转动画
                        isDisplayRotationAnimationStarted = true;
                        continue;
                    }
                } else {
                    // ...
                }
            }

            if (mode == TRANSIT_CHANGE) {
                // ...

                // No default animation for this, so just update bounds/position.
                final int rootIdx = TransitionUtil.rootIndexFor(change, info);
                startTransaction.setPosition(change.getLeash(),
                        change.getEndAbsBounds().left - info.getRoot(rootIdx).getOffset().x,
                        change.getEndAbsBounds().top - info.getRoot(rootIdx).getOffset().y);
                
                // Seamless display transition doesn't need to animate.
                if (isSeamlessDisplayChange) continue;
                
                // 1.2 处理 Task 动画目标
                if (isTask || (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
                        && !change.hasFlags(FLAG_FILLS_TASK))) {
                    // Update Task and embedded split window crop bounds, otherwise we may see crop
                    // on previous bounds during the rotation animation.
                    startTransaction.setWindowCrop(change.getLeash(),
                            change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
                }

                if (change.getParent() == null
                        && change.getStartRotation() != change.getEndRotation()) {
                   // ...
                }
            }

            // ...

            // Don't animate anything that isn't independent.
            if (!TransitionInfo.isIndependent(change, info)) continue;

            // ...
        }

        // ...

        // 2. apply start transaction
        startTransaction.apply();


        // 3. 在 Handler 线程中执行动画
        mAnimExecutor.execute(() -> {
            for (int i = 0; i < animations.size(); ++i) {
                animations.get(i).start();
            }
        });

        // ...

        // run finish now in-case there are no animations
        // 立即执行一次动画完成的回调,防止当前没有动画
        onAnimFinish.run();
        return true;
    }

DefaultTransitionHandler 执行动画的过程

  1. 根据 surface layer 从低到高的顺序,依次处理动画目标。那么,对于屏幕旋转动画来说,它的动画目标的处理顺序是 DisplayContent -> TaskDisplayArea -> Task。
    • 对于 DisplayContent 动画目标,先解析它的旋转动画类型,由于并没有指定特别的旋转动画类型,因此使用的是默认的旋转动画类型 ROTATION_ANIMATION_ROTATE ,之后会创建旋转动画。参考【创建并收集旋转动画]
    • 对于 Task 动画目标,就是设置一个裁剪区域,不过对于屏幕旋转动画来说,没什么用。
  2. apply start transaction。start transaction 包含了很多操作,例如, WMCore 中收集的 sync tranction,WMShell 中对 leash 状态的初始化,以及对 leash 层级的初始化,等等。另外,WMCore 会在这个之后,收到 merged sync transaction 的提交回调。
  3. 在 Handler 线程中执行收集的动画。
  4. 当所有动画执行完成后,会执行回调,最终调用 Transition#onTransitionFinished() ,并且传递的两个参数都是为 null。
创建并收集旋转动画
// DefaultTransitionHandler.java

    // 参数 animations 是根据 transition token 从 DefaultTransitionHandler#mAnimations 获取的 VALUE
    // onAnimFinish 是 startAnimation() 中创建的一个回调 onAnimFinish
    private void startRotationAnimation(SurfaceControl.Transaction startTransaction,
            TransitionInfo.Change change, TransitionInfo info, int animHint,
            ArrayList<Animator> animations, Runnable onAnimFinish) {
        final int rootIdx = TransitionUtil.rootIndexFor(change, info);

        // 1. 创建 ScreenRotationAnimation
        final ScreenRotationAnimation anim = new ScreenRotationAnimation(mContext, mSurfaceSession,
                mTransactionPool, startTransaction, change, info.getRoot(rootIdx).getLeash(),
                animHint);
        
        // The rotation animation may consist of 3 animations: fade-out screenshot, fade-in real
        // content, and background color. The item of "animGroup" will be removed if the sub
        // animation is finished. Then if the list becomes empty, the rotation animation is done.
        final ArrayList<Animator> animGroup = new ArrayList<>(3);
        final ArrayList<Animator> animGroupStore = new ArrayList<>(3);

        // 每一个动画执行完成的回调
        final Runnable finishCallback = () -> {
            if (!animGroup.isEmpty()) return;
            anim.kill();
            // 从 DefaultTransitionHandler#mAnimations 的 VALUE 中移除 transition 的所有动画
            animations.removeAll(animGroupStore);
            // 回调到 startAnimation() 中创建的一个回调 onAnimFinish
            onAnimFinish.run();
        };

        // 2. ScreenRotationAnimation 构建动画,所有构建的动画都保存到参数 animGroup 中
        anim.buildAnimation(animGroup, finishCallback, mTransitionAnimationScaleSetting,
                mMainExecutor);

        for (int i = animGroup.size() - 1; i >= 0; i--) {
            final Animator animator = animGroup.get(i);
            // animGroupStore 和 animations 都保存 transition 要执行的所有动画
            animGroupStore.add(animator);
            animations.add(animator);
        }
    }

屏幕旋转动画的构建,是由 ScreenRotationAnimation 完成,首先看下它的构造函数

// ScreenRotationAnimation.java

    // change 是 DispayContent 的
    // t 是 start transaction
    // animHint 动画类型是 ROTATION_ANIMATION_ROTATE
    ScreenRotationAnimation(Context context, SurfaceSession session, TransactionPool pool,
            Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
        mContext = context;
        mTransactionPool = pool;
        mAnimHint = animHint;

        mSurfaceControl = change.getLeash();
        mStartWidth = change.getStartAbsBounds().width();
        mStartHeight = change.getStartAbsBounds().height();
        mEndWidth = change.getEndAbsBounds().width();
        mEndHeight = change.getEndAbsBounds().height();
        mStartRotation = change.getStartRotation();
        mEndRotation = change.getEndRotation();

        // 1. 创建截图层的动画 leash
        mAnimLeash = new SurfaceControl.Builder(session)
                // 截图层 leash 的 parent 是 transition root leash
                .setParent(rootLeash)
                .setEffectLayer()
                .setCallsite("ShellRotationAnimation")
                .setName("Animation leash of screenshot rotation")
                .build();

        try {
            if (change.getSnapshot() != null) {
                mScreenshotLayer = change.getSnapshot();
                // 1.1 把截图层 surface reparent 到 截图层 leash 上
                t.reparent(mScreenshotLayer, mAnimLeash);
                mStartLuma = change.getSnapshotLuma();
            } else {
                // ...
            }

            // 1.2 截图层的动画 leash 显示在最上层
            // layer 为 10000 * 200 + 10000
            t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
            // show 截图层 leash
            t.show(mAnimLeash);
            // 这是对真是图层 DisplayContent leash 进行裁剪
            // Crop the real content in case it contains a larger child layer, e.g. wallpaper.
            t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));

            // 动画方式为 ROTATION_ANIMATION_CROSSFADE 和 ROTATION_ANIMATION_JUMPCUT 才是自定义动画
            if (!isCustomRotate()) {
                // 2. 创建一个背景色的 surface
                mBackColorSurface = new SurfaceControl.Builder(session)
                        // 挂在 transition root leash 下
                        .setParent(rootLeash)
                        .setColorLayer()
                        .setOpaque(true)
                        .setCallsite("ShellRotationAnimation")
                        .setName("BackColorSurface")
                        .build();
                // layer 为 -1
                t.setLayer(mBackColorSurface, -1);
                // 设置背景色
                // 第二个参数是一个数组,分别代表 r,g,b 的值
                t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
                // start transaction show 背景色 surface
                t.show(mBackColorSurface);
            }

        } catch (Surface.OutOfResourcesException e) {
            Slog.w(TAG, "Unable to allocate freeze surface", e);
        }

        // 3. 在 start transaction 中,设置截图层 surface 的转换矩阵
        // 使截图层 surface 在新的旋转方向上,能正确显示
        setScreenshotTransform(t);

        // 5. 立即 apply start transaction
        t.apply();
    }

ScreenRotationAnimation 的构造函数,做了很多事情

  1. 创建并显示一个截图层的动画 leash,同时把它挂在 transition root leash 之下,然后把截图层的 surface 挂在它之下,最后把它的 layer 设置的非常高,以保证截图层在动画执行时,显示在最上层。
  2. 创建一个背景色的 surface,并挂在 transition root leash 下,layer 设置为 -1。这个背景色一般是黑色,所以在执行旋转动画的时候,会看到要给黑色的背景。
  3. 在 start transaction 中,为截图层 surface 设置一个转换矩阵。由于执行动画的时候,屏幕的旋转方向会立即改变,但是截图层是一个竖屏的大小,如何能正确显示在横屏坐标系上呢?那就需要设置一个转换矩阵。
  4. 立即 apply start tranaction。 这里我不是很明白,为何这么迫不及待 apply start tranaction ?通常 start tranaction 都是在即将执行动画之前 apply 的。

来看下,如何计算截图层的转换矩阵

// ScreenRotationAnimation.java

    private void setScreenshotTransform(SurfaceControl.Transaction t) {
        if (mScreenshotLayer == null) {
            return;
        }

        // 计算转换矩阵
        final Matrix matrix = new Matrix();
        final int delta = deltaRotation(mEndRotation, mStartRotation);
        if (delta != 0) {
            // Compute the transformation matrix that must be applied to the snapshot to make it
            // stay in the same original position with the current screen rotation.
            switch (delta) {
                // ...

                case Surface.ROTATION_270:
                    matrix.setRotate(270, 0, 0);
                    matrix.postTranslate(0, mStartWidth);
                    break;
            }
        } else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
                && (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
            // ...
        }

        // 从转换矩阵中获取偏移坐标
        matrix.getValues(mTmpFloats);
        float x = mTmpFloats[Matrix.MTRANS_X];
        float y = mTmpFloats[Matrix.MTRANS_Y];

        // start transaction 中,给截图层设置偏移、缩放、错切
        // 其实主要就是偏移和缩放
        t.setPosition(mScreenshotLayer, x, y);
        t.setMatrix(mScreenshotLayer,
                mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
                mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
    }

就是计算一个 Matrix,然后在 start transcation 中,对截图层设置偏移和缩放。至于 Matrix 的原理,这就是 app 开发的功底,读者自行去了解,这里不做过多分析。

ScreenRotationAnimation 构造函数并没有创建动画,这个工作是由他的 buildAnimation() 来完成的,如下

// ScreenRotationAnimation.java

    boolean buildAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, float animationScale,
            @NonNull ShellExecutor mainExecutor) {
        if (mScreenshotLayer == null) {
            return false;
        }

        // 自定义旋转动画只有两种 ROTATION_ANIMATION_CROSSFADE 和 ROTATION_ANIMATION_JUMPCUT
        final boolean customRotate = isCustomRotate();
        if (customRotate) {
            // ...
        } else {
            // Figure out how the screen has moved from the original rotation.
            int delta = deltaRotation(mEndRotation, mStartRotation);
            switch (delta) { /* Counter-Clockwise Rotations */
                // ...

                case Surface.ROTATION_270:
                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
                            R.anim.screen_rotate_minus_90_exit);
                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
                            R.anim.screen_rotate_minus_90_enter);
                    break;
            }
        }

        // 1.构造旋转退出动画
        // 截图层执行的是旋转退出动画
        mRotateExitAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
        mRotateExitAnimation.restrictDuration(MAX_ANIMATION_DURATION);
        mRotateExitAnimation.scaleCurrentDuration(animationScale);
        
        // 2.构造旋转进入动画
        // DisplayContent leash 执行的是旋转进入动画
        mRotateEnterAnimation.initialize(mEndWidth, mEndHeight, mStartWidth, mStartHeight);
        mRotateEnterAnimation.restrictDuration(MAX_ANIMATION_DURATION);
        mRotateEnterAnimation.scaleCurrentDuration(animationScale);

        if (customRotate) {
            // ...
        } else {
            // 3.构建 DisplayContent 的旋转动画
            startDisplayRotation(animations, finishCallback, mainExecutor);
            // 4.构建截图层的旋转动画
            startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
        }

        return true;
    }

构建动画的过程非常清晰,首先构造两个动画资源,一个是退出动画,一个是进入动画。然后,利用进入动画资源, DisplayContent leash 构建一个进入动画,利用退出动画资源,为截图层构建一个退出动画。

DisplayContent 的动画代表真实图层的动画,截图层的动画代表假的图层的动画,它们的构建过程如下

// ScreenRotationAnimation.java

    // DisplayContent 的旋转动画
    private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
        // mSurfaceControl 是 DisplayContent leash
        // DisplayContent 旋转动画使用的进入动画 mRotateEnterAnimation
        buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
                mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                null /* clipRect */);
    }

    // 截图层的旋转动画
    private void startScreenshotRotationAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
        // 截图层动画操作的截图层动画 leash,也就是 mAnimLeash
        // 截图层动画使用的是退出动画 mRotateExitAnimation
        buildSurfaceAnimation(animations, mRotateExitAnimation, mAnimLeash, finishCallback,
                mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
                null /* clipRect */);
    }


    static void buildSurfaceAnimation(@NonNull ArrayList<Animator> animations,
            @NonNull Animation anim, @NonNull SurfaceControl leash,
            @NonNull Runnable finishCallback, @NonNull TransactionPool pool,
            @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius,
            @Nullable Rect clipRect) {

        // 从池中获取一个 Transaction
        final SurfaceControl.Transaction transaction = pool.acquire();

        // 1.构建 ValueAnimator,代表一个动画
        final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
        final Transformation transformation = new Transformation();
        final float[] matrix = new float[9];
        // Animation length is already expected to be scaled.
        va.overrideDurationScale(1.0f);
        va.setDuration(anim.computeDurationHint());

        // 3.ValueAnimator 监听每一帧的绘制,对 surface 执行动画
        final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
            final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());

            // 操作 surface
            applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
                    position, cornerRadius, clipRect);
        };
        va.addUpdateListener(updateListener);

        // 构建一个Runnable,作为动画执行完成的回调
        final Runnable finisher = () -> {
            applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
                    position, cornerRadius, clipRect);

            pool.release(transaction);
            mainExecutor.execute(() -> {
                // 从集合中移除已经完成的动画
                animations.remove(va);
                // 执行回调
                finishCallback.run();
            });
        };

        va.addListener(new AnimatorListenerAdapter() {
            private boolean mFinished = false;

            @Override
            public void onAnimationEnd(Animator animation) {
                onFinish();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                onFinish();
            }

            private void onFinish() {
                if (mFinished) return;
                mFinished = true;
                // 4. 动画执行完成,执行回调的 Runnable
                finisher.run();
                va.removeUpdateListener(updateListener);
            }
        });

        // 2.保存构建的 ValueAnimator
        animations.add(va);
    }

所谓的构建动画,其实就是创建 ValueAnimator 来操作 surface。这里我还是要强调一下,真实图层的动画,操作的 DisplayContent leash,具体是 DisplayContent surface 下挂载的 WindowedMagnification:0:31。而截图层动画,并不是直接操作截图层 surface,而是截图层的动画 leash。

现在只是把创建并收集 ValueAnimator,等会就会执行。当 ValueAnimator 执行的时候,会不断操作 surface,如下

// DefaultTransitionHandler.java

    private static void applyTransformation(long time, SurfaceControl.Transaction t,
            SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix,
            Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
        tmpTransformation.clear();

        // 获取动画的转换信息
        anim.getTransformation(time, tmpTransformation);

        // position 为 null
        if (position != null) {
            // ...
        }

        // 在 transaction 中设置转换矩阵,透明度
        // 注意,这个 transcation 不是 start transaction ,也不是 finish transaction
        // 它是从池中获取的一个 Transaction
        t.setMatrix(leash, tmpTransformation.getMatrix(), matrix);
        t.setAlpha(leash, tmpTransformation.getAlpha());

        // 参数 immutableClipRect 此时为 null
        final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
        Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE);
        if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
            // ...
        }

        // 参数 cornerRadius 此时为 0
        if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) {
            // ...
        }

        // transactihon 中设置 Vsync id
        t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());

        // apply transaction 之时,就是操作 surface 之时
        t.apply();
    }

终于,我们看到动画的庐山真面目。所谓的窗口动画,其实就是不断操作窗口 surface 的一个过程,例如,设置转换矩阵(偏移,缩放),设置透明度,等等。

动画执行完成

根据前面的分析,当所有动画执行完成后,最终会调用到 Transitions#onFinish()

// Transitions.java

    private void onFinish(ActiveTransition active,
            @Nullable WindowContainerTransaction wct,
            @Nullable WindowContainerTransactionCallback wctCB) {

        // 获取当前正在执行的 transition 的 track
        final Track track = mTracks.get(active.getTrack());
        if (track.mActiveTransition != active) {
            Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
                    + " a handler didn't properly deal with a merge. " + active,
                    new RuntimeException());
            return;
        }
        // 1. 重置 Track#mActiveTransition
        track.mActiveTransition = null;

        for (int i = 0; i < mObservers.size(); ++i) {
            mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
        }

        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
                + "(aborted=%b), notifying core %s", active.mAborted, active);
        
        // 2.清理 start transaction
        if (active.mStartT != null) {
            // Applied by now, so clear immediately to remove any references. Do not set to null
            // yet, though, since nullness is used later to disambiguate malformed transitions.
            active.mStartT.clear();
        }
        
        // 3. apply finish transaction
        SurfaceControl.Transaction fullFinish = active.mFinishT;
        // 不考虑 merged transition 的情况
        if (active.mMerged != null) {
            // ...
        }
        if (fullFinish != null) {
            fullFinish.apply();
        }

        // 4. 释放 TransitionInfo 中引用的 surface
        // 也就是 截图层 surface 以及 transition root leash surface
        releaseSurfaces(active.mInfo);

        // 5.通知 WMCore 动画完成
        // 参数 wct 和 wctCB 都是 null
        mOrganizer.finishTransition(active.mToken, wct, wctCB);

        if (active.mMerged != null) {
            // ...
        }

        // 6. 继续处理 ready queue 中下一个 transition
        processReadyQueue(track);
    }

WMShell 处理 finish transition

  1. 重置 Track#mActiveTransition,表明 Tack 中没有正在执行的动画。
  2. clear start tranction。从注释中可以了解到,是为了清理一些引用(surface 引用?)。
  3. apply finish transaction。finish transaction 保存的是一些针对动画目标 leash 的重置操作,例如 reparent 到原本的 parent surface 之下,更新 layer 等等。
  4. 释放 TransitionInfo 中引用的截图层 surface,以及 transtion root leash surface。注意,这里 WMShell 中的释放引用的操作,WMCore 中其实也有释放引用的操作。
  5. 通知 WMCore 动画执行完成。
  6. 继续处理 Track 的 ready queue 中的 transition。

WMCore 处理 finish transition

动画执行完成后,WMShell 会通知 WMCore,最终会执行 Transition#finishTransition(),如下

// Transition.java

    void finishTransition() {
        if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
            asyncTraceEnd(System.identityHashCode(this));
        }

        // 记录 finish transition 的日志
        mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
        mController.mLoggerHandler.post(mLogger::logOnFinish);
        mController.mTransitionTracer.logFinishedTransition(this);


        // 1.清理 ST,FT, CT
        if (mStartTransaction != null) mStartTransaction.close();
        if (mFinishTransaction != null) mFinishTransaction.close();
        mStartTransaction = mFinishTransaction = null;
        if (mCleanupTransaction != null) {
            mCleanupTransaction.apply();
            mCleanupTransaction = null;
        }

        if (mState < STATE_PLAYING) {
            throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
        }

        // 标记正在 finishing 的 transition
        mController.mFinishingTransition = this;

        if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
            // ...
        }

        boolean hasParticipatedDisplay = false;
        boolean hasVisibleTransientLaunch = false;
        boolean enterAutoPip = false;
        boolean committedSomeInvisible = false;
        // Commit all going-invisible containers
        for (int i = 0; i < mParticipants.size(); ++i) {
            final WindowContainer<?> participant = mParticipants.valueAt(i);

            // ...

            // 旋转动画有 DisplayContent 参与
            if (participant.asDisplayContent() != null) {
                hasParticipatedDisplay = true;
                continue;
            }

            // ...

        // ...

        for (int i = 0; i < mTargetDisplays.size(); ++i) {
            final DisplayContent dc = mTargetDisplays.get(i);
            final AsyncRotationController asyncRotationController = dc.getAsyncRotationController();
            // 2.通知 AsyncRotationController 动画完成了
            if (asyncRotationController != null && containsChangeFor(dc, mTargets)) {
                asyncRotationController.onTransitionFinished();
            }

            // ...
        }
       
        // ...

        // 3.Transition 状态切换到 STATE_FINISHED
        mState = STATE_FINISHED;

        // 4. 执行推迟的旋转动画
        if (hasParticipatedDisplay && !mController.useShellTransitionsRotation()) {
            mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
                    false /* forceRelayout */);
        }
        
        // ...

        // finishing transition 已经处理完成
        mController.mFinishingTransition = null;
    }

其实这里的代码量非常大,但是对于屏幕旋转动画来说,只有最后两步有点价值,但是本文不会对这两步进行分析。其他几步,看看就行。

结束

写屏幕旋转动画的文章,对于我的时间和精力来说,消耗巨大,让我非常吃不消,甚至让我想放弃。但是,我本着精益求精的精神,不断斟酌每一处细节,力求让自己理解更透彻,因此才坚持下来。

说个比较嚣张的话,我的屏幕旋转动画系列的文章,是全网独一无二的,可能只有从培训机构,花钱才能获取到。但是,要消化掉这些内容,也不是那么容易,需要读者有扎实的基本功,还要有强大的毅力。

OK,为了分析的严谨性,当我斟酌到新的细节时,我会不定期更新这系列的文章。祝各位读者看的愉快,看得忘我,但是不要忘记点赞收藏,bye~