忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
-- 服装学院的IT男
建议阅读顺序:
ShellTransitions-2-requestStartTransition处理
1. 2次 WMCore -> WMShell 简介
ITransitionPlayer 的实现在 SystemUI 下的 Transitions 下的内部类 TransitionPlayerImpl 。
# com.android.wm.shell.transition.Transitions
private final TransitionPlayerImpl mPlayerImpl;
// Player 服务端
@BinderThread
private class TransitionPlayerImpl extends ITransitionPlayer.Stub {
@Override
public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo,
SurfaceControl.Transaction t, SurfaceControl.Transaction finishT)
throws RemoteException {
// WMCore -> WMShell onTransitionReady
mMainExecutor.execute(() -> Transitions.this.onTransitionReady(
iBinder, transitionInfo, t, finishT));
}
@Override
public void requestStartTransition(IBinder iBinder,
TransitionRequestInfo request) throws RemoteException {
// WMCore -> WMShell requestStartTransition
// 切线程,调用到外部类 Transitions 的方法
mMainExecutor.execute(() -> Transitions.this.requestStartTransition(iBinder, request));
}
}
SystemUI 会创建 TransitionPlayerImpl 对象,并通过 WindowOrganizer::registerTransitionPlayer 传递给 system_server 端,之前在 TransitionController 看到的 mTransitionPlayer 就是这里传过去的对象,用于进程通信。
这边看到 TransitionPlayerImpl 只提供了2个方法,并且内部实现也只是简单的切换线程执行了外部类 Transitions 的方法。
这2个方法也是整个流程中 WMCore 调用 WMShell 的方法。
-
- TransitionPlayerImpl::requestStartTransition 的第一次 WMCore -> WMShell 通信,目的是为了让 SystemUI 端做三件事
- 1.1 在 SystemUI 端创建一个和 Transition 对应的 ActiveTransition (通过 transitionToken 联系)
- 1.2 为这个 ActiveTransition 匹配一个真正播放动画的处理类(也可能匹配不到)
- 1.3 与 system_server 端通信,触发下一个流程 -- startTransition
-
- TransitionPlayerImpl::onTransitionReady 这是第二次 WMCore -> WMShell 通信,目的是要开始在 SystemUI 触发动画播放了。
当前先分析第一次 WMCore -> WMShell ,也就是 TransitionPlayerImpl::requestStartTransition 的逻辑。
2. requestStartTransition 阶段(第一次 WMCore -> WMShell)
# com.android.wm.shell.transition.Transitions
// 应该是处理具体动画的集合,里面放了很多子类的对象
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
// 稍后要执行动画的Transition
private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
// 日志
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
......
// 1. 创建对应的 ActiveTransition
final ActiveTransition active = new ActiveTransition();
WindowContainerTransaction wct = null;
// 2. 从 mHandlers 集合中找到能播放动画的对象
if (request.getType() == TRANSIT_SLEEP) {
mSleepHandler.handleRequest(transitionToken, request);
active.mHandler = mSleepHandler;
} else {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
// 通过 TransitionHandler::handleRequest 确定
wct = mHandlers.get(i).handleRequest(transitionToken, request);
if (wct != null) {
// 如果这个 TransitionHandler 能处理这个请求? 就设置给 ActiveTransition
active.mHandler = mHandlers.get(i);
break;
}
}
......
}
// 3. WMCShell -> WMCore 开始事务
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
// 通过token 与 WMCore 的 Transitions联系
active.mToken = transitionToken;
// 放入集合
mPendingTransitions.add(0, active);
}
第一次 WMCShell -> WMCore 的逻辑不复杂,都在这了,先看一下这个方法打印的日志
打印日志: WindowManagerShell: Transition requested: android.os.BinderProxy@8a54fca TransitionRequestInfo { type = OPEN, triggerTask = TaskInfo{userId=0 taskId=17 displayId=0 isRunning=true baseIntent=Intent ......}, remoteTransition = null, displayChange = null }
这段日志的输出还是很长的,将 TaskInfo 部分做了省略,目前是以桌面冷启动场景分析的,可以看到 type 是 OPEN。
这里将方法分为3个点,和第一小节的描述对应上再详细解释下。
-
- 首先就创建了一个 ActiveTransition 对象 active ,并且在方法底下看到将 system_server 端 Transition 的 token 设置给了 ActiveTransition ,那这2个对象就有映射关系了。而且在代码中看到如果匹配上了播放动画的对象,则会赋值给 active 的 mHandler 对象,也就是说这个 mHandler 是一个过渡事务播放动画的对象
-
- 这一块逻辑的目的是为了给 ActiveTransition 匹配到一个播放动画的对象,当前 type = OPEN ,所以会通过 TransitionHandler::handleRequest 确定当前事务类型能用哪个 TransitionHandler 子类能播放这个事务的动画。看代码的实现也是有可能匹配不到的,也就是说当前这个方法执行完,可能这个 ActiveTransition 可能还是没有播放动画对象的(active.mHandler = null)
-
- 这一阶段 SystemUI 的处理就完成了,继续主流程走回 system_server 端
SystemUI 端的 requestStartTransition 阶段处理就完成了,逻辑相对简单。比较有 是 mHandlers 集合下存放的 TransitionHandler 对象到底是什么,简单理解它就是一个接口,定义了实现过渡事务的几个操作,当前场景主要关心的是过渡动画的播放,具体场景会匹配具体的子类来处理过渡动画的具体行为。
下面对这个类的简单介绍一下。
3. TransitionHandler 介绍
# com.android.wm.shell.transition.Transitions
/**
* Interface for something which can handle a subset of transitions.
*/
public interface TransitionHandler {
// 播放过渡动画,能成功播放返回 true
boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback);
// 合并其他动画到当前
default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull TransitionFinishCallback finishCallback) { }
// 处理事务请求,如果能处理则返回一个 WindowContainerTransaction
WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request);
// 事务消耗回调()
default void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
@Nullable SurfaceControl.Transaction finishTransaction) { }
// 动画缩放设置,比如播放时长延长
default void setAnimScaleSetting(float scale) {}
}
当前主要关注 handleRequest 和 startAnimation 方法即可。 startAnimation 方法就是具体播放动画的处理里,后续流程会详细分析。 handleRequest 方法在 Transitions::requestStartTransition 中已经看到了。 如果这个事务能够被当前 TransitionHandler 的实现处理,则其 handleRequest 方法会返回一个 WindowContainerTransaction ,如果不能则返回 null 。 当前分析的冷启动场景,mHandlers 里的元素都返回 null ,也就是说 ActiveTransition 下的 mHandler 变量还是 null 。
我手上源码 TransitionHandler 的实现类有下面几个,当然还有没列出来的,看类名也能大概猜到是处理什么场景的过渡事务。
后续的逻辑回到了 system_server 端执行 startTransition 阶段。
4. startTransition WMShell -> WMCore
WindowOrganizerController::startTransition
Transition::start
Transition::applyReady
BLASTSyncEngine::setReady
SyncGroup::setReady
WindowOrganizerController::applyTransaction -- 也可能是 onStartingWindowDrawn -- > executeAppTransition 流程
WindowOrganizerController::applyTransaction
Transition::applyDisplayChangeIfNeeded
Transition::setReady
ReadyTracker::setReadyFrom -- 设置准备好
Transition::applyReady
BLASTSyncEngine::setReady
SyncGroup::setReady
当前场景在 Transitions::requestStartTransition 方法由于没有对应的 TransitionHandler 匹配,所以传递的 wct 是 null 。
# WindowOrganizerController
@Override
public void startTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t) {
startTransition(-1 /* unused type */, transitionToken, t);
}
private IBinder startTransition(@WindowManager.TransitionType int type,
@Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {
......
// 根据 token 获取到 WMCore 对应的 Transition
Transition transition = Transition.fromBinder(transitionToken);
......
// 获取 wct ,参数为 null 就新建一个
final WindowContainerTransaction wct =
t != null ? t : new WindowContainerTransaction();
// * 1
transition.start();
transition.mLogger.mStartWCT = wct;
// * 2
applyTransaction(wct, -1 /*syncId*/, transition, caller);
......
}
注意传递的 syncId 是 -1 ,wct 现在也是有值的,如果参数为 null 就新建。
这里有2个逻辑:
-
- Transition::start 开始过渡事务
-
- WindowOrganizerController::applyTransaction 不过由于当前场景这里的 wct 是新建的没啥内容,所以这里的调用可以忽略
分析 Transition::start 逻辑,看方法名是要开始这个事务,那对应到同步引擎其实就是执行 SyncGroup::setReady 方法,设置这个同步组准备就绪
4.1 Transition::start
先看这条线
# Transition
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
void start() {
......//异常状态处理
// 设置状态
mState = STATE_STARTED;
// 日志
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
mSyncId);
applyReady();
......
SurfaceControl.mergeToGlobalTransaction(mTmpTransaction);
}
这里有2个点:
-
- 把 Transition 的状态设置为 STATE_STARTED
-
- 执行 applyReady 方法,目的是把同步组设置为就绪状态,但是内部逻辑也可能设置失败
打印日志: WindowManager: Starting Transition 6
# Transition
private void applyReady() {
// 1. 判断状态是否满足设置条件
if (mState < STATE_STARTED) return;
// 判断参与容器是否都准备好了
// allReady query
final boolean ready = mReadyTracker.allReady();
// 日志
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Set transition ready=%b %d", ready, mSyncId);
// 2. 设置同步组是否准备好了
boolean changed = mSyncEngine.setReady(mSyncId, ready);
......
}
-
- 从当前分析的调用链,状态肯定是 STATE_STARTED ,不过既然加了这个代码,说明肯定还有其他地方调用
-
- 这个方法在分析同步引擎的时候提过,既然这里调用了2个参数的方法,那说明 “ready” 可能就是 false
打印日志: WindowManager: Set transition ready=false 6
通过日志,也确实看到这一次的执行 “ready=false” 那么同步组就不能背设置为就绪状态,根据同步引擎的设计,只有当同步处于“ready=true”才能在每一次刷新的时候去检查是否同步完成。
而这里的 ready 值受 ReadyTracker::allReady 影响,先来看看这个方法的返回值受什么影响
4.2 了解 ReadyTracker
4.2.1 ReadyTracker介绍
首先看一下 ReadyTracker 类的定义,代码如下:
# Transition
/**
* The transition sync mechanism has 2 parts:
* 1. Whether all WM operations for a particular transition are "ready" (eg. did the app
* launch or stop or get a new configuration?).
* 2. Whether all the windows involved have finished drawing their final-state content.
*
* A transition animation can play once both parts are complete. This ready-tracker keeps track
* of part (1). Currently, WM code assumes that "readiness" (part 1) is grouped. This means that
* even if the WM operations in one group are ready, the whole transition itself may not be
* ready if there are WM operations still pending in another group. This class helps keep track
* of readiness across the multiple groups. Currently, we assume that each display is a group
* since that is how it has been until now.
*/
// 静态内部类
private static class ReadyTracker {
// 跟踪的容器(DisplayContent级别)的准备状态
private final ArrayMap<WindowContainer, Boolean> mReadyGroups = new ArrayMap<>();
private boolean mUsed = false;
private boolean mReadyOverride = false;
private int mDeferReadyDepth = 0;
void addGroup(WindowContainer wc) {
if (mReadyGroups.containsKey(wc)) {
Slog.e(TAG, "Trying to add a ready-group twice: " + wc);
return;
}
// 添加进跟踪组,默认是 false
mReadyGroups.put(wc, false);
}
void setReadyFrom(WindowContainer wc, boolean ready) {
mUsed = true;
WindowContainer current = wc;
while (current != null) {
// 只有 DisplayContent 类型才能被添加进集合
if (isReadyGroup(current)) {
// 设置 value
mReadyGroups.put(current, ready);
// 打印相关信息
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting Ready-group to"
+ " %b. group=%s from %s", ready, current, wc);
break;
}
current = current.getParent();
}
}
// 设置全部准备完毕(目前不知道调用场景,可暂时忽略)
void setAllReady() {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Setting allReady override");
mUsed = true;
mReadyOverride = true;
}
boolean allReady() {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
+ "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
groupsToString());
if (!mUsed) return false;
if (mDeferReadyDepth > 0) return false;
if (mReadyOverride) return true;
for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
final WindowContainer wc = mReadyGroups.keyAt(i);
if (!wc.isAttached() || !wc.isVisibleRequested()) continue;
// mReadyGroups 对应的 value 为 false 就返回 false
if (!mReadyGroups.valueAt(i)) return false;
}
return true;
}
// 只有 DisplayContent 类型才能被 ReadyTracker 跟踪
private static boolean isReadyGroup(WindowContainer wc) {
return wc instanceof DisplayContent;
}
}
处理第一次 ReadyTracker::addGroup 被跟踪对象外,另外的方法会输出如下日志:
注释都在代码上了,另外再提几点:
-
- ReadyTracker 是 Transition 内部类,可以理解为是 Transition 工作流程的一个帮助类
-
- 过渡动画播放有2个前提,也是 ReadyTracker 定义上的 google 注释:
- 2.1 确定所有与特定过渡相关的窗口管理(WM)操作是否已经准备就绪,目前是以一个屏幕作为一组
- 2.2 所有参与设计的窗口都需要绘制完完成
第二点好理解,第一点有点抽象,但是也是 ReadyTracker 这个类的功能,就是跟着是否满足第一个条件,第一个条件满足了 ReadyTracker::allReady 才返回 true ,才能将同步组设置为准备就绪,才会有 2.2 后续的逻辑。
- 3. mReadyGroups 下的元素只能是 DisplayContent ,那以一个屏幕的手机为例,这个集合最少就只有1个元素
-
- 如果执行了 ReadyTracker::setAllReady 就表示全部准备就绪(没有设置延迟的话),不过目前没发现这个方法的调用场景,暂时忽略
-
- 根据日志,正常走到 ReadyTracker::setAllReady 能不能返回 true 和 mReadyGroups 这个 map 对应的 value 有关,而赋值的地方处理刚添加,其他时候就只能通过 ReadyTracker::setReadyFrom 赋值,这个方法还有会打印是哪个容器触发的设置值
-
- 调用 ReadyTracker::setReadyFrom 设置为 true 的场景和 Android 14 以前版本的 AppTransition 三大步的 executeAppTransition 是一样的,这个其实也就是 ReadyTracker 所谓跟踪的目的
4.2.2 跟踪者的添加
分析 ReadyTracker 的第一步先看看 ReadyTracker::addGroup 方法的执行。 执行的地方是在 Transition::collect 方法收集容器时,这个方法之前看过,不过这次看主要关注 ReadyTracker::addGroup 逻辑。
# Transition
private final ReadyTracker mReadyTracker = new ReadyTracker();
void collect(@NonNull WindowContainer wc) {
......
// 日志,收集了某个容器
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// 想要收集对应的屏幕到 ReadyTracker 中
// getAnimatableParent方法会一直获取这个容器的父容器,直到是 DisplayContent
for (WindowContainer<?> curr = getAnimatableParent(wc);
curr != null && !mChanges.containsKey(curr);
curr = getAnimatableParent(curr)) {
final ChangeInfo info = new ChangeInfo(curr);
updateTransientFlags(info);
mChanges.put(curr, info);
// 如果是 DisplayContent
if (isReadyGroup(curr)) {
// 放入跟踪集合
mReadyTracker.addGroup(curr);
// 打印收集日志
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Creating Ready-group for"
+ " Transition %d with root=%s", mSyncId, curr);
}
}
......
// 下面为收集容器事务,省略部分代码
// 1. 加入到同步组
mSyncEngine.addToSyncSet(mSyncId, wc);
// 2. 获取容器对应的 ChangeInfo ,记录这这个容器哪些属性发生了改变
ChangeInfo info = mChanges.get(wc);
// 3. 保存到集合
mParticipants.add(wc);
......
}
每当 Transition 收集一个容器时,就会执行 Transition::collect 方法,然后会通过 getAnimatableParent 方法不断获取容器的父容器,直到是 DisplayContent (Transition::isReadyGroup才返回true)。
当找到这个容器所在的 DisplayContent 时如果它还没有被添加进 ReadyTracker 跟踪,就执行 ReadyTracker::addGroup 方法添加,如果已经添加过就不处理了。 这里刚好有收集容器和 ReadyTracker 收集 DisplayContent 的日志,可以根据日志的输出分析执行逻辑。
这里看一下日志的输出:
根据日志可以确认是在 Transition 收集 Task 容器的的时候 ReadyTracker 才收集了对应的 DisplayContent ,并不是在最初 Transition 收集 ActivityRecord 的时候。 这是因为 Transition 收集 ActivityRecord 的时候 ActivityRecord 才刚创建,还没有挂载到窗口树上(Task 都还没创建),所有无法找到其对应的屏幕也就无法收集。
4.2.3 ReadyTracker准备状态逻辑
现在已经知道 ReadyTracker 下有当前屏幕了,就可以去看什么时候设置为准备就绪的逻辑了。
根据日志 ReadyTracker::setReadyFrom 执行了3次,第一次是启动 Actvity 前期,上一个 Activity 要 pause 的时候,也就是执行了 TaskDisplayArea::pauseBackTasks . 后面2个其实都是 executeAppTransition 流程。
新的 Activity 要创建时相关日志:
StartingWindow绘制完成相关日志:
2次设置为 true 的调用链如下:
ActivityTaskSupervisor::realStartActivityLocked
LaunchActivityItem::init -- 触发应用端处理
ResumeActivityItem::init
Task::minimalResumeActivityLocked -- 设置状态为 RESUMED
ActivityRecord::setState
ActivityRecord::completeResumeLocked
ActivityTaskSupervisor::reportResumedActivityLocked
RootWindowContainer::executeAppTransitionForAllDisplay
DisplayContent::executeAppTransition -- executeAppTransition 逻辑
TransitionController::setReady
TransitionController::setReady
Transition::setReady
ReadyTracker::setReadyFrom
Transition::applyReady
BLASTSyncEngine::setReady
RootWindowContainer::performSurfacePlacementNoTrace
RootWindowContainer::applySurfaceChangesTransaction
DisplayContent::applySurfaceChangesTransaction
DisplayContent::mApplySurfaceChangesTransaction
WindowStateAnimator::commitFinishDrawingLocked
WindowState::performShowLocked
ActivityRecord::onStartingWindowDrawn
DisplayContent::executeAppTransition -- executeAppTransition 逻辑
TransitionController::setReady
TransitionController::setReady
Transition::setReady
ReadyTracker::setReadyFrom
Transition::applyReady
BLASTSyncEngine::setReady
公共流程是 DisplayContent::executeAppTransition 之后,以这个方法看一下代码执行逻辑。
# DisplayContent
void executeAppTransition() {
// * 本次重点
mTransitionController.setReady(this);
// 进入条件
if (mAppTransition.isTransitionSet()) {
// log
ProtoLog.w(WM_DEBUG_APP_TRANSITIONS,
"Execute app transition: %s, displayId: %d Callers=%s",
mAppTransition, mDisplayId, Debug.getCallers(5));
// 以前 AppTransition 的逻辑
mAppTransition.setReady();
mWmService.mWindowPlacerLocked.requestTraversal();
}
}
这个方法很重要,常说的 executeAppTransition 流程其实指的也就是这个方法。 以前分析的重点是 AppTransition::isTransitionSet 满足后的条件,现在 Android 14 已经不走下面的逻辑了, 目前只看 TransitionController::setReady 的调用,传递的参数是 this ,也就是当前屏幕(DisplayContent)
# TransitionController
void setReady(WindowContainer wc) {
// 第二个参数就是给 ReadyTracker 的
// 这里直接传递true
setReady(wc, true);
}
void setReady(WindowContainer wc, boolean ready) {
if (mCollectingTransition == null) return;
mCollectingTransition.setReady(wc, ready);
}
mCollectingTransition 就是当前收集的主事务
# Transition
void setReady(WindowContainer wc, boolean ready) {
if (!isCollecting() || mSyncId < 0) return;
// 设置屏幕准备完成
mReadyTracker.setReadyFrom(wc, ready);
// 设置同步组
applyReady();
}
ReadyTracker::setReadyFrom 设置 true 后又执行了 ReadyTracker::applyReady ,这个方法之前已经分析过来,只不过执行内部因为 ReadyTracker::allReady 返回 false ,导致同步组也设置 false ,不过现在就可以把同步组设置为 true 了。
这里设置完 true 后,即表示 ReadyTracker 的跟踪,这个屏幕已经准备就绪,当然根据注释,不代表目标收集的窗口都绘制完了。 后续 ReadyTracker::allReady 也会返回 true 。
现在同步引擎就处于就绪状态了,后续逻辑就是在每一次layout都会检查同步组容器是否都绘制完