Based on AndroidR source code analysis

In the last article, I analyzed the execution principle of WMS animation system and the loading and execution process of window animation

This article continues to analyze the flow of the Activity transition animation

Transition animations

The switching process of an Activity

When the first Activity changes from resume to pause and the second Activity moves to Resume, set the first resume window to invisible and the second resume window to visible.

ActivityStack#resumeTopActivityUncheckedLocked -> ActivityStack#resumeTopActivityInnerLocked ->

After setAppTransition is executed, if the previously activated Activity component has Paused and the client process has started, the ActivityRecord#setVisibility method is called to set window visibility.

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); }... }Copy the code

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 scenario, unset 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)) { // SetAppTransition (transit, flags); } else if (transit == TRANSIT_ACTIVITY_OPEN && isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) { // An animation that starts a new Activity takes precedence over an animation that closes (instead of closing) setAppTransition(transit, flags); } else if (isTaskTransit(transit) &&isActivityTransit (mNextAppTransition)) {// Task always has a higher priority than Activity setAppTransition(transit, flags); } } boolean prepared = prepare(); if (isTransitionSet()) { removeAppTransitionTimeoutCallbacks(); mHandler.postDelayed(mHandleAppTransitionTimeoutRunnable, APP_TRANSITION_TIMEOUT_MS); } return prepared; }Copy the code
  • PrepareAppTransition this step is to prepare the Transition animation for the window

There are four scenarios to reset the AppTransition

  1. Keyguardtransition scenario, transition not configured, TRANSIT_CRASHING_ACTIVITY_CLOSE, and forceOverride
  2. The last time you set the Activity to switch was TRANSIT_TASK_CLOSE, you can call setAppTransition because the animation that is on has higher priority than the animation that is closed
  3. The last time you set the Activity to switch was TRANSIT_ACTIVITY_CLOSE, you can call setAppTransition because open animations have higher priority than closed animations
  4. The last time you set the Activity to ActivityTransition, this time to TaskTransition, you can call setAppTransition because the Task animation takes precedence over the Activity animation

ActivityRecord#setVisibility

void setVisibility(boolean visible, boolean deferHidingClient) { final AppTransition appTransition = getDisplayContent().mAppTransition; // Isibility to false if not isible is set To prevent apps that are already visible from being added to the closing apps list, set the visibility to true even if the app is already visible to ensure that it is added to the opening apps list So you can choose the right 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 and mClosingApps is a member of the WMS, remove this first, later. According to the visible adding displayContent mOpeningApps. Remove (this); displayContent.mClosingApps.remove(this); waitingToShow = false; mVisibleRequested = visible; mLastDeferHidingClient = deferHidingClient; if (! Visible) {// If the application is dead while visible, we leave its dead window on the screen. // Now the application is invisible, we can delete it. If it is displayed again, the system restarts. removeDeadWindows(); } else { if (! AppTransition. IsTransitionSet () && appTransition. IsReady ()) {/ / added to mOpeningApps if! But isReady isTransitionSet (). // This means that you are doing screen freeze and will wait until all open applications are ready to 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 */); }}} // Tell the application process to set the Activity component to true // In the case of making an application visible but waiting for Transition // we still need to tell the client to make its Windows visible so that they can be drawn. // Otherwise, we will wait to execute the transition until all the Windows are drawn. 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. // The if branch is set after the animation is done and the screen is not frozen, And bright screen, Display OK will go under the condition of an if (okToAnimate ignoreFrozen (true / * * /) && 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(); }Copy the code

The window addition to the Activity is added to the Activity’s onResume method. After adding a window, measure, layout, draw, etc., it calls WMS. FinishDrawingWindow () to notify WMS that the window has been drawn and is ready to animate. The WindowSurfacePlacer’s requestTraversal method simply sends a DO_TRAVERSAL message to the main thread of the WMS. When the WMS receives this message, the performSurfacePlacement method is executed.

RootWindowContainer#performSurfacePlacementNoTrace

Void performSurfacePlacementNoTrace () {/ / above carding window animation setup process from here as the entrance to the 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); }Copy the code

The window animation setup process analyzed in the previous chapter also serves as the entry point from here

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;
    }
Copy the code

The next section will focus on creating and starting animations

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> 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); }}Copy the code

WindowContainer#applyAnimation -> WindowContainer#applyAnimationUnchecked

WindowContainer#applyAnimationUnchecked

protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, int transit, Boolean isVoiceInteraction, @Nullable ArrayList<WindowContainer> sources) {// This step loads the real animation and encapsulates it into the 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) -> { }); }}}Copy the code

From here to start the process is similar to window animation, mAppTransition. LoadAnimation method to load the real animation, then wrapped in WindowAnimationSpec, create AnimationAdapter, The startAnimation method of WindowContainer eventually calls the startAnimation of AnimationAdapter to start the animation

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); / / here is called getDisplayContent () mAppTransition. Final loadAnimation loading Animation 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; New WindowAnimationSpec(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; }Copy the code

WindowContainer#startAnimation -> SurfaceAnimator#startAnimation This step starts to create minti and call the onAnimationLeashCreated method of Animatable (WindowContainer) and startAnimat of AnimationAdapter (LocalAnimationAdapter) Ion method to start executing the animation.

Behind the animation of the execution process and window animation is consistent, you can refer to the analysis of the previous chapter.

In the next article I will continue to analyze the process of screen rotation animation

Screen rotation animation

The construction of…