Android U WMS : 屏幕旋转动画(1)

2,429 阅读45分钟

在 Android S 上,Google 开发了一个 feature,名为 shell transition。它把窗口的表现力(例如,窗口动画)从 WMS 剥离,放到了 SystemUI 中。那么窗口的处理,就分为了两个部分。WMS 侧,称之为 WMCore,负责窗口的生命周期、层级、配置,等等。SystemUI 侧,称之为 WMShell,负责窗口表现力。

对于一个窗口动画,WMCore 和 WMShell 的交互过程,大致如下

sequenceDiagram
WMCore->>WMShell: request transition
WMShell ->> WMCore: start transition
WMCore ->> WMCore: 收集WC,并等待窗口绘制完成
WMCore->>WMShell: transition ready
WMShell ->> WMShell: play transition(执行动画)
WMShell ->> WMCore: finish transition

这个图,只是简单展示 shell transtion 处理窗口动画的流程,实际的执行流程比这个要复杂的多。本文,用一个具体的例子,屏幕旋转动画,分析它在 shell transition 框架下,是如何执行的。

案例

  1. 在下拉控制中心打开屏幕旋转开关。
  2. 用 Android Studio 创建一个 HelloWorld app,然后运行起来。
  3. 在 MainActivity 界面,把手机从竖屏旋转到横屏,我们会看到一个屏幕旋转动画。这个动画就是本文要分析的案例。

获取 sensor上报的方向

当旋转手机时,系统肯定是检测到了 sensor 上报的旋转方向,才执行了屏幕旋转动画。sensor 数据上报流程,如下面的类关系图所示

classDiagram

SensorEventListener <|.. OrientationJudge
OrientationJudge <|-- OrientationSensorJudge
OrientationSensorJudge *-- WindowOrientationListener
WindowOrientationListener <|-- OrientationListener
OrientationListener *-- DisplayRotation

class DisplayRotation {
-OrientationListener mOrientationListener
}

class OrientationListener {
+onProposedRotationChanged(int rotation)
}

class WindowOrientationListener {
<<abtract>>
~OrientationJudge mOrientationJudge
+enable()
+disable()
+abstract onProposedRotationChanged(int rotation)
}

class OrientationSensorJudge {
+onSensorChanged(SensorEvent event)
}

class OrientationJudge {
<<abtract>>
}

class SensorEventListener {
<<interface>>
onSensorChanged(SensorEvent event)
}

从这个类关系图可以看出,OrientationSensorJudge 实现了 SensorEventListener 接口,它用来接收 sensor 上报的数据,如下

// WindowOrientationListener.java
final class OrientationSensorJudge extends OrientationJudge {
    
    public void onSensorChanged(SensorEvent event) {
        // 1. 获取 sensor 上报的方向
        int reportedRotation = (int) event.values[0];

        if (reportedRotation < 0 || reportedRotation > 3) {
            return;
        }

        FrameworkStatsLog.write(
                FrameworkStatsLog.DEVICE_ROTATED,
                event.timestamp,
                rotationToLogEnum(reportedRotation),
                FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
        
        // 通常是不支持 rotation resolver 这个 feature 的
        if (isRotationResolverEnabled()) {
            // ...
        } else {
            // 计算最终的方向
            finalizeRotation(reportedRotation);
        }
    }

    private void finalizeRotation(int reportedRotation) {
        int newRotation;
        synchronized (mLock) {
            // mDesiredRotation 保存 sensor 上报的方向
            mDesiredRotation = reportedRotation;
            // 2. 计算出最终要上报旋转方向,系统称之为建议的旋转方向
            // 一般来说,就是等于 sensor 上报的方向
            newRotation = evaluateRotationChangeLocked();
        }
        
        if (newRotation >= 0) {
            // mLastRotationResolution 保存的是上报给系统处理的旋转方向
            mLastRotationResolution = newRotation;
            mLastRotationResolutionTimeStamp = SystemClock.uptimeMillis();
            // 3. 通知宿主类,处理旋转方向改变
            // 这个抽象方法,由DisplayRotation的内部类OrientationListener实现
            onProposedRotationChanged(newRotation);
        }
    }
}

sensor 上报的旋转方向,经过处理后,转化成建议的旋转方向,通常都是等于 sensor 上报的方向。根据前面的类关系图可知,由 OrientationListener#onProposedRotationChanged() 来处理建议的旋转方向

// DisplayRotation.java

private class OrientationListener extends WindowOrientationListener implements Runnable {
    public void onProposedRotationChanged(@Surface.Rotation int rotation) {
        // 这个动态log,打印了系统要处理的最终方向
        ProtoLog.v(WM_DEBUG_ORIENTATION, "onProposedRotationChanged, rotation=%d", rotation);
        
        // Send interaction power boost to improve redraw performance.
        mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
        
        // 把建议的旋转方向,通知给 IRotationWatcher 
        // 只有系统 app 才能注册 IRotationWatcher
        dispatchProposedRotation(rotation);
        
        if (isRotationChoiceAllowed(rotation)) {
            // 有些情况,是不会立即执行屏幕旋转,而是先在屏幕右下角显示一个旋转图标
            // 用户点击这个旋转图标后,才会强制发生旋转
            // 例如,屏幕旋转关闭,并且 Activity 不声明任何旋转方向
        } else {
            // 1. 一般来说,由 WMS 直接更新系统的旋转方向
            mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
            mService.updateRotation(false /* alwaysSendConfiguration */,
                    false /* forceRelayout */);
        }
    }
}

本文分析的案例情况,是打开了屏幕旋转开关,因此直接由 WindowManagerService 来执行系统方向更新。

WMS更新系统旋转方向

// DisplayRotation.java

// 两个参数,都是false
public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
    updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
}

private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
    ProtoLog.v(WM_DEBUG_ORIENTATION, "updateRotationUnchecked:"
                    + " alwaysSendConfiguration=%b forceRelayout=%b",
            alwaysSendConfiguration, forceRelayout);

    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation");

    final long origId = Binder.clearCallingIdentity();

    try {
        synchronized (mGlobalLock) {
            boolean layoutNeeded = false;
            final int displayCount = mRoot.mChildren.size();
            
            // 遍历 RootWindowContainer 下的所有 DisplayContent
            for (int i = 0; i < displayCount; ++i) {
                final DisplayContent displayContent = mRoot.mChildren.get(i);
                
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
                // 1. DisplayContent 更新旋转
                final boolean rotationChanged = displayContent.updateRotationUnchecked();
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

                if (rotationChanged) {
                    mAtmService.getTaskChangeNotificationController()
                            .notifyOnActivityRotation(displayContent.mDisplayId);
                }

                // 对于屏幕旋转动画来说,rotationChanged 为 true,并且还会请求一个 Transition
                // 因此,pendingRemoteDisplayChange 为 true
                final boolean pendingRemoteDisplayChange = rotationChanged
                        && (displayContent.mRemoteDisplayChangeController
                                .isWaitingForRemoteDisplayChange()
                        || displayContent.mTransitionController.isCollecting());
                
                // Even if alwaysSend, we are waiting for a transition or remote to provide
                // updated configuration, so we can't update configuration yet.
                if (!pendingRemoteDisplayChange) {
                    // ...
                }
            }

            if (layoutNeeded) {
                // ...
            }
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
}

WindowManagerService 遍历 RootWindowContainer 下的所有 DisplayContent,为它们更新旋转方向

// DisplayContent.java

boolean updateRotationUnchecked() {
    // 交给 DisplayRotation 处理
    return mDisplayRotation.updateRotationUnchecked(false /* forceUpdate */);
}

DisplayContent 把更新旋转方向的任务,交给 DisplayRotation 处理

// DisplayRotation.java

// 参数 forceUpdate 为 false
boolean updateRotationUnchecked(boolean forceUpdate) {
    final int displayId = mDisplayContent.getDisplayId();
    if (!forceUpdate) {
        // ... 省略一段推迟更新旋转的代码
    }

    if (!mService.mDisplayEnabled) {
        ProtoLog.v(WM_DEBUG_ORIENTATION, "Deferring rotation, display is not enabled.");
        return false;
    }

    final int oldRotation = mRotation;
    final int lastOrientation = mLastOrientation;
    
    // 1. 计算系统的旋转方向
    int rotation = rotationForOrientation(lastOrientation, oldRotation);
    
    // Use the saved rotation for tabletop mode, if set.
    if (mFoldController != null && mFoldController.shouldRevertOverriddenRotation()) {
        // ...
    }

    if (DisplayRotationCoordinator.isSecondaryInternalDisplay(mDisplayContent)
            && mDeviceStateController
                    .shouldMatchBuiltInDisplayOrientationToReverseDefaultDisplay()) {
        // ...
    }

    ProtoLog.v(WM_DEBUG_ORIENTATION,
            "Computed rotation=%s (%d) for display id=%d based on lastOrientation=%s (%d) and "
                    + "oldRotation=%s (%d)",
            Surface.rotationToString(rotation), rotation,
            displayId,
            ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
            Surface.rotationToString(oldRotation), oldRotation);

    ProtoLog.v(WM_DEBUG_ORIENTATION,
            "Display id=%d selected orientation %s (%d), got rotation %s (%d)", displayId,
            ActivityInfo.screenOrientationToString(lastOrientation), lastOrientation,
            Surface.rotationToString(rotation), rotation);

    if (oldRotation == rotation) 
        return false;
    }

    if (isDefaultDisplay) {
        mDisplayRotationCoordinator.onDefaultDisplayRotationChanged(rotation);
    }

    // 打开 shell transition 的情况下,RecentsAnimationController 代表的近期任务动动画已经废弃
    final RecentsAnimationController recentsAnimationController =
            mService.getRecentsAnimationController();
    if (recentsAnimationController != null) {
        recentsAnimationController.cancelAnimationForDisplayChange();
    }

    ProtoLog.v(WM_DEBUG_ORIENTATION,
            "Display id=%d rotation changed to %d from %d, lastOrientation=%d",
                    displayId, rotation, oldRotation, lastOrientation);

      
    // 2. 保存数据
    // 保存计算出来的旋转方向
    mRotation = rotation;
    // 标记 DisplayContent 需要 layout
    mDisplayContent.setLayoutNeeded();
    // 标记 DisplayContent 在等待配置更新
    mDisplayContent.mWaitingForConfig = true;

    // shell transition 打开的情况下,走这里
    if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
        // 假设此时没有 transition 正处于收集状态,因此 wasCollecting 为 false
        final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
        
        // 创建 TransitionRequestInfo.DisplayChange 保存旋转前后的方向
        final TransitionRequestInfo.DisplayChange change = wasCollecting ? null
                : new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
                        oldRotation, mRotation);

        // 3. 由 DisplayContent 请求 change transition
        mDisplayContent.requestChangeTransitionIfNeeded(
                ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
        
        if (wasCollecting) {
            // ...
        }
        
        return true;
    }

    // ...
}

DisplayRotation 更新系统旋转方向

  1. 计算系统新的旋转方向。这里会综合各方因素,计算出系统的旋转方向。这些因素包括 sensor 上报的方向,Activity 申请的方向,等等。
  2. 更新一些状态变量。这些变量都有非常重要的作用,DisplayRotation#mRotation 保存系统当前的旋转方向。DisplayContent#mLayoutNeeded 更新为 true,表示 DisplayContent 需要 layout,在 WMS 大刷新的时候,会对 DisplayContent 下的窗口重新 layout。DisplayContent#mWaitingForConfig 更新为 true,表示 DisplayContent 正在等待配置更新完毕,当配置更新完成后,才会把这个变量更新为 false,而如果配置一直没有更新完成,那么 WMS 是无法执行大刷新的,这就会导致屏幕的界面一直不刷新。
  3. 创建一个 TransitionRequestInfo.DisplayChange 对象,保存旋转前后的方向,然后由 DisplayContent 发起一个 change transition 请求。

计算系统旋转方向

根据案例,可以知道如下信息

  1. 屏幕旋转方向是打开的。
  2. 把手机从竖屏旋转到横屏,sensor 上报的方向是 1。
  3. MainActivity 在 AndroidManifes 中没有声明方向,并且在代码中也没有动态请求方向,因此它提供的方向是 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED

根据这些信息,来看下系统是如何计算旋转方向的

// DisplayRotation.java

// orientation 为 activity 请求的方向,目前为 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
int rotationForOrientation(@ScreenOrientation int orientation,
        @Surface.Rotation int lastRotation) {
    ProtoLog.v(WM_DEBUG_ORIENTATION,
            "rotationForOrientation(orient=%s (%d), last=%s (%d)); user=%s (%d) %s",
            ActivityInfo.screenOrientationToString(orientation), orientation,
            Surface.rotationToString(lastRotation), lastRotation,
            Surface.rotationToString(mUserRotation), mUserRotation,
            mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
                    ? "USER_ROTATION_LOCKED" : "");
    if (isFixedToUserRotation()) {
        return mUserRotation;
    }


    // 1. 获取sensor方向,一般来说,就是 sensor 上报的方向
    int sensorRotation = mOrientationListener != null
            ? mOrientationListener.getProposedRotation() // may be -1
            : -1;
    if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
        sensorRotation = -1;
    }
    if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
        sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
    }
    mLastSensorRotation = sensorRotation;
    if (sensorRotation < 0) {
        sensorRotation = lastRotation;
    }


    final int lidState = mDisplayPolicy.getLidState();
    final int dockMode = mDisplayPolicy.getDockMode();
    final boolean hdmiPlugged = mDisplayPolicy.isHdmiPlugged();
    final boolean carDockEnablesAccelerometer =
            mDisplayPolicy.isCarDockEnablesAccelerometer();
    final boolean deskDockEnablesAccelerometer =
            mDisplayPolicy.isDeskDockEnablesAccelerometer();
    @Surface.Rotation
    final int preferredRotation;
    if (/* QTI_BEGIN */ !(overrideMirroring && isBuiltin) && /* QTI_END */ !isDefaultDisplay) {

    } else if (lidState == LID_OPEN && mLidOpenRotation >= 0) {

    } else if (dockMode == Intent.EXTRA_DOCK_STATE_CAR
            && (carDockEnablesAccelerometer || mCarDockRotation >= 0)) {

    } else if ((dockMode == Intent.EXTRA_DOCK_STATE_DESK
            || dockMode == Intent.EXTRA_DOCK_STATE_LE_DESK
            || dockMode == Intent.EXTRA_DOCK_STATE_HE_DESK)
            && (deskDockEnablesAccelerometer || mDeskDockRotation >= 0)
            && !(orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED
                    || orientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR)) {

    } else if ((hdmiPlugged || mWifiDisplayConnected) && mDemoHdmiRotationLock) {

    } else if (mWifiDisplayConnected && (mWifiDisplayRotation > -1)) {

    } else if (hdmiPlugged && dockMode == Intent.EXTRA_DOCK_STATE_UNDOCKED
            && mUndockedHdmiRotation >= 0) {

    } else if (mDemoRotationLock) {

    } else if (mDisplayPolicy.isPersistentVrModeEnabled()) {

    } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LOCKED) {

    } else if (!mSupportAutoRotation) {

    } 
    // mUserRotationMode 是旋转开关的状态
    // orientation 是 activity 请求的方向,目前为 SCREEN_ORIENTATION_UNSPECIFIED
    else if (((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE
                        || isTabletopAutoRotateOverrideEnabled())
                    && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER
                            || orientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
                            || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
                            || orientation == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
                            || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER))
            || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR
            || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
            || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
            || orientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
        // Otherwise, use sensor only if requested by the application or enabled
        // by default for USER or UNSPECIFIED modes.  Does not apply to NOSENSOR.
        if (sensorRotation != Surface.ROTATION_180
                || getAllowAllRotations() == ALLOW_ALL_ROTATIONS_ENABLED
                || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
                || orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_USER) {
            // 2. 使用 sensor 上报的方向作为优先的屏幕旋转方向
            preferredRotation = sensorRotation;
        } else {
            
        }
    } else if (mUserRotationMode == WindowManagerPolicy.USER_ROTATION_LOCKED
            && orientation != ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
            && orientation != ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
            && orientation != ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
            && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
            && orientation != ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
        
    } else {
       
    }
    switch (orientation) {
        // ...

        default:
            // For USER, UNSPECIFIED, NOSENSOR, SENSOR and FULL_SENSOR,
            // just return the preferred orientation we already calculated.
            if (preferredRotation >= 0) {
                // 3. 使用优先的屏幕旋转方向,作为最终的屏幕旋转方向。
                return preferredRotation;
            }
            return Surface.ROTATION_0;
    }
}

对于目前分析的案例情况来说,在旋转开关打开,并且 MainActivity 请求的方向为 SCREEN_ORIENTATION_UNSPECIFIED 的情况下,系统的旋转方向取自 sensor 上报的方向。由于是从竖屏旋转到横屏,sensor 上报的是1,那么系统旋转方向此时也是1,也就是 Surface.ROTATION_90。

请求 change transition

根据前面分析,当计算完系统旋转方向后,创建了一个 TransitionRequestInfo.DisplayChange 对象,保存了旋转前后的方向,然后由 DisplayContent 请求一个 change transition,如下

// DisplayContent.java

// 参数 changes 值为 ActivityInfo.CONFIG_WINDOW_CONFIGURATION
// 参数 displayChange 保存旋转前后的方向
void requestChangeTransitionIfNeeded(@ActivityInfo.Config int changes,
        @Nullable TransitionRequestInfo.DisplayChange displayChange) {
    if (!mLastHasContent) return;
    final TransitionController controller = mTransitionController;
    if (controller.isCollecting()) {
        // ...
    }
    
    // 1. 通过 TransitionController 请求类型为 TRANSIT_CHANGE 的 transition
    final Transition t = controller.requestTransitionIfNeeded(TRANSIT_CHANGE, 0 /* flags */,
            this, this, null /* remoteTransition */, displayChange);
    
    if (t != null) {
        mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
        if (mFixedRotationLaunchingApp != null) {
            // ... 屏幕旋转动画,不涉及 Fixed Rotation
        } else if (isRotationChanging()) {
            if (displayChange != null) {
                // 省略无缝旋转的逻辑...
            }
            mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
            controller.mTransitionMetricsReporter.associate(t.getToken(),
                    startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
            
            // 2. 启动异步旋转
            startAsyncRotation(false /* shouldDebounce */);
        }
        
        // 3. Transition 保存 WindowContainer 的 change
        // 此时的 WindowContainer 就是 DisplayContent
        // changes 的值为 ActivityInfo.CONFIG_WINDOW_CONFIGURATION
        t.setKnownConfigChanges(this, changes);
    }
}

DisplayContent 请求 change transition

  1. 通过 TransitionController 请求一个 type 为 TRANSIT_CHANGE 的 transition。
  2. 启动异步旋转。何为异步旋转?例如,状态栏,导航栏,它们是不跟随 Transition 做旋转动画的,而是单独做动画的,因此称之为异步旋转。
  3. Transition 保存 DisplayContent 已知的 change 信息,其实就是在 Transition#mChanges 中,为 DisplayContent 更新 ChangeInfo.mKnownConfigChangesActivityInfo.CONFIG_WINDOW_CONFIGURATION

关于异步旋转,也是一个比较有趣的研究课题,我在工作中遇到过比较难解的异步旋转问题。 后面如果有时间,我会写一些关于异步旋转的文章。

继续看第1步,现在请求 change transition 的任务交给了 TransitionController,如下

// TransitionController.java

// type 为 TRANSIT_CHANGE
// flags 为 0
// trigger 为 DisplayContent
// readyGroupRef 为 DisplayContent
// remoteTransition 为 null
// displayChange 保存了旋转前后的方向
Transition requestTransitionIfNeeded(@WindowManager.TransitionType int type,
        @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger,
        @NonNull WindowContainer readyGroupRef, @Nullable RemoteTransition remoteTransition,
        @Nullable TransitionRequestInfo.DisplayChange displayChange) {
    if (mTransitionPlayer == null) {
        return null;
    }
    Transition newTransition = null;
    if (isCollecting()) {
        // ...
    } else {
        // 1. createTransition()创建 Transition,然后向 WMShell 发起 transition 请求
        // 向 WMShell 发起请求的函数,参考下面函数
        newTransition = requestStartTransition(createTransition(type, flags),
                trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
        if (newTransition != null && displayChange != null && trigger != null
                && trigger.asDisplayContent() != null) {
            // 如果做了180度旋转,那么 SyncGroup.mSyncMethod 更新为 METHOD_BLAST
            // 它表示 app 绘制完成后,需要把 buffer 发送到 wms 进行同步 apply
            setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
        }
    }
    
    // trigger 代表触发此时 transition 的 WindowContainer
    // 此时它的值为 DisplayContent
    if (trigger != null) {
        if (isExistenceType(type)) {
            // ...
        } else {
            // 2.Transition 收集 DisplayContent
            collect(trigger);
        }
    }
    return newTransition;
}

// 向 WMShell 发起 transition 请求
Transition requestStartTransition(@NonNull Transition transition, @Nullable Task startTask,
        @Nullable RemoteTransition remoteTransition,
        @Nullable TransitionRequestInfo.DisplayChange displayChange) {
    if (mIsWaitingForDisplayEnabled) {
        
    }
    if (mTransitionPlayer == null || transition.isAborted()) {
        
    }
    
    try {
        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                "Requesting StartTransition: %s", transition);
        ActivityManager.RunningTaskInfo info = null;
        // startTask 为 null
        if (startTask != null) {
            // ...
        }
        
        // 创建 TransitionRequestInfo,保存 Transition 信息
        // transition.mType 值为 TRANSIT_CHANGE
        // info 是 task 信息,此时为 null
        // remoteTransition 为 null
        // displayChange 仅仅保存了旋转前后的方向
        final TransitionRequestInfo request = new TransitionRequestInfo(
                transition.mType, info, remoteTransition, displayChange);
        transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos();
        transition.mLogger.mRequest = request;
        
        // 向 wm-shell 发起 transition 请求
        mTransitionPlayer.requestStartTransition(transition.getToken(), request);
        if (remoteTransition != null) {
            
        }
    } catch (RemoteException e) {
        
    }
    return transition;
}

TransitionController 请求 transition

  1. 通过 createTransition() 创建 Transition,并向 WMShell 发起 transition 请求。
  2. Transition 收集触发此次 Transition 的 WindowContainer,也就是 DisplayContent。

在后面的分析中,我用 WC 代替 WindowContainer。

创建 Transition

// TransitionController.java

// type 值为 TRANSIT_CHANGE
// flags 是 0
private Transition createTransition(@WindowManager.TransitionType int type,
        @WindowManager.TransitionFlags int flags) {
    // ...
    
    // 1. 创建 Transition 对象
    Transition transit = new Transition(type, flags, this, mSyncEngine);
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Transition: %s", transit);
    // 2. Transition 进入 collecting 状态
    moveToCollecting(transit);
    return transit;
}

TransitionController 创建 transition 的过程,就是创建 Transition 对象,并让 Transition 进入收集状态,如下

// TransitionController.java

void moveToCollecting(@NonNull Transition transition) {
    // ...
    if (mTransitionPlayer == null) {
        // ...
    }
    
    // 1.TransitionController#mCollectingTransition 保存正在收集状态的 Transition
    mCollectingTransition = transition;
    
    // Transition 收集,也有超时时间,对于旋转来说,是2s,其他都是5s
    // Distinguish change type because the response time is usually expected to be not too long.
    final long timeoutMs =
            transition.mType == TRANSIT_CHANGE ? CHANGE_TIMEOUT_MS : DEFAULT_TIMEOUT_MS;
    
    // 2.Transition 开始收集
    mCollectingTransition.startCollecting(timeoutMs);
    
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Start collecting in Transition: %s",
            mCollectingTransition);
    dispatchLegacyAppTransitionPending();
}

TransitionController#mCollectingTransition 保存处于收集状态的 Transition,然后让这个 Transition 开始收集,如下

// Transition.java

void startCollecting(long timeoutMs) {
    if (mState != STATE_PENDING) {
        throw new IllegalStateException("Attempting to re-use a transition");
    }
    
    // 1.Transition 把状态切换到 STATE_COLLECTING
    mState = STATE_COLLECTING;
    
    // 2. 为 Transition 创建对应的 SyncGroup
    // 返回的 sync id,就是唯一代表 Transition 的 SyncGroup
    mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG,
            mParallelCollectType != PARALLEL_TYPE_NONE);
    // TransitionController.SYNC_METHOD 是一个三元表达式,默认值为 BLASTSyncEngine.METHOD_NONE
    mSyncEngine.setSyncMethod(mSyncId, TransitionController.SYNC_METHOD);

    mLogger.mSyncId = mSyncId;
    mLogger.mCollectTimeNs = SystemClock.elapsedRealtimeNanos();
}

Transition 进入收集状态时,首先把 Transition#mState 切换到 STATE_COLLECTING,然后通知 BLASTSyncEngine 创建一个 SyncGroup,如下

// BLASTSyncEngine.java

// listener 是 Transition,也就是说,SyncGroup 监听者是 Transition
// parallel 为 false
int startSyncSet(TransactionReadyListener listener, long timeoutMs, String name,
        boolean parallel) {
    // 1. 其实就是 new 一个 SyncGroup 对象
    final SyncGroup s = prepareSyncSet(listener, name);
    // 启动 SyncGroup
    startSyncSet(s, timeoutMs, parallel);
    return s.mSyncId;
}

void startSyncSet(SyncGroup s, long timeoutMs, boolean parallel) {
    final boolean alreadyRunning = mActiveSyncs.size() > 0;
    if (!parallel && alreadyRunning) {
        // We only support overlapping syncs when explicitly declared `parallel`.
        Slog.e(TAG, "SyncGroup " + s.mSyncId
                + ": Started when there is other active SyncGroup");
    }
    // 2.BLASTSyncEngine#mActiveSyncs 保存已经创建的 SyncGroup
    mActiveSyncs.add(s);
    // 参数 parallel 此时为 false
    s.mIgnoreIndirectMembers = parallel;
    ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started %sfor listener: %s",
            s.mSyncId, (parallel && alreadyRunning ? "(in parallel) " : ""), s.mListener);
            
    // 3. 发送一个超时消息
    scheduleTimeout(s, timeoutMs);
}

BLASTSyncEngine 启动 SyncGroup 过程

  1. new 一个 SyncGroup 对象。在 SyncGroup 构造函数中,保存了参数 listener,它的类型为 TransactionReadyListener ,实现者为 Transition。
  2. BLASTSyncEngine#mActiveSyncs 保存刚创建的 SyncGroup。
  3. SyncGroup 收集 WC 是有时间限制的,因此需要发送一个超时消息。如果 SyncGroup 收集 WC 超时了,那么 WMS 会强制动画进入下一个流程,保证动画能执行,否则可能会看到界面卡住的现象。

那么,SyncGroup 的作用是什么呢?先看下官方解释

// BLASTSyncEngine.java

/**
 * Holds state associated with a single synchronous set of operations.
 */
class SyncGroup {}

官方的解释是, SyncGroup 保存了一个同步操作集合的状态?这个解释,晦涩难懂。SyncGroup 从字面意思看,就是一个同步组,它会收集需要同步的 WC,然后等待 WC 下的窗口都绘制完毕,然后执行动画的下一步。

Transition 收集 WC

根据前面的分析,创建并请求 Tranition 后,会收集触发此次 Transition 的 WC,这个 WC 此时是 DisplayContent。

// TransitionController.java

// wc 值为 DisplayContent
void collect(@NonNull WindowContainer wc) {
    if (mCollectingTransition == null) return;
    // 由 Transition 进行收集
    mCollectingTransition.collect(wc);
}
// Transition.java

void collect(@NonNull WindowContainer wc) {
    if (mState < STATE_COLLECTING) {
        throw new IllegalStateException("Transition hasn't started collecting.");
    }
    
    if (!isCollecting()) {
        return;
    }
    
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
            mSyncId, wc);
    
    // 1.向上遍历 WC 的 parent,找到可以做动画的 parent
    // DisplayContent 的 parent 是 RootWindowContainer ,它是不能做动画的
    // "snapshot" all parents (as potential promotion targets). Do this before checking
    // if this is already a participant in case it has since been re-parented.
    for (WindowContainer<?> curr = getAnimatableParent(wc);
            curr != null && !mChanges.containsKey(curr);
            curr = getAnimatableParent(curr)) {
        // 为可做动画的 parent,在 TransitionController#mChanges 中建立 ChangeInfo 信息
        final ChangeInfo info = new ChangeInfo(curr);
        updateTransientFlags(info);
        mChanges.put(curr, info);
        // 如果 parent 是 ready-group,那么还要保存到 TransitionController#mReadyTracker 中
        // 简单来说, ready-group 就是 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);
        }
    }

    if (mParticipants.contains(wc)) return;
    
    // 2.如果需要同步,把 WC 加入到 SyncGroup 中
    final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
            // Transient-hide may be hidden later, so no need to request redraw.
            && !isInTransientHide(wc);
    if (needSync) {
        mSyncEngine.addToSyncSet(mSyncId, wc);
    }
    
    // 3. 为 WC 建立 ChangeInfo,并保存到 Transition#mChanges 
    ChangeInfo info = mChanges.get(wc);
    if (info == null) {
        // 注意,ChangeInfo 保存的是 WindowContainer 当前的状态
        // 不是改变后的状态
        info = new ChangeInfo(wc);
        updateTransientFlags(info);
        mChanges.put(wc, info);
    }
    
    // 4.Transition#mParticipants 保存 Transition 的参与者
    mParticipants.add(wc);
    
    // 5.Transition 第一次收集 WindowContainer 时
    // 会使用 mOnTopTasksStart 保存 top root task 及其 children task
    recordDisplay(wc.getDisplayContent());
    
    // 6. 如果 WC 还需要显示壁纸,Transition 还会收集壁纸的 WindowToken
    if (info.mShowWallpaper) {
        // ...
    }
}

由于此时的 WC 是 DisplayContent,因此这里只列举下 Transition 收集 DisplayContent 的主要过程

  1. DisplayContent 是需要同步的,因此被添加到 SyncGroup 中。
  2. Transition#mChange 为 DisplayContent 建立 ChangeInfo。注意,ChangeInfo 保存的是 WC 的当前状态。
  3. Transition#mParticipants 保存动画的参与者 DisplayContent。
SyncGroup 收集 WC

根据前面的分析,SyncGroup 收集了需要同步的 WC,也就是 DisplayContent

// BLASTSyncEngine.java

void addToSyncSet(int id, WindowContainer wc) {
    // 保存到 SyncGroup 中
    getSyncGroup(id).addToSync(wc);
}

class SyncGroup {
    private void addToSync(WindowContainer wc) {
        if (mRootMembers.contains(wc)) {
            return;
        }
        
        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Adding to group: %s", mSyncId, wc);
        
        // WindowContainer 的 SyncGroup 不仅仅自己保存的 SyncGroup,还包括 parent 的 SyncGroup
        final SyncGroup dependency = wc.getSyncGroup();
        if (dependency != null && dependency != this && !dependency.isIgnoring(wc)) {
            // ... 不分析 SyncGroup 相互依赖的情况
        } else {
            // 1. SyncGroup 和 WC 相互关联
            // SyncGroup#mRootMembers 保存需要同步的根成员
            mRootMembers.add(wc);
            // WindowContainer 保存 SyncGroup
            wc.setSyncGroup(this);
        }
        
        // 3. WindowContainer 进入同步准备
        wc.prepareSync();
        
        // 此时还没有 ready
        if (mReady) {
            
        }
    }
}

SyncGroup 保存 WC,一般来说,过程如下

  1. SyncGroup 与 WC 相互关联。SyncGroup#mRootMembers 保存需要同步的 WC, WindowContainer#mSyncGroup 保存相关联的 SyncGroup。
  2. 调用 WindowContainer#prepareSync() 让 WC 准备同步。所谓的 WC 准备同步,就是让 WC 及其 children 进入同步状态。

此时, WC 是 DisplayContent,因此接下来看下 DisplayContent 的准备同步的过程。但是,首先看下基类的方法如何实现同步的

// WindowContainer.java

boolean prepareSync() {
    // 已经处于同步状态,那就不需要执行同步准备了,返回 false
    if (mSyncState != SYNC_STATE_NONE) {
        // Already part of sync
        return false;
    }
    
    for (int i = getChildCount() - 1; i >= 0; --i) {
        final WindowContainer child = getChildAt(i);
        child.prepareSync();
    }
    mSyncState = SYNC_STATE_READY;
    return true;
}

基类的同步方法的逻辑是,递归地让 children 执行准备同步,然后把自己的同步状态 mSyncState 切换到 SYNC_STATE_READY。也就是说,对某个 WC 执行准备同步,那么以 WC 为根的树,树上的所有的节点 WC 都会执行准备同步。

DisplayContent 并没有实现自己的同步准备方法,因此 DisplayContent 的同步状态 mSyncState,最终会切换到 SYNC_STATE_READY,同时也会递归地让它所有的 children 也做同步准备。

目前,只有 WindowToken 和 WindowState 复写这个方法,因此,重点看下这两个类的方法,就可以知道 DisplayContent 的同步准备的实现。

首先看下 WindowToken 的准备同步

// WindowToken.java

boolean prepareSync() {
    // 如果发生旋转,并且 WindowToken 可以做异步旋转,那么是不需要执行同步准备
    if (mDisplayContent != null && mDisplayContent.isRotationChanging()
            && AsyncRotationController.canBeAsync(this)) {
        return false;
    }
    return super.prepareSync();
}

对于 WindowToken 来说,如果发生旋转,并且 WindowToken 可以做异步旋转,那么是不需要执行同步准备的。一般来说,像状态栏、导航栏这样的非 app 窗口,是做异步旋转的,它们的 WindowToken 是不需要执行同步准备的。也就是说,异步旋转的 WindowToken 及其 children WindowState ,在系统发生旋转的时候,它们的同步状态保持为 SYNC_STATE_NONE

代表 Activity 的 ActivityRecord 是 WindowToken 的子类,它是一个 app 窗口,是需要执行同步准备的。那么,ActivityRecord 及其 children WindowState,都是需要执行同步准备的。 由于 WindowToken 使用基类函数实现同步准备,因此 ActivityRecord 的同步状态是切换到了 SYNC_STATE_READY。那么 WindowState 是如何实现同步准备,同步状态是如何切换的呢?如下

// WindowState.java

boolean prepareSync() {
    // TODO: DrawHandler 实际作用是什么?
    if (!mDrawHandlers.isEmpty()) {
        Slog.w(TAG, "prepareSync with mDrawHandlers, " + this + ", " + Debug.getCallers(8));
    }
    
    // 1. 利用基类检测是否已经处于同步状态
    // 如果是,返回 false
    if (!super.prepareSync()) {
        return false;
    }
    
    if (mIsWallpaper) {
        return false;
    }
    
    // In the WindowContainer implementation we immediately mark ready
    // since a generic WindowContainer only needs to wait for its
    // children to finish and is immediately ready from its own
    // perspective but at the WindowState level we need to wait for ourselves
    // to draw even if the children draw first or don't need to sync, so we start
    // in WAITING state rather than READY.
    // 2. WindowState 的同步状态切换到 SYNC_STATE_WAITING_FOR_DRAW
    mSyncState = SYNC_STATE_WAITING_FOR_DRAW;

    if (mPrepareSyncSeqId > 0) {
        // 处理已经处于 blast sync 中的情况
    }

    // 同步序列号+1
    mSyncSeqId++;
    
    // 3. 请求窗口重绘
    // BLAST sync 一般是设置给 top window,大致有两种情况
    // 一种情况是发生无缝旋转动画,另外一种情况是 fixed rotation 时发生旋转
    if (getSyncMethod() == BLASTSyncEngine.METHOD_BLAST) {
        // ... 本文不讨论 BLAST sync 的重绘方式 ...
    } 
    // 对于非 blast sync。,只有窗口绘制完成了,才能请求重绘
    else if (mHasSurface && mWinAnimator.mDrawState != DRAW_PENDING) {
        // Only need to request redraw if the window has reported draw.
        // 所谓请求重绘,就是把 WindowState#mRedrawForSyncReported 设置为 false
        requestRedrawForSync();
    }
    return true;
}

WindowState 的同步准备

  1. 通过基类函数,检测是否已经处于同步状态了。每一个 WC 的同步状态默认为 SYNC_STATE_NONE,只有这个状态才代表不处于同步状态。
  2. 把 WindowState 的同步状态 mSyncState 更新为 SYNC_STATE_WAITING_FOR_DRAW,表示需要 SyncGroup 等待重绘。
  3. 使用 requestRedrawForSync() 请求窗口重绘,其实就是把 WindowState#mRedrawForSyncReported 设置为 false。一般来说,只有 top activity 的窗口才会被标记为请求重绘。

BLAST sync 的意思是,app 绘制完成后,把绘制 buffer 同步到 wms 进行 apply。由于屏幕旋转动画在执行的过程中,会显示一个截图层在最上面,因此无论是否使用 BLAST sync 绘制,

现在总结下,对于屏幕旋转动画来说,DisplayContent 的同步过程如下

  1. 异步旋转的 WindowToken 及其 children WindowState 不参与同步准备。
  2. 其他的 WindowState 的同步状态 mSyncState 更新为 SYNC_STATE_WAITING_FOR_DRAW
  3. 其他的 WC 的同步状态 mSyncState 都是切换到 SYNC_STATE_READY

WMShell 处理 transition 请求

根据前面的分析,WMCore 向 WMShell 发起了一个 transition 请求,现在来看下 WMShell 是如何处理这个 transition 请求

// Transitions.java

void requestStartTransition(@NonNull IBinder transitionToken,
        @Nullable TransitionRequestInfo request) {
    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
            transitionToken, request);
    
    if (isTransitionKnown(transitionToken)) {
        throw new RuntimeException("Transition already started " + transitionToken);
    }
    
    // 在 WMShell 中, transition 由 ActiveTransition 代表
    final ActiveTransition active = new ActiveTransition();

    WindowContainerTransaction wct = null;

    if (request.getType() == TRANSIT_SLEEP) {
        // ...
    } else {
        // 1. 让所有的 TransitionHandler 处理 transition 请求
        for (int i = mHandlers.size() - 1; i >= 0; --i) {
            // 如果某一个 TransitionHandler 处理了请求,那么会返回一个非空的 WindowContainerTransaction
            wct = mHandlers.get(i).handleRequest(transitionToken, request);
            if (wct != null) {
                // active.mHandler 保存的 TransitionHandler,在后面会优先用来执行动画
                active.mHandler = mHandlers.get(i);
                break;
            }
        }
        
        
        if (request.getDisplayChange() != null) {
            TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
            if (change.getEndRotation() != change.getStartRotation()) {
                if (wct == null) {
                    wct = new WindowContainerTransaction();
                }
                // 2. 通知对 display change 感兴趣的监听者
                // 例如,如果当前处于分屏,那么分屏会对这个感兴趣
                mDisplayController.onDisplayRotateRequested(wct, change.getDisplayId(),
                        change.getStartRotation(), change.getEndRotation());
            }
        }
    }
    
    if (request.getType() == TRANSIT_KEYGUARD_OCCLUDE && request.getTriggerTask() != null
            && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FREEFORM) {
        // ...
    }
    
    // 3. 通知 wm-core 去 start transition
    mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
    
    // ActiveTransition#mToken 保存 transition token
    active.mToken = transitionToken;
    // 4. mPendingTransitions 保存 ActiveTransition
    mPendingTransitions.add(0, active);
}

WMShell 处理屏幕旋转 transition 过程

  1. 让所有 TransitionHandler 处理这个请求,如果某一个 TransitionHandler 对这个请求感兴趣,会返回一个非空的 WindowContainerTransaction 。这个 WindowContainerTransaction 是在 WMCore 的 start transition 流程中 apply 的。因此,如果 TransitionHandler 有什么需要 WMCore 执行的操作,那么需要在 WindowContainerTransaction 中保存操作的数据。对于屏幕旋转动画来说,目前没有任何 TransitionHandler 对这个 transition 请求感兴趣。
  2. 通知对屏幕旋转感兴趣的监听者。例如,如果当前处于分屏模式,那么分屏的 TransitionHandler,也就是 StageCoordinator ,会处理屏幕旋转的情况。
  3. 通知 WMCore 执行 start transition。
  4. 在 WMShell 中,ActiveTransition 代表一个 transition 动画,Transitions#mPendingTransitions 保存了即将被执行的 transition 动画 。其中 ,ActiveTransition#mToken 指向了 WMCore 侧的 Transition, ActiveTransition#mHanlder 指向了对 transition 请求感兴趣的 TransitionHandler,当执行动画的时候,WMShell 会优先使用它执行动画。

后面用 WCT 代替 WindowContainerTransaction。

WMCore start transition

WMShell 处理了 transition 请求后,向 WMCore 发起了 start transition,如下

// WindowOrganizerController.java

// t 虽然不为 null,但是数据为空
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) {
    enforceTaskPermission("startTransition()");
    final CallerInfo caller = new CallerInfo();
    final long ident = Binder.clearCallingIdentity();
    try {
        synchronized (mGlobalLock) {
            // 根据 token 找到 Transition
            Transition transition = Transition.fromBinder(transitionToken);
            
            if (mTransitionController.getTransitionPlayer() == null && transition == null) {
                
            }
            
            final WindowContainerTransaction wct =
                    t != null ? t : new WindowContainerTransaction();
            if (transition == null) {
                
            }
            if (!transition.isCollecting() && !transition.isForcePlaying()) {
                
            }
            
            // 1. start transition
            transition.start();
            
            transition.mLogger.mStartWCT = wct;
            
            // 2. apply WindowContainerTransaction
            applyTransaction(wct, -1 /*syncId*/, transition, caller);
            
            return transition.getToken();
        }
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}    

WMCore 的 start transition

  1. 执行 Transition#start() 启动 Transition,主要就是把 Transition 的状态切换到 STATE_STARTED
  2. apply WCT。对于屏幕旋转动画来说,WCT 中没有包含任何数据,但是这一步仍然有事情要做,那就是更新配置。

来看看 apply WCT 的执行流程

// WindowOrganizerController.java

private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
        @Nullable Transition transition, @NonNull CallerInfo caller) {
    return applyTransaction(t, syncId, transition, caller, null /* finishTransition */);
}

private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
        @Nullable Transition transition, @NonNull CallerInfo caller,
        @Nullable Transition finishTransition) {
    int effects = TRANSACT_EFFECTS_NONE;
    ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);
    // 1. 推迟旋转以及可见性更新
    mService.deferWindowLayout();
    mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);
    try {
    
        // 2. apply display change
        if (transition != null) {
            transition.applyDisplayChangeIfNeeded();
        }
        
        // 处理 WCT,WCT 数据此时为空
        final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
        final int hopSize = hops.size();
        final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
        Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
                t.getChanges().entrySet().iterator();
        while (entries.hasNext()) {
            // ...
        }
        
        // Hierarchy changes
        if (hopSize > 0) {
            // ...
        }
        // Queue-up bounds-change transactions for tasks which are now organized. Do
        // this after hierarchy ops so we have the final organized state.
        entries = t.getChanges().entrySet().iterator();
        while (entries.hasNext()) {
            // ...
        }
        
        if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
            // ...
        } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
            // ....
        }
        if (effects != 0) {
            // ...
        }
    } finally {
        // 3. 恢复可见性更新以及layout
        mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
        mService.continueWindowLayout();
    }
    return effects;
}    

对于屏幕旋转动画来说,apply WCT 主要是执行了 Trantiion#applyDisplayChangeIfNeeded(),它会更新配置。

但是,这里其实还需要注意二点事情

  • apply WCT 是被 ActivityTaskManagerService#deferWindowLayout()ActivityTaskManagerService#continueWindowLayout() 包围,因此 apply WCT 的整个过程中,如果需要发起大刷新,或者添加 layout reason,都不会立即执行大刷新,而是在 恢复 layout 时才执行。
  • apply WCT 还被 ActivityTaskSupervisor#setDeferRootVisibilityUpdate(true)ActivityTaskSupervisor#setDeferRootVisibilityUpdate(false) 包围,那么在整个 apply WCT 的过程中,不会执行由 RootWindowContainer 触发的可见性更新,例如 RootWindowContainer#ensureVisibilityAndConfig()

现在来看下 Trantiion#applyDisplayChangeIfNeeded() 更新配置的过程,如下

// Transition.java

void applyDisplayChangeIfNeeded() {
    for (int i = mParticipants.size() - 1; i >= 0; --i) {
        // 1. 检测是否有 DisplayContent 参与 Transition
        final WindowContainer<?> wc = mParticipants.valueAt(i);
        final DisplayContent dc = wc.asDisplayContent();
        if (dc == null || !mChanges.get(dc).hasChanged()) continue;
        
        // 2. 如果有 DisplayContent 参与 Transition,那么 DisplayContent 发送新配置
        dc.sendNewConfiguration();
        
        // 目前 mReadyTracker 还没有被使用
        if (!mReadyTracker.mUsed) {
            // 3. 更新 transition ready 状态为 true
            setReady(dc, true);
        }
    }
}
  1. DisplayContent 必须参与到 Transition 中,并且 DisplayContent 必须有改变。根据前面的分析,这两个条件是成立的。
  2. DisplayContent 发送新配置。
  3. 更新 transition ready 状态为 true。

更新配置

// DisplayContent.java

void sendNewConfiguration() {
    // ...
    
    // 配置更新
    final boolean configUpdated = updateDisplayOverrideConfigurationLocked();
    if (configUpdated) {
        return;
    }

    // ...
}

boolean updateDisplayOverrideConfigurationLocked() {
    final RecentsAnimationController recentsAnimationController =
            mWmService.getRecentsAnimationController();
    if (recentsAnimationController != null) {
        recentsAnimationController.cancelAnimationForDisplayChange();
    }
    
    // 1. 计算新配置
    Configuration values = new Configuration();
    computeScreenConfiguration(values);
    
    mAtmService.mH.sendMessage(PooledLambda.obtainMessage(
            ActivityManagerInternal::updateOomLevelsForDisplay, mAtmService.mAmInternal,
            mDisplayId));
    Settings.System.clearConfiguration(values);
    
    // 2. 执行下一步配置更新
    updateDisplayOverrideConfigurationLocked(values, null /* starting */,
            false /* deferResume */, mAtmService.mTmpUpdateConfigurationResult);
    
    // 3. 返回改变的配置
    return mAtmService.mTmpUpdateConfigurationResult.changes != 0;
}    

既然 DisplayContent 要发送新配置,那么首先得计算新配置。新的配置就是根据新的旋转方向计算出来的,大致看下代码,如下

// DisplayContent.java
void computeScreenConfiguration(Configuration config) {
    // 主要是更新 DisplayContent#mDisplayInfo 
    // 同时也会填充一些配置到参数 config 中
    final DisplayInfo displayInfo = updateDisplayAndOrientation(config);

    // 下面主要就是把新配置填充到 config 中
    final int dw = displayInfo.logicalWidth;
    final int dh = displayInfo.logicalHeight;
    mTmpRect.set(0, 0, dw, dh);
    config.windowConfiguration.setBounds(mTmpRect);
    config.windowConfiguration.setMaxBounds(mTmpRect);
    config.windowConfiguration.setWindowingMode(getWindowingMode());
    config.windowConfiguration.setDisplayWindowingMode(getWindowingMode());

    computeScreenAppConfiguration(config, dw, dh, displayInfo.rotation);
    
    // ...
}

updateDisplayAndOrientation() 更新 display info 的过程,有一个比较重要的知识点,这里需要提一下

// DisplayContent.java

private DisplayInfo updateDisplayAndOrientation(Configuration outConfig) {
    // 根据新方向,重新更新 DisplayContent#mDisplayInfo
    final int rotation = getRotation();
    final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
    final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
    final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
    // ...
    mDisplayInfo.rotation = rotation;
    mDisplayInfo.logicalWidth = dw;
    mDisplayInfo.logicalHeight = dh;
    mDisplayInfo.logicalDensityDpi = mBaseDisplayDensity;
    mDisplayInfo.physicalXDpi = mBaseDisplayPhysicalXDpi;
    mDisplayInfo.physicalYDpi = mBaseDisplayPhysicalYDpi;
    mDisplayInfo.appWidth = appFrame.width();
    mDisplayInfo.appHeight = appFrame.height();
    

    // 把新的 display info 更新到 DisplayManager
    mWmService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
            mDisplayInfo);
    
    if (isDefaultDisplay) {
        mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
                mCompatDisplayMetrics);
    }

    // 更新一些数据,例如 DisplayContent#mDisplayFrames, layout input consumer,等等
    onDisplayInfoChanged();
    return mDisplayInfo;
}

根据新方向更新了 DisplayContent#mDisplayInfo 之后,会把新的 display info 更新到 DisplayManagerService。然后 DMS 会调用 scheduleTraversalLocked(false) 请求 WMS 大刷新。在 WMS 大刷新的 RootWindowContainer#applySurfaceChangesTransaction() 中,会调用 DisplayManagerInternal#performTraversal(syncTransaction) ,这个 syncTransaction 是 DisplayContent 的 sync transaction,在这个 sync transaction 中,会把新的 display info 应用到底层。

但是,前面分析指出过,apply WCT 的过程中,layout 是被推迟了。因此,把新的 display info 更新到 DMS 后,不会立即执行 WMS 大刷新,更不会执行应用 display info 到底层的操作。

新配置计算出来后,利用新配置,执行下一步的配置更新,如下

// DisplayContent.java

// values 为计算出的新配置
// starting 为 null
// deferResume 为 false
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
        ActivityRecord starting, boolean deferResume,
        ActivityTaskManagerService.UpdateConfigurationResult result) {

    int changes = 0;
    boolean kept = true;

    mAtmService.deferWindowLayout();
    try {
        if (values != null) {
            if (mDisplayId == DEFAULT_DISPLAY) {
                // Override configuration of the default display duplicates global config, so
                // we're calling global config update instead for default display. It will also
                // apply the correct override config.
                // 1.更新默认屏幕配置
                changes = mAtmService.updateGlobalConfigurationLocked(values,
                        false /* initLocale */, false /* persistent */,
                        UserHandle.USER_NULL /* userId */);
            } else {
                // ... 本文值关心默认屏幕的配置更新 ...
            }
        }

        if (!deferResume) {
            // 2. 确保 top activity 能够处理新配置,以及更新可见性
            // 由于 apply WCT 时,推迟了可见性更新,因此这里不会执行可见性更新
            kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
        }
    } finally {
        mAtmService.continueWindowLayout();
    }

    if (result != null) {
        result.changes = changes;
        result.activityRelaunched = !kept;
    }
    return kept;
}

对于默认屏幕,它的配置更新过程如下

  1. 由 ActivityTaskManagerService 先计算 global config,再把 global config 发送给 RootWindowContainer ,由 RootWindowContainer 开始递归地更新窗口层级树中每一个节点的配置,这其中自然也包含 DisplayContent 配置的更新。
  2. 当配置更新完成后,根据顶层的 Activity 是否能自己处理改变的配置,从而决定是否对 Activity 进行 relaunch。这里虽然还包含可见性更新,但是根据前面的分析,apply WCT 是推迟了可见性更新,所以这里不会执行可见性更新。

现在来看下默认屏幕的配置更新,从 ActivityTaskManagerService 更新 global config 开始,如下

// ActivityTaskManagerService.java

int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
        boolean persistent, int userId) {
    // 1. 检测 global config 是否有改变
    // mTempConfig 保存 global config
    mTempConfig.setTo(getGlobalConfiguration());
    // mTempConfig 从新计算出的配置中更新
    final int changes = mTempConfig.updateFrom(values);
    
    // global config 必须有变化,否则不会执行配置更新
    if (changes == 0) {
        return 0;
    }

    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateGlobalConfiguration");
    ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
            + "to: %s", values);
    
    if (Process.myUid() == Process.SYSTEM_UID) {
        
    }

    if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
        
    }

    mTempConfig.seq = increaseConfigurationSeqLocked();

    // 这个 log 表示 global config change
    Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);

    mUsageStatsInternal.reportConfigurationChange(mTempConfig, mAmInternal.getCurrentUserId());

    updateShouldShowDialogsLocked(mTempConfig);

    AttributeCache ac = AttributeCache.instance();
    if (ac != null) {
        ac.updateConfiguration(mTempConfig);
    }

    // 2.使用新 global config,更新 system_server 进程的资源
    mSystemThread.applyConfigurationToResources(mTempConfig);

    // persistent 为 false
    if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
        
    }

    // 3.把新 global config 发送给系统中正在运行的进程。
    SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
    for (int i = pidMap.size() - 1; i >= 0; i--) {
        final int pid = pidMap.keyAt(i);
        final WindowProcessController app = pidMap.get(pid);
        ProtoLog.v(WM_DEBUG_CONFIGURATION, "Update process config of %s to new "
                + "config %s", app.mName, mTempConfig);
        app.onConfigurationChanged(mTempConfig);
    }

    // 4.发送广播,通知 global config 改变了
    final Message msg = PooledLambda.obtainMessage(
            ActivityManagerInternal::broadcastGlobalConfigurationChanged,
            mAmInternal, changes, initLocale);
    mH.sendMessage(msg);

    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RootConfigChange");
    // 4. 把新计算出的 global config,发送给 RootWindowContainer,然后窗口层级树就开始更新配置了
    // mTempConfig 是新计算出来的 global config
    mRootWindowContainer.onConfigurationChanged(mTempConfig);
    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    return changes;
}

ActivityTaskManagerService 更新 global config

  1. 检测新配置与 global config 是否有差异,如果有,才进行下一步配置更新。那么问题来了,什么是全局配置( global config )呢? 其实就是 RootWindowContainer#mFullConfiguration。由于 RootWindowContainer 是窗口层级树的根,它的 full config 称之为 global config,很合理。
  2. 使用新 global config,更新 system_server 进程的资源。system_server 进程也有 app 环境,因此它的资源也必须跟随配置的更新而更新。
  3. 把新的 global config 发送给系统中正在运行的进程。注意,top app 进程并不会在此时收到 global config,一般来说,这里会把 global config 发送给不拥有 Activity 的进程。限于篇幅的原因,我不能详细解释配置更新的原理。
  4. 发送 global config changed 广播,从广播数据中可以得知哪些配置改变了。
  5. 把新的 global config,发送给窗口层级树的根节点 RootWindowContainer,然后递归地更新层级树每一个节点 WC 的配置,这其中当然就包括 DisplayContent 的配置更新。

窗口层级树递归地更新配置的逻辑,由基类 ConfigurationContainer#onConfigurationChanged() 完成,如下

// ConfigurationContainer.java

public void onConfigurationChanged(Configuration newParentConfig) {
    // 1. 解析 override config
    // mResolvedTmpConfig 保存当前的 mResolvedOverrideConfiguration
    mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
    // 解析新的 mResolvedOverrideConfiguration
    resolveOverrideConfiguration(newParentConfig);
    
    // 2. 更新 full config
    // mFullConfiguration 首先来自 parent 传下来的 full config
    mFullConfiguration.setTo(newParentConfig);
    mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
    // mFullConfiguration 再从 mResolvedOverrideConfiguration 更新
    mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
    
    // 3. 更新自己及其 children 的 merged override config
    // 如下面函数所示
    onMergedOverrideConfigurationChanged();
    
    // 4. resolved override config 有改变,那么通知监听者
    if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
        for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
            mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
                    mResolvedOverrideConfiguration);
        }
    }
    
    // 5. 通知监听者 merged override config 改变了
    for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
        mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
                mMergedOverrideConfiguration);
    }
    
    // 6. 把 full config 发送给 children
    // 如下面函数所展示
    for (int i = getChildCount() - 1; i >= 0; --i) {
        dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
    }
}

// 更新 mMergedOverrideConfiguration 的逻辑
void onMergedOverrideConfigurationChanged() {
    final ConfigurationContainer parent = getParent();
    if (parent != null) {
        // 有 parent 的情况下
        // WC 的 merged override config 首先来自于 parent merged override config
        // 然后来自于自己的 resolved override config
        mMergedOverrideConfiguration.setTo(parent.getMergedOverrideConfiguration());
        mMergedOverrideConfiguration.windowConfiguration.unsetAlwaysOnTop();
        mMergedOverrideConfiguration.updateFrom(mResolvedOverrideConfiguration);
    } else { 
        // 只有 RootWindowContainer 没有 parent
        // RWC 的 merged override config 就是 resolved override config
        mMergedOverrideConfiguration.setTo(mResolvedOverrideConfiguration);
    }
    
    // 递归更新 children 的 merged override config
    for (int i = getChildCount() - 1; i >= 0; --i) {
        final ConfigurationContainer child = getChildAt(i);
        child.onMergedOverrideConfigurationChanged();
    }
}

// 把配置发送给 children 的逻辑
void dispatchConfigurationToChild(E child, Configuration config) {
    child.onConfigurationChanged(config);
}    

递归更新层级树配置的过程如下

  1. 保存旧的 mResolvedOverrideConfiguration,并解析新的 mResolvedOverrideConfiguration。不同的 WC 可能会覆盖这个解析方法,加入自己的逻辑,但是基本上 mResolvedOverrideConfiguration 是来自于 mRequestedOverrideConfiguration。
  2. 解析 mFullConfiguration。mFullConfiguration 首先从 parent 传入的 full config 更新,然后从 mResolvedOverrideConfiguration 更新。
  3. 更新 mMergedOverrideConfiguration,并递归地更新 children 的 mMergedOverrideConfiguration。对于 RootWindowContainer 来说,它没有 parent,因此它的 mMergedOverrideConfiguration 就是 mResolvedOverrideConfiguration。而对于其他 WindowContainer 来说,mMergedOverrideConfiguration 首先从 parent 的 mMergedOverrideConfiguration 更新,然后从 mResolvedOverrideConfiguration 更新。
  4. mResolvedOverrideConfiguration 如果改变了,那么发送给监听者。对于 Activity 来说,WindowProcessController 就是其中一个监听者。
  5. 把 mMergedOverrideConfiguration 发送给监听者。对于 Activity 来说,WindowProcessController 就是其中一个监听者。
  6. 把当前 WindowContainer 的 mFullConfiguration 发送给 children,让其更新配置。这样就可以递归地更新所有WC的配置。

这里只是描述了窗口层级树递归更新配置的过程,但是实际上,不同的 WC 可能有自己配置更新的逻辑。例如, ActivityRecord 还可能会更新一个配置 mRequestedOverrideConfiguration。

DisplayContent 配置更新

配置更新中,有很重要的一环,就是 DisplayContent 的配置更新。但是并不是简单调用 DisplayContent#onConfigurationChanged() 更新配置。

根据前面的分析,一个 WC 的配置更新,是由它的 parent 调用 dispatchConfigurationToChild() 发起的。 DisplayContent 的 parent 是 RootWindowContainer,而 RootWindowContainer 恰好是唯一复写了此函数的类,如下

// RootWindowContainer.java

void dispatchConfigurationToChild(DisplayContent child, Configuration config) {
    if (child.isDefaultDisplay) {
        // The global configuration is also the override configuration of default display.
        // 注意看注释,global config 是默认屏幕的 override config
        child.performDisplayOverrideConfigUpdate(config);
    } else {
        child.onConfigurationChanged(config);
    }
}

默认屏幕与非默认屏幕的配置更新,是有区别的。对于默认 DisplayContent,它首先会调用 DisplayContent#performDisplayOverrideConfigUpdate() 更新 override config,然后会调用 DisplayContent#onConfigurationChanged() 更新 full config 等配置。

从注释可知,global config 也是默认 DisplayContent 的 override config,为何这么说呢?根据我的经验来看,RootWindowContainer 只负责维护 global config,也就是 mFullConfiguration,而没有 override config。RootWindowContainer 把 full config 发给 DisplayContent 来更新 display override config,因此可以说,global config 就是 DisplayContent 的 override config。

现在来看下默认屏幕的 override config 更新过程

// DisplayContent.java

// values 是 RootWindowContainer 的 full config
int performDisplayOverrideConfigUpdate(Configuration values) {
    // 1. 检测要更新的配置,与 mRequestedOverrideConfiguration 的差异
    mTempConfig.setTo(getRequestedOverrideConfiguration());
    final int changes = mTempConfig.updateFrom(values);
    
    if (changes != 0) {
        // 这个log,表明正在更新 display override config
        Slog.i(TAG, "Override config changes=" + Integer.toHexString(changes) + " "
                + mTempConfig + " for displayId=" + mDisplayId);
        
        if (isReady() && mTransitionController.isShellTransitionsEnabled()) {
            // 由于此时有正在收集的 Transition,并且收集了 DisplayContent,因此这里没有实质性的作用
            requestChangeTransitionIfNeeded(changes, null /* displayChange */);
        }

        // 2. 处理 mRequestedOverrideConfiguration 改变
        // mTempConfig 此时代表新的 override config
        onRequestedOverrideConfigurationChanged(mTempConfig);
        
        final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
        if (isDensityChange && mDisplayId == DEFAULT_DISPLAY) {
            
        }
        
        mWmService.mDisplayNotificationController.dispatchDisplayChanged(
                this, getConfiguration());
    }
    return changes;
}

mRequestedOverrideConfiguration 就是 DisplayContent 的 override config。

因此,如果要更新 display override config,必须与它先比较,如果有改变,那么调用 DisplayContent#onRequestedOverrideConfigurationChanged() 处理 override config 改变,如下

// DisplayContent.java

// overrideConfiguration 是更新后的 override config,但是是一个临时的
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
    final Configuration currOverrideConfig = getRequestedOverrideConfiguration();
    
    // 当前 override config 的旋转方向
    final int currRotation = currOverrideConfig.windowConfiguration.getRotation();
    
    // 新 override config 的旋转方向
    final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation();

    // 1. 如果 override config 中旋转方向改变了,那么要应用新旋转方向
    if (currRotation != ROTATION_UNDEFINED && overrideRotation != ROTATION_UNDEFINED
            && currRotation != overrideRotation) {
        applyRotationAndFinishFixedRotation(currRotation, overrideRotation);
    }

    mCurrentOverrideConfigurationChanges = currOverrideConfig.diff(overrideConfiguration);
    // 2. 通过基类,继续更新 override config
    super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
    mCurrentOverrideConfigurationChanges = 0;
    
    // 3. 到这里,配置已经更新完毕,那么重置 DisplayContent#mWaitingForConfig 为 false,表示 DisplayContent 配置更新完毕。
    if (mWaitingForConfig) {
        mWaitingForConfig = false;
        mWmService.mLastFinishedFreezeSource = "new-config";
    }
    
    // 4. 添加需要 layout 的原因 LAYOUT_REASON_CONFIG_CHANGED
    mAtmService.addWindowLayoutReasons(
            ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED);
}

默认屏幕 DisplayContent 处理 overide config 改变

  1. 如果新旧override config 的旋转方向改变了,那么应用新的旋转方向,以及 Fixed Rotation( 本文不涉及 ),例如,把新旋转方向配置给 Display。
  2. 通过基类继续处理 display override config。我多提一嘴,这里要理解代码结构的设计意图,DisplayContent 自己处理 override config 时,只处理了 override config 的旋转方向和 Fixed Rotation,它继续调用基类的函数处理 override config,基类会处理 override config 的一些公用配置改变,例如 WindowContainer 处理大小改变。
  3. 经过第2步后,DisplayContent 配置更新完毕,那么是时候重置 DisplayContent#mWaitingForConfig 为 false。
  4. 添加窗口 layout 原因 LAYOUT_REASON_CONFIG_CHANGED 由于 apply WCT 推迟了layout。因此在恢复 layout 时,会执行 WMS 大刷新。

根据前面分析,DisplayContent 处理 override config 改变时,如果 override config 的旋转方向改变了,那么会应用新的旋转方向,如下

// DisplayContent.java

private void applyRotationAndFinishFixedRotation(int oldRotation, int newRotation) {
    
    // 目前没有 fixed rotation app
    final WindowToken rotatedLaunchingApp = mFixedRotationLaunchingApp;
    
    if (rotatedLaunchingApp == null) {
        // 应用新方向
        applyRotation(oldRotation, newRotation);
        return;
    }

    // ...
}

private void applyRotation(final int oldRotation, final int rotation) {
    // 1.保存旋转记录,并且更新 WindowOrientationListener#mCurrentRotation
    mDisplayRotation.applyCurrentRotation(rotation);

    // 打开 shell transition 情况下,shellTransitions 为 true
    final boolean shellTransitions = mTransitionController.getTransitionPlayer() != null;

    // false
    final boolean rotateSeamlessly =
            mDisplayRotation.isRotatingSeamlessly() && !shellTransitions;

    // 由于 DisplayContent 做过同步准备,因此这里获取到的是 DisplayContent 的 sync transaction
    final Transaction transaction =
            shellTransitions ? getSyncTransaction() : getPendingTransaction();
    
    // shell transition 打开的情况下,ScreenRotationAnimation 代表的屏幕旋转动画已经失效
    // 因此 screenRotationAnimation 为 null
    ScreenRotationAnimation screenRotationAnimation = rotateSeamlessly
            ? null : getRotationAnimation();

    // 根据新的旋转方向更新 DisplayContent#mDisplayInfo
    // 由于在计算 global config 时,调用过这个方法,因此这里其实没有实质性作用
    updateDisplayAndOrientation(null /* outConfig */);

    if (screenRotationAnimation != null && screenRotationAnimation.hasScreenshot()) {

    }

    if (!shellTransitions) {
        
    }

    // 2. 根据新的 display info 重新配置屏幕
    // 不过这些操作都保存在 transaction 中,而且还是 DisplayContent 的 sync transaction
    mWmService.mDisplayManagerInternal.performTraversal(transaction);
    
    // TODO: 下面两个操作,涉及到底层操作 surface,暂时还不明白具体有什么用
    if (shellTransitions) {
        // Before setDisplayProjection is applied by the start transaction of transition,
        // set the transform hint to avoid using surface in old rotation.
        getPendingTransaction().setFixedTransformHint(mSurfaceControl, rotation);
        // The sync transaction should already contains setDisplayProjection, so unset the
        // hint to restore the natural state when the transaction is applied.
        transaction.unsetFixedTransformHint(mSurfaceControl);
    }

    scheduleAnimation();

    // 3. 通知 IRotationWatcher 新的旋转方向
    // 注意,这个时机发生在旋转动画之前
    mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);
}

DisplayContent 应用新旋转方向

  1. 在 DisplayRotation 中保存旋转记录,可以通过 adb shell dumpsys window displays 查看旋转记录。
  2. 通知 DMS,根据新的 display info,重新配置屏幕,包括屏幕的旋转方向,不过这些操作都保存在 DisplayContent 的 sync transaction 中。这里涉及 DMS 知识,就不过多介绍了。
  3. 把新的旋转方向,通知方向监听者 IRotationWatcher。注意,此时还没有执行旋转动画,并且 DisplayContent 才刚开始更新 override config。因此,不要乱用 IRotationWatcher 获取到的旋转方向。

DisplayContent 自己处理 override config change,只处理了旋转方向改变,之后它调用父类函数,继续处理 override config change,如下

// WindowContainer.java

public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
    // We must diff before the configuration is applied so that we can capture the change
    // against the existing bounds.
    // 1. 检测 override config bounds 改变
    // 目前只有 bounds 大小改变了,因此这里返回 BOUNDS_CHANGE_SIZE
    final int diff = diffRequestedOverrideBounds(
            overrideConfiguration.windowConfiguration.getBounds());

    // 2.继续通过父类处理 override config change
    super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
    
    if (mParent != null) {
        mParent.onDescendantOverrideConfigurationChanged();
    }
    
    if (diff == BOUNDS_CHANGE_NONE) {
        return;
    }
    
    if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
        // 3. 处理 bounds size change 
        onResize();
    } else {
        onMovedByResize();
    }
}

DisplayContent 的父类 WindowContainer 处理 override config changes,只处理了 bounds change。对于屏幕旋转来说,横屏和竖屏的宽高肯定是不同的。这里调用 DisplayContent#onResize() 处理 bounds change

// DisplayContent.java

void onResize() {
    // 调用 WindowContainer 的方法,如下
    super.onResize();
    if (mWmService.mAccessibilityController.hasCallbacks()) {
        mWmService.mAccessibilityController.onDisplaySizeChanged(this);
    }
}

// WindowContainer.java

void onResize() {
    for (int i = mChildren.size() - 1; i >= 0; --i) {
        final WindowContainer wc = mChildren.get(i);
        wc.onParentResize();
    }
}

DisplayContent 处理 bounds size change(宽高改变)的方式是,通知其 children,它们的 parent 正在 resize,调用的是 child 的 onParentResize()。

目前,没有类复写 onParentSize(),因此基类 WindowContainer 的 onParentSize() 就是所有子类的处理逻辑,如下

// WindowContainer.java

void onParentResize() {
    // 如果有 override bounds,那么不处理 parent resize
    // override config 指的是 mRequestedOverrideConfiguration.windowConfiguration.mBounds 指定了 
    // 谁会有 override bounds,当然是分屏的 task!
    if (hasOverrideBounds()) {
        return;
    }

    // 如果没有 override bounds,调用 onResize(),
    // 也就是调用自己 children 的 onParentResize
    // Default implementation is to treat as resize on self.
    onResize();
}

基类的 onParentResize() 逻辑是

  • 如果有指定 override bounds,那么不处理 parent resize,注意,同时也不会让其 children 处理 parent resize。
  • 如果没有指定 override bounds,那么调用自己的 onResize()。

从基类 WindowContainer 的 onResize() 和 onParentResize() 的逻辑可以看出,当 DisplayContent 因 bounds size change 调用 onResize() 时,会递归调用其所有 children 的 onResize()。但是,如果某一个 child 有自己 override bounds,那么这个 child 及其 children 都不会调用 onResize()。

DisplayContent 的 children 中,目前只有 WindowState 复写了 onResize(),如下

// WindowState.java

void onResize() {
    final ArrayList<WindowState> resizingWindows = mWmService.mResizingWindows;

    // 对于有 surface,并且可见的 WindowState,保存到 mWmService.mResizingWindows
    if (mHasSurface && !isGoneForLayout() && !resizingWindows.contains(this)) {
        ProtoLog.d(WM_DEBUG_RESIZE, "onResize: Resizing %s", this);
        resizingWindows.add(this);
    }

    if (isGoneForLayout()) {
        mResizedWhileGone = true;
    }

    super.onResize();
}

WindowState 代表一个窗口,它处理 rezie 的方式是,把自己保存到 mWmService.mResizingWindows。在 WMS 大刷新的时候,会遍历这个集合,一般情况下,就是通知 app 端 resize。而某些特殊情况下,是不会发生 resize,例如 WindowState 所在的 Activity 正在重启。

DisplayContent 的父类 WindowContainer 处理 override config change,主要处理 bounds size change,而后继续调用父类 ConfigurationContainer 的 onRequestedOverrideConfigurationChanged(),继续处理 display override config。

ConfigurationContainer 是配置的基类,来看下它如何处理 display override config change,如下

// ConfigurationContainer.java

public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
    // 1. 更新 mRequestedOverrideConfiguration
    updateRequestedOverrideConfiguration(overrideConfiguration);
    // Update full configuration of this container and all its children.
    final ConfigurationContainer parent = getParent();

    // 2. 通过 onConfigurationChanged() 更新 full config 等配置
    // 注意,参数是 parent 的 full config
    onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
}

// 更新 mRequestedOverrideConfiguration
void updateRequestedOverrideConfiguration(Configuration overrideConfiguration) {
    // Pre-compute this here, so we don't need to go through the entire Configuration when
    // writing to proto (which has significant cost if we write a lot of empty configurations).
    mHasOverrideConfiguration = !Configuration.EMPTY.equals(overrideConfiguration);
    mRequestedOverrideConfiguration.setTo(overrideConfiguration);
    final Rect newBounds = mRequestedOverrideConfiguration.windowConfiguration.getBounds();
    if (mHasOverrideConfiguration && providesMaxBounds()
            && diffRequestedOverrideMaxBounds(newBounds) != BOUNDS_CHANGE_NONE) {
        mRequestedOverrideConfiguration.windowConfiguration.setMaxBounds(newBounds);
    }
}

基类 ConfigurationContainer 处理 override config

  1. 用新的 override config 更新 mRequestedOverrideConfiguration。
  2. 调用 onConfigurationChanged() 更新其他配置。前面分析过基类 ConfigurationContainer#onConfigurationChanged() ,就是更新 full config 等其他配置。

DisplayContent 利用基类 ConfigurationContainer 处理 display override config change 时,更新了自己的 display override config,也就是 mRequestedOverrideConfiguration。然后调用了自己的 DisplayContent#onConfigurationChanged(),如下

// DisplayContent.java

// newParentConfig 是 RootWindowContainer 的 full config
public void onConfigurationChanged(Configuration newParentConfig) {
    final int lastOrientation = getConfiguration().orientation;
    
    // 通过父类 DisplayArea 继续更新配置
    super.onConfigurationChanged(newParentConfig);
    
    if (mDisplayPolicy != null) {
        // 前面已经根据新方向,更新过 system_server 资源
        // 这里从更新过的资源中,获取一些配置
        mDisplayPolicy.onConfigurationChanged();
        // 重新加载 PIP 中配置
        mPinnedTaskController.onPostDisplayConfigurationChanged();
    }
    
    // Update IME parent if needed.
    updateImeParent();

    // Update surface for MediaProjection, if this DisplayContent is being used for recording.
    if (mContentRecorder != null) {
        mContentRecorder.onConfigurationChanged(lastOrientation);
    }

    if (lastOrientation != getConfiguration().orientation) {
        getMetricsLogger().write(
                new LogMaker(MetricsEvent.ACTION_PHONE_ORIENTATION_CHANGED)
                        .setSubtype(getConfiguration().orientation)
                        .addTaggedData(MetricsEvent.FIELD_DISPLAY_ID, getDisplayId()));
    }
}

DisplayContent 处理 config changes,自己处理了一些事情,例如重新加载一些配置,然后调用其父类 DisplayArea 来继续处理 config changes,如下

// DisplayArea.java

public void onConfigurationChanged(Configuration newParentConfig) {
    // 1.Transition收集一些 DisplayArea 下的 WC
    mTransitionController.collectForDisplayAreaChange(this);
    
    mTmpConfiguration.setTo(getConfiguration());
    
    // 2.由父类 WindowContainer 继续处理配置更新
    super.onConfigurationChanged(newParentConfig);

    // 3.把 DisplayArea 信息发送给 WMShell
    if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) {
        mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
    }
}

DisplayContent 利用 DisplayArea 处理配置更新,如下

  1. Transition 收集 DisplayArea 下的一些 WC。
  2. 利用父类 WindowContainer 继续处理配置更新。
  3. 由于 DisplayArea 配置改变,会把 DisplayArea 信息发送给 WMShell。

先来看下 Transition 收集 DsiplayArea 下的 WC 的过程,如下

// TransitionController.java

// wc 此时是 DisplayContent
void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc) {
    // 1. 当前必须有正在收集的 Transition,并且当前 DisplayArea 必须参与到 Transition 中
    final Transition transition = mCollectingTransition;
    if (transition == null || !transition.mParticipants.contains(wc)) return;
    
    // 2.为 DisplayArea 显示一个截图层
    transition.collectVisibleChange(wc);
    
    // 3. Transition 收集 DisplayArea 下可见的 task
    // Collect all visible tasks.
    wc.forAllLeafTasks(task -> {
        if (task.isVisible()) {
            transition.collect(task);
        }
    }, true /* traverseTopToBottom */);
    
    // 4. Transition 收集非 app 窗口
    // Collect all visible non-app windows which need to be drawn before the animation starts.
    final DisplayContent dc = wc.asDisplayContent();
    if (dc != null) {
        final boolean noAsyncRotation = dc.getAsyncRotationController() == null;
        wc.forAllWindows(w -> {
            if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
                    && (noAsyncRotation || !AsyncRotationController.canBeAsync(w.mToken))) {
                transition.collect(w.mToken);
            }
        }, true /* traverseTopToBottom */);
    }
}

DisplayContent 利用父类 DisplayArea 处理 config changes 时,收集 DisplayArea (实为 DisplayContent)下的 WC 的过程

  1. DisplayArea 必须已经参与到正在收集的 Transition 中,否则不能收集其下的 WC。根据前面的分析,这里的条件是成立的。
  2. Transition#collectVisibleChange(WC) 方法的名字很奇怪,它的实际作用是,根据情况为 WC 创建一个截图层,并显示在 WC 的 children 的最上层。
  3. Transition 收集 DisplayArea 下的所有可见的 Tasks。此时,可见 Task 就是 HelloWorld Task。
  4. Transition 收集非 app 窗口。一般来说,非 app 窗口是需要做异步旋转的,但是有时候会遇到一些特殊情况,例如此时没有创建 AsyncRotationController,那么本应该做异步旋转的窗口,会被收集到 Transition 中做动画。

第3步就是 Transition 收集 WC,这个过程在前面分析过,但是这里还有一点不同,那就是需要为 Task 的可做动画的 parent 建立信息,如下

// Transition.java

void collect(@NonNull WindowContainer wc) {
    // ...
    
    // 为参与者的可做动画的 parent 建立 ChangeInfo 信息
    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);
        
        // ...
    }
    
    // ...
   
}

对于 Task 来说,可做动画的 parent 只有 TaskDisplayArea 和 DisplayContent。而前面收集 DisplayContent 时,已经为 DisplayContent 建立了信息,因此这里只会为 TaskDisplayArea 建立 ChangeInfo 信息,并保存到 Transition#mChanges 中。

现在重点看下第2步,是如何为 WC 创建并显示截图层的

// Transition.java

/**
 * Records that a particular container is changing visibly (ie. something about it is changing
 * while it remains visible). This only effects windows that are already in the collecting
 * transition.
 */
// 注意,参数 wc 是 DisplayArea,实际上是 DisplayContent
void collectVisibleChange(WindowContainer wc) {
    // SyncGroup 使用 BLAST 进行同步绘制,那么也就不需要显示截图层
    if (mSyncEngine.getSyncSet(mSyncId).mSyncMethod == BLASTSyncEngine.METHOD_BLAST) {
        // All windows are synced already.
        return;
    }
    
    // Transition 必须收集了 wc
    if (wc.mDisplayContent == null || !isInTransition(wc)) return;
    
    if (!wc.mDisplayContent.getDisplayPolicy().isScreenOnFully()
            || wc.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF) {
        mFlags |= WindowManager.TRANSIT_FLAG_INVISIBLE;
        return;
    }
    
    // 启动窗口绑定到 task,是不需要显示截图层
    // Activity doesn't need to capture snapshot if the starting window has associated to task.
    if (wc.asActivityRecord() != null) {
        final ActivityRecord activityRecord = wc.asActivityRecord();
        if (activityRecord.mStartingData != null
                && activityRecord.mStartingData.mAssociatedTask != null) {
            return;
        }
    }

    if (mContainerFreezer == null) {
        mContainerFreezer = new ScreenshotFreezer();
    }
    
    Transition.ChangeInfo change = mChanges.get(wc);
    // Transition#mChanges 中必须有 wc 的 ChangeInfo,并且wc在收集是,以及在当前,都需要可见
    if (change == null || !change.mVisible || !wc.isVisibleRequested()) return;
    
    // 冻结 wc,其实就是为 wc 创建并显示一个截图层
    mContainerFreezer.freeze(wc, change.mAbsoluteBounds);
}

ScreenshotFreezer#freeze() 是冻结 WC,其实就是根据情况创建并显示截图层,如下

// Transition.java

private class ScreenshotFreezer implements IContainerFreezer {
    private final ArraySet<WindowContainer> mFrozen = new ArraySet<>();

    /** Takes a screenshot and puts it at the top of the container's surface. */
    @Override
    public boolean freeze(@NonNull WindowContainer wc, @NonNull Rect bounds) {
        // 1. 检测不需要显示截图层的情况
        // 不可见的 WC,没有必要为其创建截图层
        if (!wc.isVisibleRequested()) return false;

        // 如果 wc 或者 parent 已经被冻结,那么就不用为 WC 创建截图层
        for (WindowContainer p = wc; p != null; p = p.getParent()) {
            if (mFrozen.contains(p)) return false;
        }

        // 无缝旋转时不需要显示截图层的
        if (mIsSeamlessRotation) {
            // ...
        }

        ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Screenshotting %s [%s]",
                wc.toString(), bounds.toString());

        // 参数 bounds 是 wc 当前的 Bound
        Rect cropBounds = new Rect(bounds);
        cropBounds.offsetTo(0, 0);
        
        // 判断是否是屏幕旋转
        // 此时 isDisplayRotation 为 true
        final boolean isDisplayRotation = wc.asDisplayContent() != null
                && wc.asDisplayContent().isRotationChanging();
        
        // 2. 根据wc 的 surface 创建截图层的 buffer
        ScreenCapture.LayerCaptureArgs captureArgs =
                new ScreenCapture.LayerCaptureArgs.Builder(wc.getSurfaceControl())
                        .setSourceCrop(cropBounds)
                        .setCaptureSecureLayers(true)
                        .setAllowProtected(true)
                        .setHintForSeamlessTransition(isDisplayRotation)
                        .build();
        ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
                ScreenCapture.captureLayers(captureArgs);
        final HardwareBuffer buffer = screenshotBuffer == null ? null
                : screenshotBuffer.getHardwareBuffer();
        if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
            // This can happen when display is not ready.
            Slog.w(TAG, "Failed to capture screenshot for " + wc);
            return false;
        }
        
        // 3. 在 WC 下创建截图层 surface
        // Some tests may check the name "RotationLayer" to detect display rotation.
        final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
        SurfaceControl snapshotSurface = wc.makeAnimationLeash()
                .setName(name)
                .setOpaque(wc.fillsParent())
                .setParent(wc.getSurfaceControl())
                .setSecure(screenshotBuffer.containsSecureLayers())
                .setCallsite("Transition.ScreenshotSync")
                .setBLASTLayer()
                .build();
        
        // 4.保存冻结的 WC
        mFrozen.add(wc);
        
        // 5.WC 对应的 ChangeInfo 中保存截图 surface
        final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
        changeInfo.mSnapshot = snapshotSurface;
        
        if (isDisplayRotation) {
            // This isn't cheap, so only do it for display rotations.
            changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
                    buffer, screenshotBuffer.getColorSpace());
        }
        
        // 6. 通过一个 Transaction 把截图层显示在 WC 的 children 最上面
        SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get();
        // 把截图层的 buffer 设置到截图层的 layer 中
        TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer);
        t.show(snapshotSurface);
        t.setLayer(snapshotSurface, Integer.MAX_VALUE);
        t.apply(); // apply 事务
        t.close();
        buffer.close();

        wc.getSyncTransaction().reparent(snapshotSurface, null /* newParent */);
        return true;
    }
}

冻结一个 WC (或者称之为显示截图层)的过程如下

  1. 检测不需要显示截图层的情况。对于屏幕旋转动画来说,是需要显示截图层的。
  2. 根据 WC 的 surface,创建一个截图层 buffer。
  3. WC 创建一个动画 leash,也就是截图层的 surface,并且这个 leash 挂在了 WC 的 surface 之下。
  4. 保存冻结的 WC。
  5. 从 Transition#mChanges 中找到 WC 对应的 ChangeInfo,并用 ChangeInfo#mSnapshot 保存截图层 surface。
  6. 通过一个 Transaction,显示截图层 surface,并把这个截图层显示在 WC 的 children 的最上面。

此时的 WC 是 DisplayContent,因此现在有一个截图层 surface,挂在 DisplayContent 的 surface 之下,并且显示在其 children 的最上层。

现在,DisplayContent 利用父类 DisplayArea 处理 config changes 时,已经分析完了收集 DisplayArea 下的 WC 的过程,如下

// DisplayArea.java

    public void onConfigurationChanged(Configuration newParentConfig) {
        // 1. 收集 DisplayArea 下的WC
        mTransitionController.collectForDisplayAreaChange(this);
        mTmpConfiguration.setTo(getConfiguration());
        // 2. 调用父类 WindowContainer 继续处理 config change
        super.onConfigurationChanged(newParentConfig);
        // 3. 把 DisplayArea 信息发送给 WMShell
        if (mOrganizer != null && getConfiguration().diff(mTmpConfiguration) != 0) {
            mOrganizerController.onDisplayAreaInfoChanged(mOrganizer, this);
        }
    }

现在继续分析 DisplayContent 如何利用父类 WindowContainer 处理 config change,如下

// WindowContainer.java

    public void onConfigurationChanged(Configuration newParentConfig) {
        // 1. 调用父类 ConfigurationContainer 处理 config change
        // 这个就是基本的配置更新逻辑
        super.onConfigurationChanged(newParentConfig);
        
        // 2. 更新 surface position
        updateSurfacePositionNonOrganized();
        scheduleAnimation();
        if (mOverlayHost != null) {
            mOverlayHost.dispatchConfigurationChanged(getConfiguration());
        }
    }

WindowContainer 在处理 config changes 时,主要负责 surface position 的更新,但是对于旋转动画来说,是不涉及的。 然后, WindowContainer 就是利用父类 ConfigurationContainer#onConfigurationChanged() 来处理 config changes,前面分析过这个方法,它就是最基本的配置更新逻辑。因此,到这里,DisplayContent 的配置更新就结束了。

activity relaunch

前面已经分析窗口层级树的配置更新,但是整个配置更新的过程,还没有结束,如下

// DisplayContent.java

// values 为计算出的新配置
// starting 为 null
// deferResume 为 false
boolean updateDisplayOverrideConfigurationLocked(Configuration values,
        ActivityRecord starting, boolean deferResume,
        ActivityTaskManagerService.UpdateConfigurationResult result) {

    int changes = 0;
    boolean kept = true;

    mAtmService.deferWindowLayout();
    try {
        if (values != null) {
            if (mDisplayId == DEFAULT_DISPLAY) {
                // Override configuration of the default display duplicates global config, so
                // we're calling global config update instead for default display. It will also
                // apply the correct override config.
                // 1.更新默认屏幕配置
                changes = mAtmService.updateGlobalConfigurationLocked(values,
                        false /* initLocale */, false /* persistent */,
                        UserHandle.USER_NULL /* userId */);
            } else {
                // ... 本文值关心默认屏幕的配置更新 ...
            }
        }

        if (!deferResume) {
            // 2. 让 top activity 正确处理 changed config
            // 由于 apply WCT 推迟了可见性更新,因此这里不会执行可见性更新
            kept = mAtmService.ensureConfigAndVisibilityAfterUpdate(starting, changes);
        }
    } finally {
        mAtmService.continueWindowLayout();
    }

    // ...
    return kept;
}

前面已经分析了第1步的配置更新,现在来看下第2步

// ActivityTaskManagerService.java

// starting 为 null,changes 不为0
boolean ensureConfigAndVisibilityAfterUpdate(ActivityRecord starting, int changes) {
    boolean kept = true;
    final Task mainRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
    if (mainRootTask != null) {
        if (changes != 0 && starting == null) {
            // 获取 top focused root task 下的 top non-finishing activity
            starting = mainRootTask.topRunningActivity();
        }

        if (starting != null) {
            // 检测是否 relaunch top activity
            kept = starting.ensureActivityConfiguration(changes,
                    false /* preserveWindow */);
            
            // 这里不会更新 activity 可见性,因为 apply WCT 时,推迟了可见性更新
            mRootWindowContainer.ensureActivitiesVisible(starting, changes,
                    !PRESERVE_WINDOWS);
        }
    }

    return kept;
}

当配置更新完成后,主要是确保 top root task 下的 top activity 正确处理 changes config

// ActivityRecord.java

// globalChanges 不为 0,preserveWindow 为 false
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow) {
    return ensureActivityConfiguration(globalChanges, preserveWindow,
            false /* ignoreVisibility */, false /* isRequestedOrientationChanged */);
}

// globalChanges 值为 CONFIG_WINDOW_CONFIGURATION | CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION
// 其他都为 false
boolean ensureActivityConfiguration(int globalChanges, boolean preserveWindow,
        boolean ignoreVisibility, boolean isRequestedOrientationChanged) {
    final Task rootTask = getRootTask();
    
    // 省略一段不需要处理配置改变的代码 ...

    ProtoLog.v(WM_DEBUG_CONFIGURATION, "Ensuring correct "
            + "configuration: %s", this);

    // ... 省略一段无关紧要代码 ..


    // 保存上次发送给 app 的 merged override config
    mTmpConfig.setTo(mLastReportedConfiguration.getMergedConfiguration());
    
    if (getConfiguration().equals(mTmpConfig) && !forceNewConfig && !displayChanged) {

    }

    // 注意看下面这一段注释
    // Okay we now are going to make this activity have the new config.
    // But then we need to figure out how it needs to deal with that.

    // Find changes between last reported merged configuration and the current one. This is used
    // to decide whether to relaunch an activity or just report a configuration change.
    
    // 1. 获取 ActivityRecord 需要处理的 changed config
    // changes 为 CONFIG_ORIENTATION
    final int changes = getConfigurationChanges(mTmpConfig);

    // 获取更新后的 merged override config
    final Configuration newMergedOverrideConfig = getMergedOverrideConfiguration();

    // 更新 mLastReportedConfiguration 的 mGlobalConfig、mOverrideConfig
    // 然后利用这个两个配置计算 mMergedConfig
    setLastReportedConfiguration(getProcessGlobalConfiguration(), newMergedOverrideConfig);

    if (mState == INITIALIZING) {

    }
    if (changes == 0 && !forceNewConfig) {

    }

    // 这个log打印了 Activity 要处理那些改变的配置
    ProtoLog.v(WM_DEBUG_CONFIGURATION, "Configuration changes for %s, "
            + "allChanges=%s", this, Configuration.configurationDiffToString(changes));

    if (!attachedToProcess()) {

    }

    // Figure out how to handle the changes between the configurations.
    ProtoLog.v(WM_DEBUG_CONFIGURATION, "Checking to restart %s: changed=0x%s, "
            + "handles=0x%s, mLastReportedConfiguration=%s", info.name,
            Integer.toHexString(changes), Integer.toHexString(info.getRealConfigChanged()),
            mLastReportedConfiguration);

    // 2. 检测 activity 是否需要 relaunch
    boolean shouldRelaunchLocked = shouldRelaunchLocked(changes, mTmpConfig);

    if (!DeviceIntegrationUtils.DISABLE_DEVICE_INTEGRATION) {
        shouldRelaunchLocked &= !mAtmService.mRemoteTaskManager.shouldIgnoreRelaunch(task, displayChanged,
                lastReportDisplayID, newDisplayId, changes);
    }


    if (shouldRelaunchLocked || forceNewConfig) {
        // Aha, the activity isn't handling the change, so DIE DIE DIE.
        configChangeFlags |= changes;
        
        startFreezingScreenLocked(globalChanges);
        
        forceNewConfig = false;
        
        // preserveWindow 初始为 false,那么这里还是为 false
        // Do not preserve window if it is freezing screen because the original window won't be
        // able to update drawn state that causes freeze timeout.
        preserveWindow &= isResizeOnlyChange(changes) && !mFreezingScreen;
        
        // 确定 relaunch 原因
        final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
        if (hasResizeChange) {
            final boolean isDragResizing = task.isDragResizing();
            mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE
                    : RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
        } else {
            
        }
        
        // isRequestedOrientationChanged 为 false
        if (isRequestedOrientationChanged) {
            
        }
        
        if (mState == PAUSING) {

        } else {
            ProtoLog.v(WM_DEBUG_CONFIGURATION, "Config is relaunching %s",
                    this);
            if (!mVisibleRequested) {
            }
            
            // 3. relaunch activity
            relaunchActivityLocked(preserveWindow);
        }

        // All done...  tell the caller we weren't able to keep this activity around.
        return false;
    }

    // ...
}

top activity 处理 changed config

  1. 获取 Activity 需要处理的 changed config。不是所有的 changed config 都需要 Activity 处理,例如 CONFIG_WINDOW_CONFIGURATION。因此,这里过滤掉不需要处理的,剩下的就是需要处理的。
  2. 检测 Activity 处理这些 changed config 时,是否需要重启。本文分析的例子,由于 MainActivity 在 AndroidManifest.xml 中,并没有使用 android:configChangs 声明自己能够处理的配置,因此 MainActivity 需要 relaunch。
  3. 通知 app 端 realunch activity

现在来简单看下通知 app 端 relaunch activity 的流程

// ActivityRecord.java

void relaunchActivityLocked(boolean preserveWindow) {
    // ...

    // 1.检测 reluanch 后,是否要 resume activity
    final boolean andResume = shouldBeResumed(null /*activeActivity*/);
    
    // ...
    
    if (DEBUG_SWITCH) Slog.v(TAG_SWITCH,
            "Relaunching: " + this + " with results=" + pendingResults
                    + " newIntents=" + pendingNewIntents + " andResume=" + andResume
                    + " preserveWindow=" + preserveWindow);
    
    if (andResume) {
        EventLogTags.writeWmRelaunchResumeActivity(mUserId, System.identityHashCode(this),
                task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
    } else {
        EventLogTags.writeWmRelaunchActivity(mUserId, System.identityHashCode(this),
                task.mTaskId, shortComponentName, Integer.toHexString(configChangeFlags));
    }
    
    startFreezingScreenLocked(0);
    
    try {
        ProtoLog.i(WM_DEBUG_STATES, "Moving to %s Relaunching %s callers=%s" ,
                (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
        
        forceNewConfig = false;
        
        // 2. 标记 activity 正在重启
        // ActivityRecord#mPendingRelaunchCount + 1,表示 activity 正在重启中
        // 并且清除 ActivityRecord all drawn 状态
        startRelaunching();
        
        // 3.通知 app 端 relaunch activity
        final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
                pendingNewIntents, configChangeFlags,
                new MergedConfiguration(getProcessGlobalConfiguration(),
                        getMergedOverrideConfiguration()),
                preserveWindow);
        final ActivityLifecycleItem lifecycleItem;
        if (andResume) {
            // reluanch 后需要 resume,那么生命周期执行到 onResumed()
            lifecycleItem = ResumeActivityItem.obtain(isTransitionForward(),
                    shouldSendCompatFakeFocus());
        } else {
            // 从这里可以看出,relaunch activity 并不一定要把生命后期执行到 onResume()
            lifecycleItem = PauseActivityItem.obtain();
        }
        final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
        transaction.addCallback(callbackItem);
        transaction.setLifecycleStateRequest(lifecycleItem);
        mAtmService.getLifecycleManager().scheduleTransaction(transaction);
    } catch (RemoteException e) {}
    
    // ...
}

relaunch activity 过程

  1. 检测 relaunch 后,是否需要 resume activity,也就是是否需要把 activity 的生命周期执行到 onResumed()。 对于 top task 的 top activity 来说,是需要的。
  2. 把 ActivityRecord#mPendingRelaunchCount 加 1,表示 activity 正在重启。并且还会清除 ActivityRecord 的 all drawn 状态。
  3. 通知 app 端 relaunch activity。

配置更新时序图

总结下配置更新的过程,其实就是先根据新的旋转方向计算一个 global config,然后从 RootWindowContainer 递归更新窗口层级树中所有节点的配置。

这里展示两个时序图,一个是计算 global config 并把它发送给 RootWindowContainer,如下

配置更新.jpg

另一个时序图,是 DisplayContent 配置更新

111.jpg

本文所讲述的配置更新的内容,只是冰山一角!

更新 transition ready

根据前面的分析,在 apply WCT 时,调用了 Transition#applyDisplayChangeIfNeeded(),如下

// Transition.java

void applyDisplayChangeIfNeeded() {
    for (int i = mParticipants.size() - 1; i >= 0; --i) {
        // 1. 检测是否有 DisplayContent 参与 Transition
        final WindowContainer<?> wc = mParticipants.valueAt(i);
        final DisplayContent dc = wc.asDisplayContent();
        if (dc == null || !mChanges.get(dc).hasChanged()) continue;
        
        // 2. 如果有 DisplayContent 参与 Transition,那么 DisplayContent 发送新配置
        dc.sendNewConfiguration();
        
        // 目前 mReadyTracker 还没有被使用
        if (!mReadyTracker.mUsed) {
            // 3. 更新 transition ready 状态为 true
            setReady(dc, true);
        }
    }
}

前面已经分析了配置更新的过程,现在来看下第3步的,更新 transition ready 为 true 的过程,如下

// Transition.java

void setReady(WindowContainer wc, boolean ready) {
    if (!isCollecting() || mSyncId < 0) return;
    
    // 1.由 Transition#mReadyTracker 保存 transition ready 状态
    mReadyTracker.setReadyFrom(wc, ready);
    
    // 2. 应用 transition ready 状态
    applyReady();
}

Transition 更新 ready 状态的过程

  1. 由 Transition#mReadyTracker 保存 ready 状态,此时 ready 为 true。
  2. 应用 ready 状态。

首先看下 ReadyTracker 保存 ready 状态的过程

// Transition.java

private static class ReadyTracker {
    void setReadyFrom(WindowContainer wc, boolean ready) {
        mUsed = true;
        WindowContainer current = wc;
        // 以 wc 为参考,向上遍历,寻找 ready-group
        while (current != null) {
            // 只有 DisplayContent 才是 ready-group
            if (isReadyGroup(current)) {
                
                // 以 DisplayContent 为 KEY,保存它的 ready 状态
                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();
        }
    }
}

很简单,就是用 ReaderTracker#mReadyGroups 以 DisplayContent 为 KEY,保存 ready 状态。

什么是 ready-group,简单来说,只有 DisplayContent 是 ready-group。那么设计原理是什么?可能需要对窗口动画有更深层次的理解,才能很好地回答这个问题。

保存 ready 状态后,再来看下应用 ready 状态的过程

// Transition.java

private void applyReady() {
    if (mState < STATE_STARTED) return;
    
    // 1. 查询所有 ready-group 是否都进入 ready 状态
    final boolean ready = mReadyTracker.allReady();
    
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
            "Set transition ready=%b %d", ready, mSyncId);
    
    // 2. 把 ready 状态设置给 SyncGroup
    boolean changed = mSyncEngine.setReady(mSyncId, ready);
    
    // 当 ready 状态有 false 变为 true 时
    if (changed && ready) {
        mLogger.mReadyTimeNs = SystemClock.elapsedRealtimeNanos();
        mOnTopTasksAtReady.clear();
        // mTargetDisplays 是在 Transition 第一次收集 WC 时,保存 WC 所在的 DisplayContent
        for (int i = 0; i < mTargetDisplays.size(); ++i) {
            // mOnTopTasksAtReady 保存了 top root task 及其 children task
            addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady);
        }
        
        // 正在收集的 Transition 进入 ready 状态后,会启动队列中的 Transition
        mController.onTransitionPopulated(this);
    }
}

应用 ready 状态的过程,主要就是把 ready 状态设置给 SyncGroup ,如下

// BLASTSyncEngine.java

class SyncGroup {
    private boolean setReady(boolean ready) {
        if (mReady == ready) {
            return false;
        }
        
        ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
        
        // SyncGroup#mReady 保存 ready 状态
        mReady = ready;
        
        // ready 此时为 true
        if (ready) {
            // 请求 WMS 大刷新
            mWm.mWindowPlacerLocked.requestTraversal();
        }
        return true;
    }
}

SyncGroup#mReady 保存 transition ready 状态,由于此时 ready 为 true,因此还会发起 WMS 大刷新。但是,更新 transition ready 是在 apply WCT 中执行的,根据前面的分析 apply WCT 推迟了 layout,因此 此时并不会立即执行 WMS 大刷新,而是要等到 apply WCT 恢复 layout 时才执行。

结束

限于篇幅的限制,本文以旋转动画为例,仅仅分析了 shell transition 中的 request transition 和 start transition 过程。剩下的流程,留到后面的文章继续分析。