Android U WMS : 屏幕旋转动画(4) 准备动画数据

804 阅读20分钟

BLASTSyncEngine#onSurfacePlacement

根据上一篇文章的分析,当 MainActivity 的窗口绘制完成后,执行了窗口大刷新,如下

// RootWindowContainer.java

void performSurfacePlacementNoTrace() {
    // ...
    
    mWmService.openSurfaceTransaction();
    try {
        // 1. 处于所有 DisplayContent 的 surface change 
        applySurfaceChangesTransaction();
    } catch (RuntimeException e) {
        Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
    } finally {
        mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
        // ...
    }

    // ...
    
    // 2.  SyncGroup 同步完成检测
    mWmService.mSyncEngine.onSurfacePlacement();
    
    // ...
}

第1步中,通过 WindowState 的 sync transcation, 显示了 MainActivity 的窗口 surface。

第2步中,由于现在窗口已经绘制完成,并且状态也已经完成切换,因此,是时候检测 SyncGroup 的根成员,是否完成同步,如下

// BLASTSyncEngine.java

void onSurfacePlacement() {
    if (mActiveSyncs.isEmpty()) return;
    mTmpFinishQueue.addAll(mActiveSyncs);

    // 为了防止 SyncGroup 依赖循环,这里计算了最大的循环次数,看下面注释
    // There shouldn't be any dependency cycles or duplicates, but add an upper-bound just
    // in case. Assuming absolute worst case, each visit will try and revisit everything
    // before it, so n + (n-1) + (n-2) ... = (n+1)*n/2
    int visitBounds = ((mActiveSyncs.size() + 1) * mActiveSyncs.size()) / 2;

    while (!mTmpFinishQueue.isEmpty()) {
        if (visitBounds <= 0) {
            Slog.e(TAG, "Trying to finish more syncs than theoretically possible. This "
                    + "should never happen. Most likely a dependency cycle wasn't detected.");
        }
        --visitBounds;
        
        // 获取 SyncGroup
        final SyncGroup group = mTmpFinishQueue.remove(0);
        final int grpIdx = mActiveSyncs.indexOf(group);
        // Skip if it's already finished:
        if (grpIdx < 0) continue;

        // 检测 SyncGroup 同步是否完成
        if (!group.tryFinish()) continue;

        // 这里是处理依赖方 SyncGroup
        int insertAt = 0;
        for (int i = 0; i < mActiveSyncs.size(); ++i) {
            // ...
        }
    }
}

对于当前分析的屏幕旋转案例来讲,不存在 SyncGroup 相互依赖的情况。因此,这里就是检测单个 SyncGroup 是否同步完成,如下

// BLASTSyncEngine.java

// SyncGroup
private boolean tryFinish() {
    
    // 1. SyncGroup 一定要处于 ready 状态
    if (!mReady) return false;

    ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: onSurfacePlacement checking %s",
            mSyncId, mRootMembers);

    // 如果当前 SyncGroup 依赖于其他 SyncGroup,那么必须等待依赖的 SyncGroup 先完成同步
    if (!mDependencies.isEmpty()) {
        
    }

    // 2. 检测每一个根成员是否完成同步
    for (int i = mRootMembers.size() - 1; i >= 0; --i) {
        final WindowContainer wc = mRootMembers.valueAt(i);
        if (!wc.isSyncFinished(this)) {
            ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d:  Unfinished container: %s",
                    mSyncId, wc);
            // 如果任何一个跟成员没有完成同步,代表 SyncGroup 没有完成同步
            return false;
        }
    }

    // 3.根成员都完成同步,那么执行 finish 流程
    finishNow();
    return true;
}

检测 SyncGroup 是否完成同步

  1. SyncGroup 必须处于 ready 状态。
  2. 检测 SyncGroup#mRootMembers 保存的每一个跟成员,是否完成同步。如果有任何一个跟成员没有完成同步,那么 SyncGroup 就没有同步完成。
  3. 如果每一个根成员都同步完成,那么表示 SyncGroup 同步完成,执行 finishNow()。

根据 Android U WMS : 屏幕旋转动画(1)#更新 transition ready 的分析,现在 SyncGroup 已经处于 ready 状态。

根据 Android U WMS : 屏幕旋转动画(1)#请求 change transitionAndroid U WMS : 屏幕旋转动画(1)#DisplayContent 更新配置 的分析,此时的所有根成员有 DisplayContent 和 HelloWorld Task。

检测同步完成

SyncGroup 检测根成员是否同步完成,其实就是递归地检测根成员及其 children 是否同步完成,而最终的决定性因素是可见窗口 WindowState 是否同步完成。对于当前分析的屏幕旋转动画来说,其实就是检测 MainActivity 的窗口 WindowState 是否同步完成,如下

// WindowState.java

boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
    // 请求不可见,或者完全透明,那么代表同步完成
    if (!isVisibleRequested() || isFullyTransparent()) {
        // Don't wait for invisible windows. However, we don't alter the state in case the
        // window becomes visible while the sync group is still active.
        return true;
    }
    
    if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mWinAnimator.mDrawState == HAS_DRAWN
            && !mRedrawForSyncReported && !mWmService.mResizingWindows.contains(this)) {
        // Complete the sync state immediately for a drawn window that doesn't need to redraw.
        onSyncFinishedDrawing();
    }

    // 通过基类函数检测同步完成
    return super.isSyncFinished(group);
}

窗口就是可见的,并且也不是透明的,因此使用基类函数决来检测是否同步完成

// WindowContainer.java

boolean isSyncFinished(BLASTSyncEngine.SyncGroup group) {
    // 1.必须请求可见
    if (!isVisibleRequested()) {
        return true;
    }

    // 2. 同步状态必须为 SYNC_STATE_READY
    if (mSyncState == SYNC_STATE_NONE) {
        prepareSync();
    }
    if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW) {
        return false;
    }
    
    
    // 3. 从上到下遍历 children,检测 child 是否同步完成
    // 对于 WindowState 来说,就是子窗口
    for (int i = mChildren.size() - 1; i >= 0; --i) {
        // ...
    }
    
    // 默认同步完成
    return true;
}

首先,窗口是可见的。其次,根据 Android U WMS : 屏幕旋转动画(3)#窗口绘制完成 的分析,MainActivity 的窗口 WindowState 的同步状态已经切换到 SYNC_STATE_READY。最后,WindowState 没有子窗口。因此,MainActivity 的窗口 WindowState 已经完成同步。

因此,SyncGroup 此时是已经同步完成。 其实,从更广义的角度来讲,SyncGroup 检测同步完成,其实就是检测可见窗口是否重绘完成。

同步完成

SyncGroup 的根成员都同步完成以后,就会执行 SyncGroup#finishNow()

private void finishNow() {
    // ...
    
    // 1. 创建一个 Transaction,收集 sync transaction
    SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
    // WC 被移除时,如果 parent 不处于同步状态,那么 WC 的 sync transcation 被合并到 mOrphanTransaction
    if (mOrphanTransaction != null) {
        merged.merge(mOrphanTransaction);
    }
    for (WindowContainer wc : mRootMembers) {
        wc.finishSync(merged, this, false /* cancel */);
    }


    // 2. 收集需要等待 sync transaction 提交的 WC
    final ArraySet<WindowContainer> wcAwaitingCommit = new ArraySet<>();
    for (WindowContainer wc : mRootMembers) {
        wc.waitForSyncTransactionCommit(wcAwaitingCommit);
    }

    class CommitCallback implements Runnable {
        // Can run a second time if the action completes after the timeout.
        boolean ran = false;
        
        // 3.1 处理 merged sync transaction 的提交
        public void onCommitted(SurfaceControl.Transaction t) {
            synchronized (mWm.mGlobalLock) {
                if (ran) {
                    return;
                }
                mHandler.removeCallbacks(this);
                ran = true;
                
                // 通过一个 Transaction,收集那些等待 merged sync transaction 
                // 提交的 WC 的 sync transcation ,然后 apply
                for (WindowContainer wc : wcAwaitingCommit) {
                    wc.onSyncTransactionCommitted(t);
                }
                t.apply();
                wcAwaitingCommit.clear();
            }
        }
        
        // 3.2 处理 merged sync transaction 提交超时
        public void run() {
            // ...
        }
    };

    CommitCallback callback = new CommitCallback();
    // 3. 通过一个回调,监听 merged sync transaction 的提交
    merged.addTransactionCommittedListener(Runnable::run,
            () -> callback.onCommitted(new SurfaceControl.Transaction()));
    // merged sync transaction 的提交有超时限制
    mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
    
    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
    // 4. 通知监听者 transaction ready
    // 这个监听者就是 Transition
    mListener.onTransactionReady(mSyncId, merged);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    
    // 既然 SyncGroup 已经 finish 了,从 mActiveSyncs 移除 SyncGroup
    mActiveSyncs.remove(this);

    // 移除 SyncGroup 收集 WC 的超时回调
    mHandler.removeCallbacks(mOnTimeout);
    
    if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
        // ...
    }
    
    for (int i = mOnIdleListeners.size() - 1; i >= 0; --i) {
        // ...
    }
}

SyncGroup 同步完成,主要做了以下几件事

  1. 创建一个 Transaction,主要是收集根成员及其处于同步状态的 children 的 sync transcation。参考【收集 sync transaction
  2. 把根成员及其所有 children 收集起来,它们需要等待 sync transaction 的提交。参考【收集等待 merged sync transcation 提交的 WC
  3. 通过一个回调,监听 merged sync transcation 的提交。其实 merged sync transcation 是在 WMShell 侧 apply 的,WMCore 收到提交回调后,会把那些等待 merged sync transaction 提交的 WC 的 sync transcation 收集起来,然后 apply。其实就是同步提交 WMShell 和 WMCore 两侧的 sync transaction。
  4. 通知 Transition transaction ready。这个 transaction 就是 merged sync transaction。

收集 sync transcation

SyncGroup 收集根成员的 sync transcation,是在根成员的完成同步的函数 finishSync() 中进行的。对于屏幕旋转动画来说,根成员只有 DisplayContent 和 HelloWorld Task,但是它们都没有复写 finishSync(),使用的是基类 WindowContainer 函数,如下

// WindowContainer.java

void finishSync(Transaction outMergedTransaction, BLASTSyncEngine.SyncGroup group,
        boolean cancel) {
    // 1.WC 不处于同步状态,不需要完成同步
    if (mSyncState == SYNC_STATE_NONE) return;

    // 2. SyncGroup 不相同,也不能完成同步
    final BLASTSyncEngine.SyncGroup syncGroup = getSyncGroup();
    if (syncGroup != null && group != syncGroup) return;

    ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "finishSync cancel=%b for %s", cancel, this);
    
    // 3.合并 WC 的 sync transaction
    outMergedTransaction.merge(mSyncTransaction);

    // 4.递归地合并 WC children 的 sync transaction
    for (int i = mChildren.size() - 1; i >= 0; --i) {
        mChildren.get(i).finishSync(outMergedTransaction, group, cancel);
    }

    // 此时 cancel 为 false
    if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);

    // 5.重置同步的数据
    mSyncState = SYNC_STATE_NONE;
    mSyncMethodOverride = BLASTSyncEngine.METHOD_UNDEFINED;
    mSyncGroup = null;
}

基类的同步完成的逻辑,就是递归收集 当前WC 及其 children 的 sync transaction,但是有个前提,被收集 sync transaction 的 WC 必须处于同步状态。

目前,DisplayContent 下,只有做异步旋转的 WC ,不处于同步状态,因此,除了异步旋转的 WC,其他的 WC 的 sync transaction 都会被收集。

收集等待 merged sync transcation 提交的 WC

SyncGroup 调用根成员的 waitForSyncTransactionCommit() 来收集等待 merged sync transaction 提交的 WC。根成员 DisplayContent 和 Task 都没有复写这个函数,使用基类 WindowContainer 的函数,如下

// WindowContainer.java

void waitForSyncTransactionCommit(ArraySet<WindowContainer> wcAwaitingCommit) {
    if (wcAwaitingCommit.contains(this)) {
        return;
    }

    // 这个变量不为0,代表 WC 正在等待 sync transaction 提交
    mSyncTransactionCommitCallbackDepth++;

    // 递归地收集 WC 及其 children
    wcAwaitingCommit.add(this);
    for (int i = mChildren.size() - 1; i >= 0; --i) {
        mChildren.get(i).waitForSyncTransactionCommit(wcAwaitingCommit);
    }
}

非常简单,就是递归收集 WC 及其 children,没有任何限制。因此,做异步旋转的 WC 也会被收集。

transaction ready

SyncGroup 收集完 sync transcation 后,通知 Transition transaction ready,并把这个 merged sync transcation 传递给 Transition,如下

// Transition.java

// 参数 transaction 是 SyncGroup 收集的 sync tranasction
public void onTransactionReady(int syncId, SurfaceControl.Transaction transaction) {
    // ...

    // Transition 状态切换到 STATE_PLAYING
    mState = STATE_PLAYING;
    
    // mStartTransaction 保存的是 merged sync transaction
    // 现在它的名字叫 start transaction
    mStartTransaction = transaction;
    
    mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();

    // ...

    // 1. 计算所有做动画的目标
    mTargets = calculateTargets(mParticipants, mChanges);

    // ...

    // 2. TransitionInfo 收集动画信息
    final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
    info.setDebugId(mSyncId);
    // 给 Transition 和 TransitionInfo 设置 track id
    mController.assignTrack(this, info);

    // 3.把正在收集的 Transition 保存到 mPlayingTransitions,并且重置 mCollectingTransition
    mController.moveToPlaying(this);

    // 根据 transition root leash 所在的 DisplayContent,重新填充 mTargetDisplays
    // Repopulate the displays based on the resolved targets.
    mTargetDisplays.clear();
    for (int i = 0; i < info.getRootCount(); ++i) {
        final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent(
                info.getRoot(i).getDisplayId());
        mTargetDisplays.add(dc);
    }

    // ...


    for (int i = 0; i < mTargetDisplays.size(); ++i) {
        final DisplayContent dc = mTargetDisplays.get(i);
        final AsyncRotationController controller = dc.getAsyncRotationController();
        // 4.如果 DisplayContent 发生了改变,异步旋转在 start transaction 做一些初始化操作
        if (controller != null && containsChangeFor(dc, mTargets)) {
            controller.setupStartTransaction(transaction);
        }
    }

    // 4.构建 finish transaction
    buildFinishTransaction(mFinishTransaction, info);

    // 5.构建 cleanup transaction
    mCleanupTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
    buildCleanupTransaction(mCleanupTransaction, info);
    
    if (mController.getTransitionPlayer() != null && mIsPlayerEnabled) {
        mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
        try {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "Calling onTransitionReady: %s", info);
            mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos();
            mLogger.mInfo = info;
            // 6. 通知 WMShell 开始执行动画
            mController.getTransitionPlayer().onTransitionReady(
                    mToken, info, transaction, mFinishTransaction);
            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
            }
        } catch (RemoteException e) {}
        
        // ...
    } else {}
    
    // ...
}

Transition 处理 transaction ready

  1. 计算所有要做动画的目标。并不是所有动画的参与者都需要做动画,因此需要计算最终需要做动画的目标。
  2. 计算动画的信息,保存到 TransitionInfo。由于动画是在 WMShell 中执行,因此需要把动画信息转化成跨进程传输的数据,然后发送给 WMShell。参考【计算动画目标
  3. Transition 马上要执行了,因此重置了 Transition#mCollectingTransition,然后把 Transition 保存到 Transition#mPlayingTransition 集合中。
  4. 构建 finish transaction ,用于在动画执行完成后,执行复位操作。参考【构建 finish transaction
  5. 构建 cleanup transaction,用于清理 transition root leash 以及截图层 的 surface。其实,这些工作在 finish transcation 执行过,只不过,为了以防万一,再执行了一遍。
  6. 通知 WMShell 开始执行动画。传递了 TransitionInfo, start transaction(即 merged sync transcation),finish transcation。

计算动画目标

在分析动画目标的计算之前,对于当前分析的屏幕旋转动画,首先得知道,数据结构到底保存了哪些数据。根据 Android U WMS : 屏幕旋转动画(1) 的分析可知

  1. Transition#mParticipants 只保存了 DisplayContent 和 HelloWorld Task。
  2. Transition#mChanges 保存了 DisplayContent, TaskDisplayArea,HelloWorld Task 在改变前 的信息。

知道这些数据之后,再来分析屏幕旋转动画的动画目标的计算,如下

// Transition.java

static ArrayList<ChangeInfo> calculateTargets(ArraySet<WindowContainer> participants,
        ArrayMap<WindowContainer, ChangeInfo> changes) {
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
            "Start calculating TransitionInfo based on participants: %s", participants);

    // Targets 用于保存动画目标的信息
    final Targets targets = new Targets();
    
    // 1. Targets 保存有效的参与者作为动画目标
    for (int i = participants.size() - 1; i >= 0; --i) {
        final WindowContainer<?> wc = participants.valueAt(i);
        if (!wc.isAttached()) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "  Rejecting as detached: %s", wc);
            continue;
        }
        
        // WindowState 不能参与动画,最低要求是 WindowToken
        // The level of transition target should be at least window token.
        if (wc.asWindowState() != null) continue;

        final ChangeInfo changeInfo = changes.get(wc);

        // 没有改变的 WC 也不能参与动画
        if (!changeInfo.hasChanged()) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "  Rejecting as no-op: %s", wc);
            continue;
        }
        
        // 保存有效参与者的 ChangeInfo
        targets.add(changeInfo);
    }

    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Initial targets: %s",
            targets.mArray);
    
    // 2. 尝试提升动画目标的层级
    tryPromote(targets, changes);
    
    // 3. 建立动画目标之间的关联
    // Establish the relationship between the targets and their top changes.
    populateParentChanges(targets, changes);

    // 4.返回动画目标的 ChangeInfo 列表,以 z-order 降序排列
    final ArrayList<ChangeInfo> targetList = targets.getListSortedByZ();
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "  Final targets: %s", targetList);
    return targetList;
}

计算动画目标

  1. 首先从 Transition 的参与者中,过滤掉一些无效的参与者,例如,参与者没有改变。剩下的都是有效的参与者,它们的 ChangeInfo 会被保存到 Targets 中。对于屏幕旋转动画来说,它的几个参与者都是有效的。
  2. 尝试提升动画目标的层级。何谓提升层级?举个例子,如果 Activity 是一个动画目标,其实可以把这个动画目标提升到 parent,也就是 Task。那么不用对 ActivityRecord 动画,而是对 Task 做动画。
  3. 建立动画目标之间的关联。其实就是利用 ChangeInfo#mEndParent 指向父动画目标,并且还会收集一些 TaskFragment, DiaplayArea 作为动画目标。 对于屏幕旋转动画来说,这一步没有实质性作用,本文就不分析了。
  4. 从 Targets 中返回动画目标的 ChangeInfo 列表。

OK,来看下如何提升动画目标的层级

// Transition.java

private static void tryPromote(Targets targets, ArrayMap<WindowContainer, ChangeInfo> changes) {
    WindowContainer<?> lastNonPromotableParent = null;
    
    for (int i = targets.mArray.size() - 1; i >= 0; --i) {
        final ChangeInfo targetChange = targets.mArray.valueAt(i);
        final WindowContainer<?> target = targetChange.mContainer;
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "    checking %s", target);
        final WindowContainer<?> parent = target.getParent();
        
        // 这里是一个优化的代码,如果当前 parent 与上一次不能提升的 parent 一样
        // 那么,当前动画目标肯定就不能提升
        if (parent == lastNonPromotableParent) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "      SKIP: its sibling was rejected");
            continue;
        }

        // 1.检测动画目标是否可以提升到 parent
        if (!canPromote(targetChange, targets, changes)) {
            lastNonPromotableParent = parent;
            continue;
        }

        // 走到这里,表示能提升动画层级


        // 2. 既然能提升,但是还需要根据条件移除动画目标
        // 这个条件是动画目标的 WC 是否被 organized
        if (reportIfNotTop(target)) {
            // 如果 WC 被 organized,那么动画目标不需要从 Targets 中移除
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "        keep as target %s", target);
        } else {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "        remove from targets %s", target);
            // 如果 WC 不被 organized,那么动画目标需要从 Targets 中移除
            targets.remove(i);
        }


        // 3. 既然能提升了,那么把 parent 作为动画目标,保存到 Targets 中
        final ChangeInfo parentChange = changes.get(parent);
        if (targets.mArray.indexOfValue(parentChange) < 0) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "      CAN PROMOTE: promoting to parent %s", parent);
            // The parent has lower depth, so it will be checked in the later iteration.
            i++;
            targets.add(parentChange);
        }

        
        // 4. 既然能提升,把动画目标的,关于是否做动画标志位转移到 parent 中
        if ((targetChange.mFlags & ChangeInfo.FLAG_CHANGE_NO_ANIMATION) != 0) {
            parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_NO_ANIMATION;
        } else {
            parentChange.mFlags |= ChangeInfo.FLAG_CHANGE_YES_ANIMATION;
        }
    }
}

尝试提升动画目标的层级

  1. 检测动画目标是否能提升层级到 parent。
  2. 既然能提升,那么需要移除当前动画目标,保存 parent 这个动画目标。但是,如果这个动画目标的 WC 被 organized,是不需要移除的,因为它被 WMShell 管理,所以也可能是动画的一部分。一般来说,只有 DisplayContent,TaskDisplayArea,root Task 是被 organized。
  3. 既然能提升,那么把 parent 加入到动画目标。
  4. 既然能提升,把动画目标的,关于是否做动画标志位转移到 parent 中。也就是说,如果动画目标的标志位决定其不做动画,那么提升的 parent 也不用做动画。

好,一切的关键在于如何判断动画目标是否能提升层级到 parent,来看下

// Transition.java

/**
 * Under some conditions (eg. all visible targets within a parent container are transitioning
 * the same way) the transition can be "promoted" to the parent container. This means an
 * animation can play just on the parent rather than all the individual children.
 *
 * @return {@code true} if transition in target can be promoted to its parent.
 */
private static boolean canPromote(ChangeInfo targetChange, Targets targets,
        ArrayMap<WindowContainer, ChangeInfo> changes) {
    final WindowContainer<?> target = targetChange.mContainer;
    final WindowContainer<?> parent = target.getParent();
    final ChangeInfo parentChange = changes.get(parent);
    
    // parent 不能创建动画目标,或者 parent 没有改变,那么动画目标不能提升层级
    if (!parent.canCreateRemoteAnimationTarget()
            || parentChange == null || !parentChange.hasChanged()) {
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: %s",
                "parent can't be target " + parent);
        return false;
    }

    // 壁纸 WindowToken 不能提升动画层级
    if (isWallpaper(target)) {
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      SKIP: is wallpaper");
        return false;
    }

    // parent 改变了,也不能提升动画层级
    if (targetChange.mStartParent != null && target.getParent() != targetChange.mStartParent) {
        // When a window is reparented, the state change won't fit into any of the parents.
        // Don't promote such change so that we can animate the reparent if needed.
        return false;
    }

    // 获取动画目标的 transition mode
    final @TransitionInfo.TransitionMode int mode = targetChange.getTransitMode(target);
    
    // 从 parent 遍历动画目标的兄弟节点
    for (int i = parent.getChildCount() - 1; i >= 0; --i) {
        final WindowContainer<?> sibling = parent.getChildAt(i);
        if (target == sibling) continue;
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "      check sibling %s",
                sibling);
        final ChangeInfo siblingChange = changes.get(sibling);
        
        // 兄弟节点没有 ChangeInfo 或者 不是动画目标,不参与比较
        if (siblingChange == null || !targets.wasParticipated(siblingChange)) {
            if (sibling.isVisibleRequested()) {
                // Sibling is visible but not animating, so no promote.
                ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                        "        SKIP: sibling is visible but not part of transition");
                return false;
            }
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "        unrelated invisible sibling %s", sibling);
            continue;
        }
        
        // 走到这里表示兄弟有改变,并且还是动画目标
        
        // 获取兄弟节点的 transition mode
        final int siblingMode = siblingChange.getTransitMode(sibling);
        
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                "        sibling is a participant with mode %s",
                TransitionInfo.modeToString(siblingMode));
        
        // 动画目标和兄弟的 reduce transition mode 不相等,那么也不能提升层级
        if (reduceMode(mode) != reduceMode(siblingMode)) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "          SKIP: common mode mismatch. was %s",
                    TransitionInfo.modeToString(mode));
            return false;
        }
    }
    
    // 排除了不能提升动画层级的可能性,默认就是能提升动画层级
    return true;
}

// 计算 reduce transition mode
/** "reduces" a mode into a smaller set of modes that uniquely represents visibility change. */
@TransitionInfo.TransitionMode
private static int reduceMode(@TransitionInfo.TransitionMode int mode) {
    switch (mode) {
        case TRANSIT_TO_BACK: return TRANSIT_CLOSE;
        case TRANSIT_TO_FRONT: return TRANSIT_OPEN;
        default: return mode;
    }
}

检测一个动画目标是否能提升动画层级,是采用排除法。这里列举下不能提升的情况

  1. parent 不能创建远程动画目标。目前,只有 ActivityRecord,TaskFragment, TaskDisplayArea 能创建远程动画目标。
  2. parent 没有改变。
  3. 动画目标和兄弟的 reduce transition mode 不相同。什么是 reduce transition mode ?它是在可见性方面,把 transition mode 缩小,例如 TRANSIT_TO_BACK -> TRANSIT_CLOSE。对于旋转动画来说,transition mode 是 TRANSIT_CHANGE,reduce transition mode 也是 TRANSITION_CHANGE。

对于屏幕旋转动画来说,目前 Targets 保存的是 DisplayContent, HelloWorld Task。 Transition#mChanges 保存了 DisplayContent, TaskDisplayArea,HelloWorld Task 在改变前的信息。

那么,Task 肯定是能提升动画的层级的,原因如下

  1. Task 的 parent TaskDisplayArea 有改变(例如,配置中的旋转方向),并且也能创建远程动画目标。
  2. Task 的兄弟,不在 Targets 中,即不是动画目标。

既然 Task 能提升动画层级到 parent, 那么 Task 是否需要从 Targets 中移除呢? 不需要!因为 HelloWorld Task 是 root task,它是被 organized 的。

计算 transition info

calculateTargets() 计算出了所有的动画目标,并返回了一个 ArrayList<ChangeInfo>,现在 calculateTransitionInfo() 把这个动画目标集合,转化成可以跨进程传输的数据 TransitionInfo,如下

// type 是 TRANSIT_CHANGE
// flags 是 0
// sortedTargets 是所有动画目标的数据
// startT 是合并后的 sync transaction,现在称之为 start transaction
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
        ArrayList<ChangeInfo> sortedTargets,
        @NonNull SurfaceControl.Transaction startT) {
    // 创建 TransitionInfo 用于动画目标的信息
    final TransitionInfo out = new TransitionInfo(type, flags);
    
    // 1. 为每一个 DisplayContent 只创建一个 transition root leash
    // 并保存到 Transition#mRoots 集合中
    calculateTransitionRoots(out, sortedTargets, startT);
    if (out.getRootCount() == 0) {
        return out;
    }

    // 2. 把动画目标的 ChangeInfo 转换成 TransactionInfo.Change,然后保存到 TransitionInfo
    // Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
    final int count = sortedTargets.size();
    for (int i = 0; i < count; ++i) {
        final ChangeInfo info = sortedTargets.get(i);
        final WindowContainer target = info.mContainer;
        
        // 创建 Change
        final TransitionInfo.Change change = new TransitionInfo.Change(
                target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
                        : null, getLeashSurface(target, startT));
                        
        // mEndParent 是在 populateParentChanges() 中设置的
        // 这里把它保存到了 Change#mParent 中
        if (info.mEndParent != null) {
            change.setParent(info.mEndParent.mRemoteToken.toWindowContainerToken());
        }
        
        if (info.mStartParent != null && info.mStartParent.mRemoteToken != null
                && target.getParent() != info.mStartParent) {
            // ...
        }
        
        // 保存 transition mode
        change.setMode(info.getTransitMode(target));
        info.mReadyMode = change.getMode();
        
        change.setStartAbsBounds(info.mAbsoluteBounds);
        
        // 保存 flags
        change.setFlags(info.getChangeFlags(target));
        info.mReadyFlags = change.getFlags();
        
        change.setDisplayId(info.mDisplayId, getDisplayId(target));

        final Task task = target.asTask();
        final TaskFragment taskFragment = target.asTaskFragment();
        final ActivityRecord activityRecord = target.asActivityRecord();

        if (task != null) {
            // 获取 task info
            final ActivityManager.RunningTaskInfo tinfo = new ActivityManager.RunningTaskInfo();
            task.fillTaskInfo(tinfo);
            
            // 保存 task info
            change.setTaskInfo(tinfo);
            change.setRotationAnimation(getTaskRotationAnimation(task));
            final ActivityRecord topRunningActivity = task.topRunningActivity();
            if (topRunningActivity != null) {
                if (topRunningActivity.info.supportsPictureInPicture()) {
                    change.setAllowEnterPip(
                            topRunningActivity.checkEnterPictureInPictureAppOpsState());
                }
                setEndFixedRotationIfNeeded(change, task, topRunningActivity);
            }
        } else if ((info.mFlags & ChangeInfo.FLAG_SEAMLESS_ROTATION) != 0) {
            // ...
        }

        final WindowContainer<?> parent = target.getParent();
        final Rect bounds = target.getBounds();
        final Rect parentBounds = parent.getBounds();
        
        // 设置动画目标与 parent 的偏移量
        change.setEndRelOffset(bounds.left - parentBounds.left,
                bounds.top - parentBounds.top);
        
        // 获取旋转后的方向
        int endRotation = target.getWindowConfiguration().getRotation();
        
        if (activityRecord != null) {
            // ...
        } else {
            change.setEndAbsBounds(bounds);
        }

        if (activityRecord != null || (taskFragment != null && taskFragment.isEmbedded())) {
            // ...
        }

        // 保存旋转前后的方向
        change.setRotation(info.mRotation, endRotation);
        
        // 保存截图 surface
        if (info.mSnapshot != null) {
            change.setSnapshot(info.mSnapshot, info.mSnapshotLuma);
        }

        // TransitionInfo#Changes 集合保存 Change
        out.addChange(change);
    }

    // 3. TransitionInfo#mOptions 保存 activity 动画数据
    TransitionInfo.AnimationOptions animOptions = null;
    //  省略一段 activity to activity 的动画参数的解析...
    if (animOptions != null) {
        out.setAnimationOptions(animOptions);
    }
    
    return out;
}

TransitionInfo 总共保存了三类数据

  1. TransitionInfo 使用 ArrayList<Root> mRoots 保存 transition root leash,它其实就是一个 SurfaceControl。动画目标的 surface 都要直接/间接地 reparent 到 root leash。
  2. 为每一个动画目标创建一个 Change 对象,并填充数据,然后保存到 TransitionInfo 的 ArrayList<Change> mChanges
  3. 解析 Activity to activity 的动画,保存到 TransitionInfo 的 mOptions

屏幕旋转动画,不涉及第3步的 activity to activity 动画。

对于第2步,填充数据到 Change。这里要重点提一下 Change 构造函数的第二个参数,也就是动画目标的 leash,获取方式如下

// Transition.java

private static SurfaceControl getLeashSurface(WindowContainer wc,
        @Nullable SurfaceControl.Transaction t) {
    final DisplayContent asDC = wc.asDisplayContent();
    if (asDC != null) {
        // DisplayContent leash 是 mWindowingLayer,而不是它自己的 surface
        // DisplayContent is the "root", so we use the windowing layer instead to avoid
        // hardware-screen-level surfaces.
        return asDC.getWindowingLayer();
    }
    
    if (!wc.mTransitionController.useShellTransitionsRotation()) {
        // 屏幕旋转动画不涉及 WindowToken
        final WindowToken asToken = wc.asWindowToken();
        if (asToken != null) {
            // ...
        }
    }
    
    // 默认返回动画目标的 surface
    return wc.getSurfaceControl();
}

可以看到只有 DisplayContent 比较特殊,它的 leash 并不是自己的 surface,而是 mWindowingLayer,它的值其实是 WindowedMagnification:0:31 的 child。这是 WMS 窗口层级的基本知识,这里就不过多分析了。

最后看下如何计算 transition root leash

// Transition.java

static void calculateTransitionRoots(@NonNull TransitionInfo outInfo,
        ArrayList<ChangeInfo> sortedTargets,
        @NonNull SurfaceControl.Transaction startT) {
    // There needs to be a root on each display.
    for (int i = 0; i < sortedTargets.size(); ++i) {
        final WindowContainer<?> wc = sortedTargets.get(i).mContainer;
        
        // Don't include wallpapers since they are in a different DA.
        if (isWallpaper(wc)) continue;
        
        final int endDisplayId = getDisplayId(wc);
        if (endDisplayId < 0) continue;
        
        // 注意,一个 DisplayContent 只能有一个 transition root leash
        if (outInfo.findRootIndex(endDisplayId) >= 0) continue;
        
        // 1.找到同一个 DisplayContent 下,所有动画目标的公共祖先
        WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, wc);
        
        // 2. 找到一个创建 leash 的参考点
        // Make leash based on highest (z-order) direct child of ancestor with a participant.
        // Check whether the ancestor is belonged to last parent, shouldn't happen.
        final boolean hasReparent = !wc.isDescendantOf(ancestor);
        WindowContainer leashReference = wc;
        if (hasReparent) {
            // 根据上面注释所说,这是一个异常情况,此时 reparent 是不应该发生的
            Slog.e(TAG, "Did not find common ancestor! Ancestor= " + ancestor
                    + " target= " + wc);
        } else {
            // 从 wc 到共同祖先这条线上,最高的那个 parent,就是创建 leash 参考点
            while (leashReference.getParent() != ancestor) {
                leashReference = leashReference.getParent();
            }
        }

        // 3.创建 transition root leash
        final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
                "Transition Root: " + leashReference.getName()).build();
        rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots");
        // 注意,root leash 的 layer 是 0
        startT.setLayer(rootLeash, leashReference.getLastLayer());

        // 4.保存到 TransitionInfo#mRoots 中
        outInfo.addRootLeash(endDisplayId, rootLeash,
                ancestor.getBounds().left, ancestor.getBounds().top);
    }
}

创建 transition root leash,其实就是创建一个 SurfaceControl,所有动画都是在 root leash 执行的,并且每一个 DisplayContent 只有一个 root leash。 它的创建过程如下

  1. 首先找到同一个 DisplayContent 下的所有动画目标的共同祖先。
  2. 找到一个创建 root leash 的参考点,这个参考点,是所有动画目标中,最接近共同祖先的那一个。
  3. 利用 leash 参考点,创建 root leash surface,并把 layer 设置为 0。
  4. TransitionInfo 根据root leash 信息,创建 Root,并保存到 mRoots 集合中。

我曾经质疑过,root leash 的 layer 设置为 0,到底显示在哪里?可以通过 winscope 查看 root leash 前后的层级情况。

在 root leash 显示之前,WindowdMagnification:0:31 挂在 DisplayContent surface 之下,并且 layer 是 0,如下

wm_z.png

在 root leash 显示之后,root leash 的 layer 是 0,它显示在 WindowdMagnification:0:31 之上,如下

222222.png

继续来看第2步,创建 leash 参考点是共同祖先的 child,那么它的 makeAnimationLeash() 创建的 leash surface 到底挂在哪个 surface 之下呢? 首先看下基类 WindowContainer 的实现

// WindowContainer.java

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

Builder makeSurface() {
    final WindowContainer p = getParent();
    return p.makeChildSurface(this);
}

Builder makeChildSurface(WindowContainer child) {
    final WindowContainer p = getParent();
    return p.makeChildSurface(child)
            // 挂在 parent surface 之下
            .setParent(mSurfaceControl);
}

leash 参考点的 parent 是 公共祖先,那么 leash 参考点创建的 surface 默认是挂在公共祖先的 parent surface 之下。

但是,对于屏幕旋转动画来说,动画目标有 DisplayContent, TaskDisplayArea,Task,因此公共祖先就是 RootWindowContainer,而 leash 参考点就是 DisplayContent。那么 DisplayContent 创建的 root leash surface 是不是挂在 RWC surface 之下? 不是这样的,DisplayContent 复写了 makeAnimationLeash()

// DisplayContent.java

public SurfaceControl.Builder makeAnimationLeash() {
    return mWmService.makeSurfaceBuilder(mSession).setParent(mSurfaceControl)
            .setContainerLayer();
}

原来,DisplayContent 创建的 leash surface 挂在自己的 surface 之下。

构建 finish transaction

// Transition.java

/**
 * Build a transaction that "resets" all the re-parenting and layer changes. This is
 * intended to be applied at the end of the transition but before the finish callback. This
 * needs to be passed/applied in shell because until finish is called, shell owns the surfaces.
 * Additionally, this gives shell the ability to better deal with merged transitions.
 */
private void buildFinishTransaction(SurfaceControl.Transaction t, TransitionInfo info) {
    final Point tmpPos = new Point();
    // usually only size 1
    final ArraySet<DisplayContent> displays = new ArraySet<>();
    
    // 1.复位所有的动画目标
    for (int i = mTargets.size() - 1; i >= 0; --i) {
        final WindowContainer target = mTargets.get(i).mContainer;
        if (target.getParent() != null) {
            final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
            final SurfaceControl origParent = getOrigParentSurface(target);
            // Ensure surfaceControls are re-parented back into the hierarchy.
            t.reparent(targetLeash, origParent);
            t.setLayer(targetLeash, target.getLastLayer());
            target.getRelativePosition(tmpPos);
            t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
            // No need to clip the display in case seeing the clipped content when during the
            // display rotation. No need to clip activities because they rely on clipping on
            // task layers.
            if (target.asTaskFragment() == null) {
                t.setCrop(targetLeash, null /* crop */);
            } else {
                // Crop to the resolved override bounds.
                final Rect clipRect = target.getResolvedOverrideBounds();
                t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
            }
            t.setCornerRadius(targetLeash, 0);
            t.setShadowRadius(targetLeash, 0);
            t.setMatrix(targetLeash, 1, 0, 0, 1);
            t.setAlpha(targetLeash, 1);
            // The bounds sent to the transition is always a real bounds. This means we lose
            // information about "null" bounds (inheriting from parent). Core will fix-up
            // non-organized window surface bounds; however, since Core can't touch organized
            // surfaces, add the "inherit from parent" restoration here.
            if (target.isOrganized() && target.matchParentBounds()) {
                t.setWindowCrop(targetLeash, -1, -1);
            }
            displays.add(target.getDisplayContent());
        }
    }
    
    // 2.移除截图层
    // Remove screenshot layers if necessary
    if (mContainerFreezer != null) {
        mContainerFreezer.cleanUp(t);
    }
 
    //3. 更新 WMS 窗口层级,以及 surface 层级
    mController.mBuildingFinishLayers = true;
    try {
        for (int i = displays.size() - 1; i >= 0; --i) {
            if (displays.valueAt(i) == null) continue;
            displays.valueAt(i).assignChildLayers(t);
        }
    } finally {
        mController.mBuildingFinishLayers = false;
    }
    
    // 4.移除 transition root leash
    for (int i = 0; i < info.getRootCount(); ++i) {
        t.reparent(info.getRoot(i).getLeash(), null);
    }
}

根据注释就可以看出,finish transaction 是在 WMShell 中执行的,并且是在动画完成后执行,因此 finish transaction 执行的是复位操作

  1. 复位所有的动画目标。例如,WMShell 在执行动画前,会把动画目标的 leash,直接/间接地 reparent 到 transition root leash。那么,当动画执行完成后,就需要把动画目标的 leash 再 reparent 到本来的 parent 。
  2. 移除截图层。对于屏幕旋转动画来说,创建了截图层。在动画执行完成后,就需要移除它。
  3. WMShell 在执行动画前,会对动画目标的 leash 设置 layer。那么,当动画结束后,就需要重新根据 WMS 层级,设置动画目标 leash 的 layer,使它们回归本来的位置。
  4. 移除 transition root leash。

下一步

现在一切就绪了,下一篇文章就来分析 WMShell 如何执行旋转动画。