Anrdoid WMS动画系统初探(二)
基于AndroidR源码分析
上一篇我分析了WMS动画系统的执行原理和窗口动画的加载、执行流程
本篇来继续分析Activity过渡动画的流程
过渡动画
Activity的切换过程
前一个Activity从resume状态变成pause状态,后一个Activity进入到resume状态,将前一个resume状态的窗口设置成不可见,后一个窗口设置成可见。
ActivityStack#resumeTopActivityUncheckedLocked -> ActivityStack#resumeTopActivityInnerLocked ->
setAppTransition执行过后,如果前一个激活的Activity组件进入到Paused状态了,并且客户端进程已经启动了 ,这个时候就会调用ActivityRecord#setVisibility方法设置窗口可见性。
ActivityStack#resumeTopActivityInnerLocked
private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) { ... // We are starting up the next activity, so tell the window manager // that the previous one will be hidden soon. This way it can know // to ignore it when computing the desired screen orientation. boolean anim = true; final DisplayContent dc = taskDisplayArea.mDisplayContent; if (prev != null) { if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare close transition: prev=" + prev); if (mStackSupervisor.mNoAnimActivities.contains(prev)) { anim = false; dc.prepareAppTransition(TRANSIT_NONE, false); } else { dc.prepareAppTransition( prev.getTask() == next.getTask() ? TRANSIT_ACTIVITY_CLOSE : TRANSIT_TASK_CLOSE, false); } prev.setVisibility(false); } else { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: prev=" + prev); if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; dc.prepareAppTransition(TRANSIT_NONE, false); } else { dc.prepareAppTransition( prev.getTask() == next.getTask() ? TRANSIT_ACTIVITY_OPEN : next.mLaunchTaskBehind ? TRANSIT_TASK_OPEN_BEHIND : TRANSIT_TASK_OPEN, false); } } } else { if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare open transition: no previous"); if (mStackSupervisor.mNoAnimActivities.contains(next)) { anim = false; dc.prepareAppTransition(TRANSIT_NONE, false); } else { dc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false); } } if (anim) { next.applyOptionsLocked(); } else { next.clearOptionsLocked(); } ... if (next.attachedToProcess()) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped + " visibleRequested=" + next.mVisibleRequested); // If the previous activity is translucent, force a visibility update of // the next activity, so that it's added to WM's opening app list, and // transition animation can be set up properly. // For example, pressing Home button with a translucent activity in focus. // Launcher is already visible in this case. If we don't add it to opening // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation. final boolean lastActivityTranslucent = lastFocusedStack != null && (lastFocusedStack.inMultiWindowMode() || (lastFocusedStack.mLastPausedActivity != null && !lastFocusedStack.mLastPausedActivity.occludesParent())); // This activity is now becoming visible. if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) { next.setVisibility(true); } ... }复制代码
AppTransition#prepareAppTransitionLocked
boolean prepareAppTransitionLocked(@TransitionType int transit, boolean alwaysKeepCurrent, @TransitionFlags int flags, boolean forceOverride) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Prepare app transition: transit=%s %s alwaysKeepCurrent=%b displayId=%d " + "Callers=%s", appTransitionToString(transit), this, alwaysKeepCurrent, mDisplayContent.getDisplayId(), Debug.getCallers(5)); final boolean allowSetCrashing = !isKeyguardTransit(mNextAppTransition) && transit == TRANSIT_CRASHING_ACTIVITY_CLOSE; if (forceOverride || isKeyguardTransit(transit) || !isTransitionSet() || mNextAppTransition == TRANSIT_NONE || allowSetCrashing) { // keyguardtransition场景、未设置过transition、TRANSIT_CRASHING_ACTIVITY_CLOSE、 forceOverride setAppTransition(transit, flags); } // We never want to change from a Keyguard transit to a non-Keyguard transit, as our logic // relies on the fact that we always execute a Keyguard transition after preparing one. We // also don't want to change away from a crashing transition. else if (!alwaysKeepCurrent && !isKeyguardTransit(mNextAppTransition) && mNextAppTransition != TRANSIT_CRASHING_ACTIVITY_CLOSE) { if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) { // 打开一个新Task的动画比关闭的优先级高(取代关闭的动画) setAppTransition(transit, flags); } else if (transit == TRANSIT_ACTIVITY_OPEN && isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) { // 打开一个新Activity的动画比关闭的优先级高(取代关闭的动画) setAppTransition(transit, flags); } else if (isTaskTransit(transit) && isActivityTransit(mNextAppTransition)) { // Task动画优先级总是比Activity的高 setAppTransition(transit, flags); } } boolean prepared = prepare(); if (isTransitionSet()) { removeAppTransitionTimeoutCallbacks(); mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable, APP_TRANSITION_TIMEOUT_MS); } return prepared; }复制代码
prepareAppTransition这一步是为窗口准备transition动画
四种场景要重新设置AppTransition
keyguardtransition场景、未设置过transition、TRANSIT_CRASHING_ACTIVITY_CLOSE、forceOverride
上次给Activity设置的切换操作是TRANSIT_TASK_CLOSE,那么可以调用setAppTransition,因为打开的动画要比关闭的动画优先级要高
上次给Activity设置的切换操作是TRANSIT_ACTIVITY_CLOSE,那么可以调用setAppTransition,因为打开的动画要比关闭的动画优先级要高
上次给Activity设置的切换操作是ActivityTransition,这一次是TaskTransition,那么可以调用setAppTransition,因为Task动画优先级总是比Activity的高
ActivityRecord#setVisibility
void setVisibility(boolean visible, boolean deferHidingClient) { final AppTransition appTransition = getDisplayContent().mAppTransition; // 如果已经设置not isible则不再设置isibility为false,防止已经not visible的app又添加到closing apps list // 相反即使是已经visible的仍然要设置visibility为true以确保添加到opening apps list中 // 这样就可以选择到正确的transition if (!visible && !mVisibleRequested) { if (!deferHidingClient && mLastDeferHidingClient) { // We previously deferred telling the client to hide itself when visibility was // initially set to false. Now we would like it to hide, so go ahead and set it. mLastDeferHidingClient = deferHidingClient; setClientVisible(false); } return; } ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", appToken, visible, appTransition, isVisible(), mVisibleRequested, Debug.getCallers(6)); onChildVisibilityRequested(visible); final DisplayContent displayContent = getDisplayContent(); // mOpenningApps和mClosingApps是WMS的成员,先将this移除,后面再根据visible添加 displayContent.mOpeningApps.remove(this); displayContent.mClosingApps.remove(this); waitingToShow = false; mVisibleRequested = visible; mLastDeferHidingClient = deferHidingClient; if (!visible) { // 如果应用程序在可见时已死,我们将其死窗口保留在屏幕上。 // 现在这个应用程序是不可见的,我们可以删除它。如果再次显示,将重新启动。 removeDeadWindows(); } else { if (!appTransition.isTransitionSet() && appTransition.isReady()) { // 添加到mOpeningApps 如果!isTransitionSet() 但isReady。 // 这意味着正在做screen freeze,将等待所有打开的应用程序准备好后unfreeze。 displayContent.mOpeningApps.add(this); } startingMoved = false; // If the token is currently hidden (should be the common case), or has been // stopped, then we need to set up to wait for its windows to be ready. if (!isVisible() || mAppStopped) { clearAllDrawn(); // If the app was already visible, don't reset the waitingToShow state. if (!isVisible()) { waitingToShow = true; // If the client isn't hidden, we don't need to reset the drawing state. if (!isClientVisible()) { // Let's reset the draw state in order to prevent the starting window to be // immediately dismissed when the app still has the surface. forAllWindows(w -> { if (w.mWinAnimator.mDrawState == HAS_DRAWN) { w.mWinAnimator.resetDrawState(); // Force add to mResizingWindows, so that we are guaranteed to get // another reportDrawn callback. w.resetLastContentInsets(); } }, true /* traverseTopToBottom */); } } } // 通知应用程序进程将Activity组件设置为true // 在我们使一个应用程序可见,但等待Transition的场景下 // 我们仍然需要告诉客户端使其窗口可见,以便它们被绘制。 // 否则,我们将等待执行过渡,直到所有的窗口都已绘制。 setClientVisible(true); requestUpdateWallpaperIfNeeded(); ProtoLog.v(WM_DEBUG_ADD_REMOVE, "No longer Stopped: %s", this); mAppStopped = false; transferStartingWindowFromHiddenAboveTokenIfNeeded(); } // If we are preparing an app transition, then delay changing // the visibility of this token until we execute that transition. // Note that we ignore display frozen since we want the opening / closing transition type // can be updated correctly even display frozen, and it's safe since in applyAnimation will // still check DC#okToAnimate again if the transition animation is fine to apply. // 这个if分支在动画设置完成并且屏幕不冻屏,且亮屏、Display OK的情况下才会走 if (okToAnimate(true /* ignoreFrozen */) && appTransition.isTransitionSet()) { if (visible) { displayContent.mOpeningApps.add(this); mEnteringAnimation = true; } else { displayContent.mClosingApps.add(this); mEnteringAnimation = false; } if (appTransition.getAppTransition() == TRANSIT_TASK_OPEN_BEHIND) { // We're launchingBehind, add the launching activity to mOpeningApps. final WindowState win = getDisplayContent().findFocusedWindow(); if (win != null) { final ActivityRecord focusedActivity = win.mActivityRecord; if (focusedActivity != null) { ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "TRANSIT_TASK_OPEN_BEHIND, adding %s to mOpeningApps", focusedActivity); // Force animation to be loaded. displayContent.mOpeningApps.add(focusedActivity); } } } return; } commitVisibility(visible, true /* performLayout */); updateReportedVisibilityLocked(); }复制代码
Activity的窗口添加是Activity 的onResume方法中添加的。添加窗口、measure、layout、draw等一系列操作完成后便会调用WMS.finishDrawingWindow()来通知WMS,该窗口已经绘制好了,可以开始做动画了。 WindowSurfacePlacer的requestTraversal方法只是向WMS的主线程发送了一个DO_TRAVERSAL消息,WMS收到这个消息后,performSurfacePlacement方法就会执行。
RootWindowContainer#performSurfacePlacementNoTrace
void performSurfacePlacementNoTrace() { // 前文梳理的窗口动画设置流程是从这里作为入口的 try { applySurfaceChangesTransaction(); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces"); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); } mWmService.mAnimator.executeAfterPrepareSurfacesRunnables(); // 这里开启过渡动画的设置流程 checkAppTransitionReady(surfacePlacer); }复制代码
上篇中分析的窗口动画设置流程也是从这里作为入口的
RootWindowContainer#checkAppTransitionReady -> AppTransitionController#handleAppTransitionReady
AppTransitionController#handleAppTransitionReady
/** * Handle application transition for given display. */ void handleAppTransitionReady() { mTempTransitionReasons.clear(); // transitionGoodToGo会判断多种case情况下,不用执行动画的情况, // 比如正在做转屏动画,mOpeningApps中任何一个allDrawn不等于true等 if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons) || !transitionGoodToGo(mDisplayContent.mChangingContainers, mTempTransitionReasons)) { return; } Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO"); // 获取前面流程设置好的transition final AppTransition appTransition = mDisplayContent.mAppTransition; int transit = appTransition.getAppTransition(); if (mDisplayContent.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) { transit = WindowManager.TRANSIT_UNSET; } // 做一些重置工作 mDisplayContent.mSkipAppTransitionAnimation = false; mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear(); appTransition.removeAppTransitionTimeoutCallbacks(); mDisplayContent.mWallpaperMayChange = false; int appCount = mDisplayContent.mOpeningApps.size(); for (int i = 0; i < appCount; ++i) { // 进入animation前清除mAnimatingExit标志 // 当窗口被移除或者relayout为不可见时会设置这个标志,这个会影响窗口可见性 // 我们需要在maybeUpdateTransitToWallpaper()前清除他 // 因为transition的选择依赖于壁纸target的可见性 mDisplayContent.mOpeningApps.valueAtUnchecked(i).clearAnimatingFlags(); } appCount = mDisplayContent.mChangingContainers.size(); for (int i = 0; i < appCount; ++i) { // Clearing for same reason as above. final ActivityRecord activity = getAppFromContainer( mDisplayContent.mChangingContainers.valueAtUnchecked(i)); if (activity != null) { activity.clearAnimatingFlags(); } } // Adjust wallpaper before we pull the lower/upper target, since pending changes // (like the clearAnimatingFlags() above) might affect wallpaper target result. // Or, the opening app window should be a wallpaper target. mWallpaperControllerLocked.adjustWallpaperWindowsForAppTransitionIfNeeded( mDisplayContent.mOpeningApps); // 确定关闭和打开应用程序令牌集是否为墙纸目标,在这种情况下需要特殊的动画。 final boolean hasWallpaperTarget = mWallpaperControllerLocked.getWallpaperTarget() != null; final boolean openingAppHasWallpaper = canBeWallpaperTarget(mDisplayContent.mOpeningApps) && hasWallpaperTarget; final boolean closingAppHasWallpaper = canBeWallpaperTarget(mDisplayContent.mClosingApps) && hasWallpaperTarget; transit = maybeUpdateTransitToTranslucentAnim(transit); transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper, closingAppHasWallpaper); // Find the layout params of the top-most application window in the tokens, which is // what will control the animation theme. If all closing windows are obscured, then there is // no need to do an animation. This is the case, for example, when this transition is being // done behind a dream window. // 分别获取openingApp和closingapp以及changinapp final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers); final ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes); final ActivityRecord topOpeningApp = getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */); final ActivityRecord topClosingApp = getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */); final ActivityRecord topChangingApp = getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */); final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity); // 这里是使用RemoteAnimationAdapter覆盖transition(如果有的话) overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes); final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps) || containsVoiceInteraction(mDisplayContent.mOpeningApps); final int layoutRedo; mService.mSurfaceAnimationRunner.deferStartingAnimations(); try { // 创建、启动动画 applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction); handleClosingApps(); handleOpeningApps(); handleChangingApps(transit); appTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp, topChangingApp); final int flags = appTransition.getTransitFlags(); layoutRedo = appTransition.goodToGo(transit, topOpeningApp, mDisplayContent.mOpeningApps); handleNonAppWindowsInTransition(transit, flags); appTransition.postAnimationCallback(); appTransition.clear(); } finally { mService.mSurfaceAnimationRunner.continueStartingAnimations(); } mService.mTaskSnapshotController.onTransitionStarting(mDisplayContent); // 做一些清理工作 mDisplayContent.mOpeningApps.clear(); mDisplayContent.mClosingApps.clear(); mDisplayContent.mChangingContainers.clear(); mDisplayContent.mUnknownAppVisibilityController.clear(); // This has changed the visibility of windows, so perform // a new layout to get them all up-to-date. mDisplayContent.setLayoutNeeded(); mDisplayContent.computeImeTarget(true /* updateImeTarget */); mService.mAtmService.mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting( mTempTransitionReasons); if (transit == TRANSIT_SHOW_SINGLE_TASK_DISPLAY) { mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> { mService.mAtmInternal.notifySingleTaskDisplayDrawn(mDisplayContent.getDisplayId()); }); } Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); mDisplayContent.pendingLayoutChanges |= layoutRedo | FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG; }复制代码
下面主要来梳理下创建和启动动画部分
AppTransitionController#applyAnimations
private void applyAnimations(ArraySet<ActivityRecord> openingApps, ArraySet<ActivityRecord> closingApps, @TransitionType int transit, LayoutParams animLp, boolean voiceInteraction) { if (transit == WindowManager.TRANSIT_UNSET || (openingApps.isEmpty() && closingApps.isEmpty())) { return; } // 获取要做动画的WindowContainer final ArraySet<WindowContainer> openingWcs = getAnimationTargets( openingApps, closingApps, true /* visible */); final ArraySet<WindowContainer> closingWcs = getAnimationTargets( openingApps, closingApps, false /* visible */); applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp, voiceInteraction); applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction); final AccessibilityController accessibilityController = mDisplayContent.mWmService.mAccessibilityController; if (accessibilityController != null) { accessibilityController.onAppWindowTransitionLocked( mDisplayContent.getDisplayId(), transit); } } private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps, @TransitionType int transit, boolean visible, LayoutParams animLp, boolean voiceInteraction) { final int wcsCount = wcs.size(); for (int i = 0; i < wcsCount; i++) { final WindowContainer wc = wcs.valueAt(i); // If app transition animation target is promoted to higher level, SurfaceAnimator // triggers WC#onAnimationFinished only on the promoted target. So we need to take care // of triggering AR#onAnimationFinished on each ActivityRecord which is a part of the // app transition. final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>(); for (int j = 0; j < apps.size(); ++j) { final ActivityRecord app = apps.valueAt(j); if (app.isDescendantOf(wc)) { transitioningDescendants.add(app); } } // WindowContainer.applyAnimation wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants); } } 复制代码
WindowContainer#applyAnimation -> WindowContainer#applyAnimationUnchecked
WindowContainer#applyAnimationUnchecked
protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, int transit, boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) { // 这一步加载了真正的动画,并封装到WindowAnimationSpec,构建AnimationAdapter final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp, transit, enter, isVoiceInteraction); AnimationAdapter adapter = adapters.first; AnimationAdapter thumbnailAdapter = adapters.second; if (adapter != null) { if (sources != null) { mSurfaceAnimationSources.addAll(sources); } // 启动动画 startAnimation(getPendingTransaction(), adapter, !isVisible(), ANIMATION_TYPE_APP_TRANSITION); if (adapter.getShowWallpaper()) { getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } if (thumbnailAdapter != null) { mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { }); } } }复制代码
从这里开始流程就和窗口动画相似了, mAppTransition.loadAnimation方法加载了真正的动画, 然后封装到WindowAnimationSpec,创建AnimationAdapter,WindowContainer的startAnimation方法最终会调用到AnimationAdapter的startAnimation启动动画
Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { final Pair<AnimationAdapter, AnimationAdapter> resultAdapters; ...... if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) { ...... } else if (isChanging) { ...... } else { mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_CLIP_AFTER_ANIM); // 这里才是调用getDisplayContent().mAppTransition.loadAnimation加载动画 final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction); if (a != null) { // Only apply corner radius to animation if we're not in multi window mode. // We don't want rounded corners when in pip or split screen. final float windowCornerRadius = !inMultiWindowMode() ? getDisplayContent().getWindowCornerRadius() : 0; // 将动画封装到WindowAnimationSpec AnimationAdapter adapter = new LocalAnimationAdapter( new WindowAnimationSpec(a, mTmpPoint, mTmpRect, getDisplayContent().mAppTransition.canSkipFirstFrame(), appStackClipMode, true /* isAppAnimation */, windowCornerRadius), getSurfaceAnimationRunner()); resultAdapters = new Pair<>(adapter, null); mNeedsZBoost = a.getZAdjustment() == Animation.ZORDER_TOP || AppTransition.isClosingTransit(transit); mTransit = transit; mTransitFlags = getDisplayContent().mAppTransition.getTransitFlags(); } else { resultAdapters = new Pair<>(null, null); } } return resultAdapters; }复制代码
WindowContainer#startAnimation -> SurfaceAnimator#startAnimation
这一步开始创建Leash并调用Animatable(WindowContainer)的onAnimationLeashCreated方法和AnimationAdapter(LocalAnimationAdapter)的startAnimation方法来开始执行动画。
后面动画的执行流程就和窗口动画的一致了,可以参考上篇的分析。
下一篇我将继续分析屏幕旋转动画的相关流程
屏幕旋转动画
施工中...
作者:汪和呆喵
链接:https://juejin.cn/post/7023676770016559118