Android WMS动画办理专栏
基本概念和原理
是什么
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的基本原理和流程注解现已梳理完结。