基本概念和原理
是什么
FixedRotation解决在activity启动时发生方向变化的时候,需要旋转动画的问题。FixedRotation能够保存当前方向,走无缝旋转逻辑,从而实现activity的无缝切换。
原理
当启动Acitivity时判断到方向发生改变的时候,系统会模拟转屏后的相关信息(DisplayAdjustment),然后将这个相关信息传给应用。当应用拿到新的信息之后发起绘制,系统会基于旧方向对新方向进行方向补偿,这样就不需要进行转屏,Activity的切换就可以使用原过渡动画,过渡动画完成之后再进行转屏,从而实现无缝旋转。
时机
进行Fixedrotation必须在应用绘制好之前就进行判断,也就是onResume之前要确定。 否则应用已经绘制完成那么在走Fixedrotation还是要重新绘制会导致屏幕跳闪。
源码流程
基于AOSP android-12代码分析
流程起点
resumeTopActivity时从updateOrientation调用到handleTopActivityLaunchingInDifferentOrientation开始流程
调用堆栈如下:
handleTopActivityLaunchingInDifferentOrientation:1739, DisplayContent (com.android.server.wm) updateOrientation:1688, DisplayContent (com.android.server.wm) updateOrientation:1640, DisplayContent (com.android.server.wm) ensureVisibilityAndConfig:1894, RootWindowContainer (com.android.server.wm) resumeTopActivity:1499, TaskFragment (com.android.server.wm) resumeTopActivityInnerLocked:5875, Task (com.android.server.wm) resumeTopActivityUncheckedLocked:5801, Task (com.android.server.wm) resumeFocusedTasksTopActivities:2544, RootWindowContainer (com.android.server.wm) resumeFocusedTasksTopActivities:2530, RootWindowContainer (com.android.server.wm) completePause:1877, TaskFragment (com.android.server.wm) activityPaused:6582, ActivityRecord (com.android.server.wm) activityPaused:182, ActivityClientController (com.android.server.wm) onTransact:556, IActivityClientController$Stub (android.app) onTransact:122, ActivityClientController (com.android.server.wm) execTransactInternal:1190, Binder (android.os) execTransact:1149, Binder (android.os)
判断是否设置FixedRotation
DisplayContent#handleTopActivityLaunchingInDifferentOrientation
前面的大段逻辑都是在判断不能设置FixedRotation的例外情况,return false
/**
* 当一个处于不同的orientation的Activity被启动时,我们需要在一段时间内
* 保持固定的display rotation,直到启动动画完成,以免以错误的orientation
* 显示先前的Activity
*
* @param r 可能改变display orientation的启动中的Activity
* @param checkOpening 是否需要检查活动正在执行transition;
* 如果caller不确定活动是否正在launching,设置true
* @return fixed rotation启动了返回true
*/
boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
boolean checkOpening) {
if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
return false;
}
if (r.isFinishingFixedRotationTransform()) {
return false;
}
if (r.hasFixedRotationTransform()) {
// It has been set and not yet finished.
return true;
}
if (r.isVisible()) {
return false;
}
if (!r.occludesParent()) {
// 启动半透明或浮动Activity,在后台有一个可见Activity,
// 那么他仍需要rotation动画来覆盖Activity的配置变化
if (!(mFixedRotationLaunchingApp != null && mFixedRotationLaunchingApp != r)) {
return false;
}
}
if (r.attachedToProcess() && mayImeShowOnLaunchingActivity(r)) {
// 目前还不知道IME窗口何时能准备好。Reject这种情况,
// 以避免在不一致的方向上显示IME而出现闪动。
return false;
}
if (checkOpening) {
if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) {
// 没有activity switch时设置了不同的orientation或transition被unset{@link #mSkipAppTransitionAnimation}
return false;
}
if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
// 如果Actvity正在执行或已经完成了生命周期回调,那么就使用正常的旋转动画,
// 这样就可以立即更新显示信息(见 updateDisplayAndOrientation)。
// 这可以防止出现兼容性问题,比如在Activity#onCreate中
// 调用setRequestedOrientation,然后获取显示信息。
// 如果应用了FixedRotation,显示的旋转仍然是旧的。
// 除非客户端在Ajustments arrive后再次获取旋转。
return false;
}
} else if (r != topRunningActivity()) {
// If the transition has not started yet, the activity must be the top.
return false;
}
if (mLastWallpaperVisible && r.windowsCanBeWallpaperTarget()
&& mFixedRotationTransitionListener.mAnimatingRecents == null) {
// 如果没有在执行recents动画(maybe上滑到桌面场景),使用正常的旋转动画来改变可见的壁纸方向
return false;
}
final int rotation = rotationForActivityInDifferentOrientation(r);
if (rotation == ROTATION_UNDEFINED) {
// displayrotation不会被当前的topActivity所改变
// 客户端对于前一个rotated activity的ajustment需要提前清除
// 否则如果当前的top是在同一个process里,他能获取到旋转后的state
// transfrom将在后面通过transition回调清除,以确保动画流畅
if (hasTopFixedRotationLaunchingApp()) {
mFixedRotationLaunchingApp.notifyFixedRotationTransform(false /* enabled */);
}
return false;
}
if (!r.getDisplayArea().matchParentBounds()) {
// Because the fixed rotated configuration applies to activity directly, if its parent
// has it own policy for bounds, the activity bounds based on parent is unknown.
return false;
}
setFixedRotationLaunchingApp(r, rotation);
return true;
}
设置FixedRotation
DisplayContent#setFixedRotationLaunchingApp
/**
* Sets the provided record to {@link #mFixedRotationLaunchingApp} if possible to apply fixed
* rotation transform to it and indicate that the display may be rotated after it is launched.
*/
void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Rotation int rotation) {
final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp;
if (prevRotatedLaunchingApp == r
&& r.getWindowConfiguration().getRotation() == rotation) {
// The given launching app and target rotation are the same as the existing ones.
return;
}
if (prevRotatedLaunchingApp != null
&& prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation
// It is animating so we can expect there will have a transition callback.
&& prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)) {
// 可能存在多个Activity连续启动的情况。因为他们的rotation是一样的,
// 所以transformed state可以共享以避免重复的繁重操作。
r.linkFixedRotationTransform(prevRotatedLaunchingApp);
if (r != mFixedRotationTransitionListener.mAnimatingRecents) {
// 只更新普通activity的record,这样当transition完成他成为top activity
// 时就可以更新dislay rotation. 近期任务可以在recents animation完成时处理
setFixedRotationLaunchingAppUnchecked(r, rotation);
}
return;
}
if (!r.hasFixedRotationTransform()) {
// 模拟出转屏后的屏幕
startFixedRotationTransform(r, rotation);
}
setFixedRotationLaunchingAppUnchecked(r, rotation);
if (prevRotatedLaunchingApp != null) {
prevRotatedLaunchingApp.finishFixedRotationTransform();
}
}
setFixedRotationLaunchingApp 时序图
DisplayContent#startFixedRotationTransform
-
基于新方向得到displayInfo的configuration
-
得到计算DisplayCutout的信息方向
private void startFixedRotationTransform(WindowToken token, int rotation) {
mTmpConfiguration.unset();
// 计算rotation模拟的displayinfo
final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
// 计算rotation模拟的displaycutout、roundedcorner、indicatorbounds
final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final PrivacyIndicatorBounds indicatorBounds =
calculatePrivacyIndicatorBoundsForRotation(rotation);
final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info,
cutout, roundedCorners, indicatorBounds);
// 使用上面计算的信息模拟屏幕
token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}
startFixedRotationTransform时序图
计算模拟旋转信息并传递给app端
WindowToken#applyFixedRotationTransform
-
创建FixedRotationTransformState对象
-
模拟inset信息 (使用FixedRotation之前状态栏、导航栏的状态)
-
通知应用 onConfigurationChanged
-
mFixedRotationTransformState将在发送给client端的FixedRotationAjustments创建时使用
/** Applies the rotated layout environment to this token in the simulated rotated display. */
void applyFixedRotationTransform(DisplayInfo info, DisplayFrames displayFrames,
Configuration config) {
if (mFixedRotationTransformState != null) {
mFixedRotationTransformState.disassociate(this);
}
mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
new Configuration(config), mDisplayContent.getRotation());
mFixedRotationTransformState.mAssociatedTokens.add(this);
mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames,
mFixedRotationTransformState.mBarContentFrames);
onFixedRotationStatePrepared();
}
DisplayPolicy#simulateLayoutDisplay
计算用于layout window的displayframes(logical size、rotation和cutout已经设置好) 这个方法仅变更传进来的displayframes、insets和一些临时state,不改变真正用于屏幕窗口显示的window frames
void simulateLayoutDisplay(DisplayFrames displayFrames, SparseArray<Rect> barContentFrames) {
final WindowFrames simulatedWindowFrames = new WindowFrames();
if (mNavigationBar != null) {
simulateLayoutDecorWindow(mNavigationBar, displayFrames, simulatedWindowFrames,
barContentFrames, contentFrame -> layoutNavigationBar(displayFrames,
contentFrame));
}
if (mStatusBar != null) {
simulateLayoutDecorWindow(mStatusBar, displayFrames, simulatedWindowFrames,
barContentFrames, contentFrame -> layoutStatusBar(displayFrames, contentFrame));
}
}
WindowToken#onFixedRotationStatePrepared
使旋转后的state在windowcontainer和client端进程生效
private void onFixedRotationStatePrepared() {
// 先发送ajustment info到客户端
// 这样客户端收到configuration change时可以获取旋转后的display metrics
notifyFixedRotationTransform(true /* enabled */);
// Resolve the rotated configuration.
onConfigurationChanged(getParent().getConfiguration());
final ActivityRecord r = asActivityRecord();
if (r != null && r.hasProcess()) {
// The application needs to be configured as in a rotated environment for compatibility.
// This registration will send the rotated configuration to its process.
r.app.registerActivityConfigurationListener(r);
}
}
WindowToken#notifyFixedRotationTransform
通知应用端启用/禁用 displayinfo的rotation adjustment
void notifyFixedRotationTransform(boolean enabled) {
FixedRotationAdjustments adjustments = null;
// 一个token可以包含相同进程或不同进程的窗口
// 该列表用于避免多次向进程发送相同的adjustment
ArrayList<WindowProcessController> notifiedProcesses = null;
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowState w = mChildren.get(i);
final WindowProcessController app;
if (w.mAttrs.type == TYPE_APPLICATION_STARTING) {
// Use the host activity because starting window is controlled by window manager.
final ActivityRecord r = asActivityRecord();
if (r == null) {
continue;
}
app = r.app;
} else {
app = mWmService.mAtmService.mProcessMap.getProcess(w.mSession.mPid);
}
if (app == null || !app.hasThread()) {
continue;
}
if (notifiedProcesses == null) {
notifiedProcesses = new ArrayList<>(2);
// 使用mFixedRotationTransformState里的信息构建FixedRotationAdjustments
adjustments = enabled ? createFixedRotationAdjustmentsIfNeeded() : null;
} else if (notifiedProcesses.contains(app)) {
continue;
}
notifiedProcesses.add(app);
try {
// FixedRotationAdjustmentsItem通知app端
mWmService.mAtmService.getLifecycleManager().scheduleTransaction(
app.getThread(), FixedRotationAdjustmentsItem.obtain(token, adjustments));
} catch (RemoteException e) {
Slog.w(TAG, "Failed to schedule DisplayAdjustmentsItem to " + app, e);
}
}
}
ActivityThread端接收FixedRotationAdjustments
ActivityThread#handleFixedRotationAdjustments
FixedRotationAdjustmentsItem是一个ClientTransactionItem,流程将跳转到app端进程的ActivityThread中执行。这里定义了Consumer接口: displayAdjustments -> displayAdjustments.setFixedRotationAdjustments(fixedRotationAdjustments)
/**
* 应用rotation adjustment以覆盖属于所提供的token里的显示信息。
* 如果是activity token,那么adjustment也适用于application,
* 因为activity的外观通常对application resource更敏感。
* @param token就是上面FixedRotationAdjustmentsItem.obtain里传递的token
* @param fixedRotationAdjustments 用于覆盖Resources#mOverrideDisplayAdjustments
* 如果是null就清空现有的
*/
@Override
public void handleFixedRotationAdjustments(@NonNull IBinder token,
@Nullable FixedRotationAdjustments fixedRotationAdjustments) {
final Consumer<DisplayAdjustments> override = fixedRotationAdjustments != null
? displayAdjustments -> displayAdjustments
.setFixedRotationAdjustments(fixedRotationAdjustments)
: null;
if (!mResourcesManager.overrideTokenDisplayAdjustments(token, override)) {
// No resources are associated with the token.
return;
}
if (mActivities.get(token) == null) {
// Nothing to do for application if it is not an activity token.
return;
}
overrideApplicationDisplayAdjustments(token, override);
}
将DisplayAdjustments设置进Application的Resources
调用Resouces#overrideDisplayAdjustments方法设置resources的mOverrideDisplayAdjustments
/**
* 将最后一次override应用与application的reources以实现兼容,
* 因为显示用的resources来源于application, e.g.
* applicationContext.getSystemService(DisplayManager.class).getDisplay(displayId)
* and the deprecated usage:
* applicationContext.getSystemService(WindowManager.class).getDefaultDisplay();
*
* @param token The owner and target of the override.
* @param 用于override的displayadjustment.如果是null就删除token对应的override,然后pop出最后一个
*/
private void overrideApplicationDisplayAdjustments(@NonNull IBinder token,
@Nullable Consumer<DisplayAdjustments> override) {
final Consumer<DisplayAdjustments> appOverride;
if (mActiveRotationAdjustments == null) {
mActiveRotationAdjustments = new ArrayList<>(2);
}
if (override != null) {
mActiveRotationAdjustments.add(Pair.create(token, override));
appOverride = override;
} else {
mActiveRotationAdjustments.removeIf(adjustmentsPair -> adjustmentsPair.first == token);
appOverride = mActiveRotationAdjustments.isEmpty()
? null
: mActiveRotationAdjustments.get(mActiveRotationAdjustments.size() - 1).second;
}
mInitialApplication.getResources().overrideDisplayAdjustments(appOverride);
}
ResourcesManager#applyConfigurationToResources
ActivityThread将在ViewRootImpl的configChangedCallback中调用applyConfigurationToResources应用adjustments,目前adjustment生效的唯一场景就是模拟将应用程序放置在旋转显示中。 后面在app绘制流程时将使用调整过的configuration进行绘制。
/** Applies the global configuration to the managed resources. */
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat, @Nullable DisplayAdjustments adjustments) {
...
DisplayMetrics displayMetrics = getDisplayMetrics();
if (adjustments != null) {
// 目前adjustment生效的唯一场景就是模拟将应用程序放置在旋转显示中。
adjustments.adjustGlobalAppMetrics(displayMetrics);
}
Resources.updateSystemConfiguration(config, displayMetrics, compat);
...
}
至此,FixedRotation的基本原理和流程注解已经梳理完成。