在 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 框架下,是如何执行的。
案例
- 在下拉控制中心打开屏幕旋转开关。
- 用 Android Studio 创建一个 HelloWorld app,然后运行起来。
- 在 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 更新系统旋转方向
- 计算系统新的旋转方向。这里会综合各方因素,计算出系统的旋转方向。这些因素包括 sensor 上报的方向,Activity 申请的方向,等等。
- 更新一些状态变量。这些变量都有非常重要的作用,DisplayRotation#mRotation 保存系统当前的旋转方向。DisplayContent#mLayoutNeeded 更新为 true,表示 DisplayContent 需要 layout,在 WMS 大刷新的时候,会对 DisplayContent 下的窗口重新 layout。DisplayContent#mWaitingForConfig 更新为 true,表示 DisplayContent 正在等待配置更新完毕,当配置更新完成后,才会把这个变量更新为 false,而如果配置一直没有更新完成,那么 WMS 是无法执行大刷新的,这就会导致屏幕的界面一直不刷新。
- 创建一个 TransitionRequestInfo.DisplayChange 对象,保存旋转前后的方向,然后由 DisplayContent 发起一个 change transition 请求。
计算系统旋转方向
根据案例,可以知道如下信息
- 屏幕旋转方向是打开的。
- 把手机从竖屏旋转到横屏,sensor 上报的方向是 1。
- 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
- 通过 TransitionController 请求一个 type 为 TRANSIT_CHANGE 的 transition。
- 启动异步旋转。何为异步旋转?例如,状态栏,导航栏,它们是不跟随 Transition 做旋转动画的,而是单独做动画的,因此称之为异步旋转。
- Transition 保存 DisplayContent 已知的 change 信息,其实就是在 Transition#mChanges 中,为 DisplayContent 更新 ChangeInfo.mKnownConfigChanges 为 ActivityInfo.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
- 通过 createTransition() 创建 Transition,并向 WMShell 发起 transition 请求。
- 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 过程
- new 一个 SyncGroup 对象。在 SyncGroup 构造函数中,保存了参数 listener,它的类型为 TransactionReadyListener ,实现者为 Transition。
- BLASTSyncEngine#mActiveSyncs 保存刚创建的 SyncGroup。
- 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 的主要过程
- DisplayContent 是需要同步的,因此被添加到 SyncGroup 中。
- Transition#mChange 为 DisplayContent 建立 ChangeInfo。注意,ChangeInfo 保存的是 WC 的当前状态。
- 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,一般来说,过程如下
- SyncGroup 与 WC 相互关联。SyncGroup#mRootMembers 保存需要同步的 WC, WindowContainer#mSyncGroup 保存相关联的 SyncGroup。
- 调用 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 的同步准备
- 通过基类函数,检测是否已经处于同步状态了。每一个 WC 的同步状态默认为 SYNC_STATE_NONE,只有这个状态才代表不处于同步状态。
- 把 WindowState 的同步状态 mSyncState 更新为 SYNC_STATE_WAITING_FOR_DRAW,表示需要 SyncGroup 等待重绘。
- 使用 requestRedrawForSync() 请求窗口重绘,其实就是把 WindowState#mRedrawForSyncReported 设置为 false。一般来说,只有 top activity 的窗口才会被标记为请求重绘。
BLAST sync 的意思是,app 绘制完成后,把绘制 buffer 同步到 wms 进行 apply。由于屏幕旋转动画在执行的过程中,会显示一个截图层在最上面,因此无论是否使用 BLAST sync 绘制,
现在总结下,对于屏幕旋转动画来说,DisplayContent 的同步过程如下
- 异步旋转的 WindowToken 及其 children WindowState 不参与同步准备。
- 其他的 WindowState 的同步状态 mSyncState 更新为 SYNC_STATE_WAITING_FOR_DRAW。
- 其他的 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 过程
- 让所有 TransitionHandler 处理这个请求,如果某一个 TransitionHandler 对这个请求感兴趣,会返回一个非空的 WindowContainerTransaction 。这个 WindowContainerTransaction 是在 WMCore 的 start transition 流程中 apply 的。因此,如果 TransitionHandler 有什么需要 WMCore 执行的操作,那么需要在 WindowContainerTransaction 中保存操作的数据。对于屏幕旋转动画来说,目前没有任何 TransitionHandler 对这个 transition 请求感兴趣。
- 通知对屏幕旋转感兴趣的监听者。例如,如果当前处于分屏模式,那么分屏的 TransitionHandler,也就是 StageCoordinator ,会处理屏幕旋转的情况。
- 通知 WMCore 执行 start transition。
- 在 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
- 执行 Transition#start() 启动 Transition,主要就是把 Transition 的状态切换到 STATE_STARTED。
- 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);
}
}
}
- DisplayContent 必须参与到 Transition 中,并且 DisplayContent 必须有改变。根据前面的分析,这两个条件是成立的。
- DisplayContent 发送新配置。
- 更新 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;
}
对于默认屏幕,它的配置更新过程如下
- 由 ActivityTaskManagerService 先计算 global config,再把 global config 发送给 RootWindowContainer ,由 RootWindowContainer 开始递归地更新窗口层级树中每一个节点的配置,这其中自然也包含 DisplayContent 配置的更新。
- 当配置更新完成后,根据顶层的 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
- 检测新配置与 global config 是否有差异,如果有,才进行下一步配置更新。那么问题来了,什么是全局配置( global config )呢? 其实就是 RootWindowContainer#mFullConfiguration。由于 RootWindowContainer 是窗口层级树的根,它的 full config 称之为 global config,很合理。
- 使用新 global config,更新 system_server 进程的资源。system_server 进程也有 app 环境,因此它的资源也必须跟随配置的更新而更新。
- 把新的 global config 发送给系统中正在运行的进程。注意,top app 进程并不会在此时收到 global config,一般来说,这里会把 global config 发送给不拥有 Activity 的进程。限于篇幅的原因,我不能详细解释配置更新的原理。
- 发送 global config changed 广播,从广播数据中可以得知哪些配置改变了。
- 把新的 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);
}
递归更新层级树配置的过程如下
- 保存旧的 mResolvedOverrideConfiguration,并解析新的 mResolvedOverrideConfiguration。不同的 WC 可能会覆盖这个解析方法,加入自己的逻辑,但是基本上 mResolvedOverrideConfiguration 是来自于 mRequestedOverrideConfiguration。
- 解析 mFullConfiguration。mFullConfiguration 首先从 parent 传入的 full config 更新,然后从 mResolvedOverrideConfiguration 更新。
- 更新 mMergedOverrideConfiguration,并递归地更新 children 的 mMergedOverrideConfiguration。对于 RootWindowContainer 来说,它没有 parent,因此它的 mMergedOverrideConfiguration 就是 mResolvedOverrideConfiguration。而对于其他 WindowContainer 来说,mMergedOverrideConfiguration 首先从 parent 的 mMergedOverrideConfiguration 更新,然后从 mResolvedOverrideConfiguration 更新。
- mResolvedOverrideConfiguration 如果改变了,那么发送给监听者。对于 Activity 来说,WindowProcessController 就是其中一个监听者。
- 把 mMergedOverrideConfiguration 发送给监听者。对于 Activity 来说,WindowProcessController 就是其中一个监听者。
- 把当前 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 改变
- 如果新旧override config 的旋转方向改变了,那么应用新的旋转方向,以及 Fixed Rotation( 本文不涉及 ),例如,把新旋转方向配置给 Display。
- 通过基类继续处理 display override config。我多提一嘴,这里要理解代码结构的设计意图,DisplayContent 自己处理 override config 时,只处理了 override config 的旋转方向和 Fixed Rotation,它继续调用基类的函数处理 override config,基类会处理 override config 的一些公用配置改变,例如 WindowContainer 处理大小改变。
- 经过第2步后,DisplayContent 配置更新完毕,那么是时候重置 DisplayContent#mWaitingForConfig 为 false。
- 添加窗口 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 应用新旋转方向
- 在 DisplayRotation 中保存旋转记录,可以通过 adb shell dumpsys window displays 查看旋转记录。
- 通知 DMS,根据新的 display info,重新配置屏幕,包括屏幕的旋转方向,不过这些操作都保存在 DisplayContent 的 sync transaction 中。这里涉及 DMS 知识,就不过多介绍了。
- 把新的旋转方向,通知方向监听者 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
- 用新的 override config 更新 mRequestedOverrideConfiguration。
- 调用 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 处理配置更新,如下
- Transition 收集 DisplayArea 下的一些 WC。
- 利用父类 WindowContainer 继续处理配置更新。
- 由于 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 的过程
- DisplayArea 必须已经参与到正在收集的 Transition 中,否则不能收集其下的 WC。根据前面的分析,这里的条件是成立的。
- Transition#collectVisibleChange(WC) 方法的名字很奇怪,它的实际作用是,根据情况为 WC 创建一个截图层,并显示在 WC 的 children 的最上层。
- Transition 收集 DisplayArea 下的所有可见的 Tasks。此时,可见 Task 就是 HelloWorld Task。
- 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 (或者称之为显示截图层)的过程如下
- 检测不需要显示截图层的情况。对于屏幕旋转动画来说,是需要显示截图层的。
- 根据 WC 的 surface,创建一个截图层 buffer。
- WC 创建一个动画 leash,也就是截图层的 surface,并且这个 leash 挂在了 WC 的 surface 之下。
- 保存冻结的 WC。
- 从 Transition#mChanges 中找到 WC 对应的 ChangeInfo,并用 ChangeInfo#mSnapshot 保存截图层 surface。
- 通过一个 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
- 获取 Activity 需要处理的 changed config。不是所有的 changed config 都需要 Activity 处理,例如 CONFIG_WINDOW_CONFIGURATION。因此,这里过滤掉不需要处理的,剩下的就是需要处理的。
- 检测 Activity 处理这些 changed config 时,是否需要重启。本文分析的例子,由于 MainActivity 在 AndroidManifest.xml 中,并没有使用 android:configChangs 声明自己能够处理的配置,因此 MainActivity 需要 relaunch。
- 通知 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 过程
- 检测 relaunch 后,是否需要 resume activity,也就是是否需要把 activity 的生命周期执行到 onResumed()。 对于 top task 的 top activity 来说,是需要的。
- 把 ActivityRecord#mPendingRelaunchCount 加 1,表示 activity 正在重启。并且还会清除 ActivityRecord 的 all drawn 状态。
- 通知 app 端 relaunch activity。
配置更新时序图
总结下配置更新的过程,其实就是先根据新的旋转方向计算一个 global config,然后从 RootWindowContainer 递归更新窗口层级树中所有节点的配置。
这里展示两个时序图,一个是计算 global config 并把它发送给 RootWindowContainer,如下
另一个时序图,是 DisplayContent 配置更新
本文所讲述的配置更新的内容,只是冰山一角!
更新 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 状态的过程
- 由 Transition#mReadyTracker 保存 ready 状态,此时 ready 为 true。
- 应用 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 过程。剩下的流程,留到后面的文章继续分析。