为了不把"战线"拉得太长,上一篇文章省略了 fixed rotation 的分析,本文就重点分析这个。
fixed rotation
java
// r 为 近期任务 activity
// orientationSrc 也为 launcher activity
// checkOpening 为 true
private boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
@NonNull ActivityRecord orientationSrc, boolean checkOpening) {
// ...
// 1.使用实际的 orientation source 提供的 orientation,计算新的旋转方向
final int rotation = rotationForActivityInDifferentOrientation(orientationSrc);
// ...
// 2.
setFixedRotationLaunchingApp(r, rotation);
return true;
}
由于是从一个横屏的 app 返回竖屏的桌面,这里利用 launcher 提供的 orientation,计算出的旋转方向为0,因此接下来就会走 fixed rotation 流程。
java
// DisplayContent.java
void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Rotation int rotation) {
// ...
if (!r.hasFixedRotationTransform()) {
// 1. 根据新的旋转方向 rotation,创建 fixed rotation transform
startFixedRotationTransform(r, rotation);
}
// 2. 保存 fixed rotation app
setFixedRotationLaunchingAppUnchecked(r, rotation);
// ...
}
private void startFixedRotationTransform(WindowToken token, int rotation) {
mTmpConfiguration.unset();
// 根据竖屏旋转方向,计算配置,并保存到参数 mTmpConfiguration
// 同时返回新的 display info
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
// 下面都是根据竖屏方向计算出的数据
final DisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final PrivacyIndicatorBounds indicatorBounds =
calculatePrivacyIndicatorBoundsForRotation(rotation);
final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
cutout, roundedCorners, indicatorBounds, displayShape);
// 1.1 apply fixed rotation transform
token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
// Delay the hide animation to avoid blinking by clicking navigation bar that may
// toggle fixed rotation in a short time.
final boolean shouldDebounce = r == mFixedRotationTransitionListener.mAnimatingRecents
|| mTransitionController.isTransientLaunch(r);
// 2.1 start async rotation
startAsyncRotation(shouldDebounce);
} else if (mFixedRotationLaunchingApp != null && r == null) {
}
// 2.2 set fixed rotation app
mFixedRotationLaunchingApp = r;
}
fixed rotation 流程主要有三步
- apply fixed rotation transform : 根据新方向计算一些数据,例如,配置,display info,等等,然后 apply 这些数据。
- start async rotation : 这是为了控制图层高于 app 的窗口动画,例如 statusbar。
- set fixed rotation app: 使用 DisplayContent#mFixedRotationLaunchingApp 保存 fixed rotation app,也就是近期任务 ActivityRecord。
apply fixed rotation transform
java
// WindowToken
void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
Configuration config) {
if (mFixedRotationTransformState != null) {
mFixedRotationTransformState.disassociate(this);
}
config = new Configuration(config);
// 1. FixedRotationTransformState 包装数据
mFixedRotationTransformState = mTransitionController.isShellTransitionsEnabled()
? new FixedRotationTransformState(info, displayFrames, config)
: new FixedRotationTransformStateLegacy(info, displayFrames, config,
mDisplayContent.getRotation());
mFixedRotationTransformState.mAssociatedTokens.add(this);
mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
// 就绪
onFixedRotationStatePrepared();
}
private void onFixedRotationStatePrepared() {
// 2.Resolve the rotated configuration.
onConfigurationChanged(getParent().getConfiguration());
final ActivityRecord r = asActivityRecord();
if (r != null && r.hasProcess()) {
// WPC 重新监听 ActivityReocord 配置,并获得一次最新的配置
r.app.registerActivityConfigurationListener(r);
}
}
根据新方向计算出的数据,最终被包装到 ActivityRecord#mFixedRotationTransformState,然后 ActivityRecord 要利用这些数据更新配置
java
// WindowContainer.java
public void onConfigurationChanged(Configuration newParentConfig) {
// 1. update config
super.onConfigurationChanged(newParentConfig);
//2. update surface position
updateSurfacePositionNonOrganized();
scheduleAnimation();
if (mOverlayHost != null) {
mOverlayHost.dispatchConfigurationChanged(getConfiguration());
}
}
update config
配置更新的流程,我在屏幕旋转系列文章中已经分析过,这里只重点分析与 fixed rotation 相关的配置解析。
java
// WindowToken.java
void resolveOverrideConfiguration(Configuration newParentConfig) {
super.resolveOverrideConfiguration(newParentConfig);
if (isFixedRotationTransforming()) {
// resolved override config 从 fixed rotration config 中更新
getResolvedOverrideConfiguration().updateFrom(
mFixedRotationTransformState.mRotatedOverrideConfiguration);
}
}
ActivityRecord 的 resolved override config 从 fixed rotation 配置更新后,就会导致 full config, merged override config 等的更新,然后还会通知监听者配置改变,如下
java
// WindowContainer.java
public void onConfigurationChanged(Configuration newParentConfig) {
mResolvedTmpConfig.setTo(mResolvedOverrideConfiguration);
// 解析 resolved override config
resolveOverrideConfiguration(newParentConfig);
// 更新 full config
mFullConfiguration.setTo(newParentConfig);
mFullConfiguration.windowConfiguration.unsetAlwaysOnTop();
mFullConfiguration.updateFrom(mResolvedOverrideConfiguration);
// 更新 merged override config
onMergedOverrideConfigurationChanged();
if (!mResolvedTmpConfig.equals(mResolvedOverrideConfiguration)) {
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
// 通知监听者 resolved override config change
mChangeListeners.get(i).onRequestedOverrideConfigurationChanged(
mResolvedOverrideConfiguration);
}
}
for (int i = mChangeListeners.size() - 1; i >= 0; --i) {
// 通知监听者,mergd override config change
mChangeListeners.get(i).onMergedOverrideConfigurationChanged(
mMergedOverrideConfiguration);
}
for (int i = getChildCount() - 1; i >= 0; --i) {
// 把 ActivityRecord 的 full config,分发给 WindowState,让他们也更新配置
dispatchConfigurationToChild(getChildAt(i), mFullConfiguration);
}
}
监听者其中之一就是 WPC,它是进程在 WMS 中的代表,它会把新配置发送给 app 端,如下
java
// ConfigurationContainer.java
public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
// 就是给 mRequestedOverrideConfiguration 赋值
updateRequestedOverrideConfiguration(overrideConfiguration);
final ConfigurationContainer parent = getParent();
// WPC 处理配置改变
// WPC 没有 parent,因此传入参数为 EMPTY
onConfigurationChanged(parent != null ? parent.getConfiguration() : Configuration.EMPTY);
}
java
// WindowProcessController.java
public void onConfigurationChanged(Configuration newGlobalConfig) {
// 通过基类更新配置
super.onConfigurationChanged(newGlobalConfig);
// ...
// 分发配置
dispatchConfiguration(config);
}
void dispatchConfiguration(Configuration config) {
// ...
scheduleConfigurationChange(mThread, config);
}
private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
mHasCachedConfiguration = false;
try {
// 通知 app 配置改变
mAtm.getLifecycleManager().scheduleTransaction(thread,
ConfigurationChangeItem.obtain(config, mLastTopActivityDeviceId));
} catch (Exception e) {
Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
}
}
update surface position
ActivityRecord 更新了 fixed rotation 的配置后,还需要更新 surface position
java
// WindowContainer.java
final void updateSurfacePositionNonOrganized() {
if (isOrganized()) return;
// 通过 sync transaction 来 update surface position
// 虽然 WindowToken 有复写,但是仍然是通过基类实现
updateSurfacePosition(getSyncTransaction());
}
java
// WindowContianer.java
void updateSurfacePosition(Transaction t) {
if (mSurfaceControl == null || mSurfaceAnimator.hasLeash() || mSurfaceFreezer.hasLeash()) {
return;
}
if (isClosingWhenResizing()) {
} else {
// 获取 ActivityRecord surface 相对于 parent surface 的偏移x,y 坐标
getRelativePosition(mTmpPos);
}
// 计算旋转角度差
final int deltaRotation = getRelativeDisplayRotation();
if (mTmpPos.equals(mLastSurfacePosition) && deltaRotation == mLastDeltaRotation) {
return;
}
// 先设置 ActivityRecord surface 相对于 parent surface 的偏移 x,y 坐标
t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
// set first, since we don't want rotation included in this (for now).
mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.useShellTransitionsRotation()) {
if (deltaRotation != Surface.ROTATION_0) {
// 根据旋转角度差,update surface position
// 虽然 WindowToken 有复写,但是还是通过基类实现
updateSurfaceRotation(t, deltaRotation, null /* positionLeash */);
getPendingTransaction().setFixedTransformHint(mSurfaceControl,
getWindowConfiguration().getDisplayRotation());
} else if (deltaRotation != mLastDeltaRotation) {
}
}
mLastDeltaRotation = deltaRotation;
}
update ActivityRecord surface position
- 先计算 ActivityRecord surface 相对于 parent 的坐标偏移,然后设置给 ActivityRecord surface。ActivityRecord 一般都是填充 parent 的,因此这个偏移量都是 0 。
- 计算旋转角度差,然后根据它来 update surface position。
来看下如何计算旋转角度差
java
// WindowContainer.java
int getRelativeDisplayRotation() {
final WindowContainer parent = getParent();
if (parent == null) return Surface.ROTATION_0;
// 获取自己的旋转角度
// 此时返回的是 fixed rotation 的旋转角度
final int rotation = getWindowConfiguration().getDisplayRotation();
// 获取 parent 的旋转角度
final int parentRotation = parent.getWindowConfiguration().getDisplayRotation();
// 计算旋转角度差
// 注意,是从 parent rotation 减去自己的 rotation
return RotationUtils.deltaRotation(rotation, parentRotation);
}
java
// RotationUtils.java
public static int deltaRotation(@Rotation int oldRotation, @Rotation int newRotation) {
int delta = newRotation - oldRotation;
if (delta < 0) delta += 4;
return delta;
}
计算出旋转角度差后,再来 update ActivityRecord surface position,也就是先 rotation 再 translate。
java
// WindowContainer.java
protected void updateSurfaceRotation(Transaction t, @Surface.Rotation int deltaRotation,
@Nullable SurfaceControl positionLeash) {
// parent must be non-null otherwise deltaRotation would be 0.
// 1. rotate surface
RotationUtils.rotateSurface(t, mSurfaceControl, deltaRotation);
mTmpPos.set(mLastSurfacePosition.x, mLastSurfacePosition.y);
final Rect parentBounds = getParent().getBounds();
final boolean flipped = (deltaRotation % 2) != 0;
// 2. 计算偏移,并设置为 surface
RotationUtils.rotatePoint(mTmpPos, deltaRotation,
flipped ? parentBounds.height() : parentBounds.width(),
flipped ? parentBounds.width() : parentBounds.height());
t.setPosition(positionLeash != null ? positionLeash : mSurfaceControl,
mTmpPos.x, mTmpPos.y);
}
// RotationUtils.java
/**
* Rotates a surface CCW around the origin (eg. a 90-degree rotation will result in the
* bottom-left being at the origin). Use {@link #rotatePoint} to transform the top-left
* corner appropriately.
*/
public static void rotateSurface(SurfaceControl.Transaction t, SurfaceControl sc,
@Rotation int rotation) {
// Note: the matrix values look inverted, but they aren't because our coordinate-space
// is actually left-handed.
// Note: setMatrix expects values in column-major order.
switch (rotation) {
// ...
case ROTATION_90:
t.setMatrix(sc, 0.f, -1.f, 1.f, 0.f);
break;
}
}
/**
* Rotates a point CCW within a rectangle of size parentW x parentH with top/left at the
* origin as if the point is stuck to the rectangle. The rectangle is transformed such that
* it's top/left remains at the origin after the rotation.
*/
public static void rotatePoint(Point inOutPoint, @Rotation int rotation,
int parentW, int parentH) {
int origX = inOutPoint.x;
switch (rotation) {
// ...
case ROTATION_90:
inOutPoint.x = inOutPoint.y;
inOutPoint.y = parentW - origX;
return;
}
}
TODO: 我并没有看懂这里的 rotation 和 translate 是计算原理,如果有高手知道,请赐教。