Android V app 冷启动(8) 动画结束

WMShell finish transition

当 Launcher 执行完远程动画之时,会通知 WMShell,最终调用 Transitions#onFinish(),如下

java 复制代码
// Transitions.java

// wct 为 null
// wctCB 为 null
private void onFinish(ActiveTransition active,
        @Nullable WindowContainerTransaction wct,
        @Nullable WindowContainerTransactionCallback wctCB) {
    final Track track = mTracks.get(active.getTrack());
    track.mActiveTransition = null;

    // ...
    
    // 1. apply finish transaction
    SurfaceControl.Transaction fullFinish = active.mFinishT;
    if (active.mMerged != null) {
       // ...
    }
    if (fullFinish != null) {
        fullFinish.apply();
    }
    
    // ...
    
    // 2. 通知 WMCore finish transition
    mOrganizer.finishTransition(active.mToken, wct, wctCB);
    
    // ...
}

WMShell 在 apply finish transaction 后,通知 WMCore finish transition。

WMCore finish transition

java 复制代码
// WindowOrganizerController.java

// t 为 null
// callback 为 null
public int finishTransition(@NonNull IBinder transitionToken,
        @Nullable WindowContainerTransaction t,
        @Nullable IWindowContainerTransactionCallback callback) {
    enforceTaskPermission("finishTransition()");
    final CallerInfo caller = new CallerInfo();
    final long ident = Binder.clearCallingIdentity();
    try {
        synchronized (mGlobalLock) {
            // ...
            
            // 根据 token 获取 Transition
            final Transition transition = Transition.fromBinder(transitionToken);
            
            // ...
            
            // 由 TransitionController 执行 finish transition
            mTransitionController.finishTransition(transition);
            mTransitionController.mFinishingTransition = null;
            
            // ...
        }
    } finally {
        Binder.restoreCallingIdentity(ident);
    }
}
java 复制代码
// TransitionController.java

void finishTransition(Transition record) {
    // ...
    
    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record);
    
    // 注意,先从 mPlayingTransitions 移除,在执行 Transition#finishTransition()
    mPlayingTransitions.remove(record);
    
    // ...
    
    record.finishTransition();
    
    // ...
}
java 复制代码
// Transition.java

void finishTransition() {
    // ...
    
    // apply cleanup transaction
    if (mCleanupTransaction != null) {
        mCleanupTransaction.apply();
        mCleanupTransaction = null;
    }
    
    // ...
    
    // 标记 finishing transition
    mController.mFinishingTransition = this;
    
    // ...
    
    // 1. 提交即将不可见的 ActivityRecord 
    // Commit all going-invisible containers
    for (int i = 0; i < mParticipants.size(); ++i) {
        final WindowContainer<?> participant = mParticipants.valueAt(i);
        final ActivityRecord ar = participant.asActivityRecord();
        if (ar != null) {
            final Task task = ar.getTask();
            if (task == null) continue;
            
            // 检测哪些 WindowToken 在 transitin finish 是还可见
            boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);

            if (isTransientLaunch(ar) && !ar.isVisibleRequested()
                    && mController.inCollectingTransition(ar)) {
                // ...
            }
            
            final boolean isScreenOff = ar.mDisplayContent == null
                    || ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF;
            if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) {
            
                // 检测 Activity 是否进入PIP
                final boolean commitVisibility = !checkEnterPipOnFinish(ar);
                
                if (commitVisibility) { // Activity不进入 PIP
                    ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                            "  Commit activity becoming invisible: %s", ar);
                    final SnapshotController snapController = mController.mSnapshotController;
                    if (mTransientLaunches != null && !task.isVisibleRequested()
                            && !task.isActivityTypeHome()) {
                        // ...
                    }
                    
                    // 提交 ActivityRecord 不可见
                    ar.commitVisibility(false /* visible */, false /* performLayout */,
                            true /* fromTransition */);
                    committedSomeInvisible = true;
                } else {
                    
                }
            }
            
            // ...
            
            continue;
        }
        
        // ...
    }
    
    
    // 2. wallpaper target 不可见,那么提交 WallpaperWindowToken 不可见
    // Commit wallpaper visibility after activity, because usually the wallpaper target token is
    // an activity, and wallpaper's visibility depends on activity's visibility.
    for (int i = mParticipants.size() - 1; i >= 0; --i) {
        final WallpaperWindowToken wt = mParticipants.valueAt(i).asWallpaperToken();
        if (wt == null) continue;
        
        final WindowState target = wt.mDisplayContent.mWallpaperController.getWallpaperTarget();
        final boolean isTargetInvisible = target == null || !target.mToken.isVisible();
        final boolean isWallpaperVisibleAtEnd =
                wt.isVisibleRequested() || mVisibleAtTransitionEndTokens.contains(wt);
        
        // wallpaper target 不可见,提交 WallpaperWindowToken 不可见
        if (isTargetInvisible || !isWallpaperVisibleAtEnd) {
            ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
                    "  Commit wallpaper becoming invisible: %s", wt);
            wt.commitVisibility(false /* visible */);
        }
        
        // 在 layout change 标记 FINISH_LAYOUT_REDO_WALLPAPER
        // 在窗口刷新时,会重新计算 wallpaper target
        if (isTargetInvisible) {
            // Our original target went invisible, so we should look for a new target.
            wt.mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
        }
    }
    
    // ...

    // 3. 通知监听者 transition finish
    // dispatch legacy callback in a different loop. This is because multiple legacy handlers
    // (fixed-rotation/displaycontent) make global changes, so we want to ensure that we've
    // processed all the participants first (in particular, we want to trigger pip-enter first)
    for (int i = 0; i < mParticipants.size(); ++i) {
    
        // 必须有 ActivityRecord 参与到 Transition
        final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
        if (ar == null) continue;

        // 此时,待启动的 ActivityRecord 是可见的
        if ((ar.isVisibleRequested() || !ar.isState(ActivityRecord.State.INITIALIZING))
                && !ar.isAnimating(PARENTS, ANIMATION_TYPE_PREDICT_BACK)) {
            mController.dispatchLegacyAppTransitionFinished(ar);
        }

        // ...
    }

    // ...

    // 4.调度 stop activity
    // Always schedule stop processing when transition finishes because activities don't
    // stop while they are in a transition thus their stop could still be pending.
    mController.mAtm.mTaskSupervisor
            .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();

    // ...

    // Transition 状态切换到 STATE_FINISHED
    mState = STATE_FINISHED;
    
    // ...
    
    // 此时没有 finishing transition
    mController.mFinishingTransition = null;
    
    // ...
}

finish transition

  1. Transition 的参与者中,如果有不可见的 ActivityRecord,提交其不可见。对于本案例来说,Launcher ActivityRecord 不可见,因此对它提交了不可见,即更新 ActivityRecord#mVisible 和 ActivityRecord#mClientVisible 为 false。
  2. Launcher ActivityRecord 现已被提交不可见,导致其下的 wallpaper target 窗口即将不可见。因此,得提交 WallpaperWindowToken 的不可见。
  3. 通知监听者 transition finish。在 play transition 前,DisplayContent update orientation 时,执行了 Fixed Rotation,导致推迟了 update orientation。现在 transition finish,DisplayContent 需继续执行 update orientation,更新系统 rotation。
  4. 调度 stop activity。根据 Android V app 冷启动 (1) Activity生命周期 分析,Launcher ActivityRecord 处于 transition 中,无法执行 stop activity。还需正在启动的 Activity 上报 idle ,才可以执行 Launcher Activity 的 stop activity 流程。

提交 WallpaperWindowToken 不可见

java 复制代码
// WallpaperWindowToken.java

// 参数 visible 为 false
/** Commits the visibility of this token. This will directly update the visibility. */
void commitVisibility(boolean visible) {
    if (visible == isVisible()) return;

    ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
            "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
            isVisible(), mVisibleRequested);

    // 更新 mVisibleRequested 为 false
    setVisibleRequested(visible);
    
    // 只更新 mClientVisible 为 false
    // 注意,WallpaperWindowToken 的 mClientVisible 就是 mVisible
    setVisible(visible);
}

// 参数 visible 为 false
private void setVisible(boolean visible) {
    final boolean wasClientVisible = isClientVisible();
    // 更新 mClientVisible 为 false
    setClientVisible(visible);
    
    if (visible && !wasClientVisible) {
        
    }
}

WallpaperWindowToken 的提交不可见,只需更新 mVisibleRequested 和 mClientVisible 为 false。WallpaperWindowToken 并没有 mVisible 属性,因为它是 ActivityRecord 专有的属性。

注意,mClientVisible 被更新为 false,会通知 app 端窗口不可见,app 端不会再绘制壁纸窗口。

DisplayContent 处理 transition finish

java 复制代码
// DisplayContent.java

class FixedRotationTransitionListener extends WindowManagerInternal.AppTransitionListener {

    public void onAppTransitionFinishedLocked(IBinder token) {
        // 这个是正在启动的 ActivityRecord
        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
          
        // ...
        
        continueUpdateOrientationForDiffOrienLaunchingApp();
    }
}


/**
 * Continue updating the orientation change of display if it was deferred by a top activity
 * launched in a different orientation.
 */
void continueUpdateOrientationForDiffOrienLaunchingApp() {
    // ...
    
    
    // 1.继续 update orientation
    // Update directly because the app which will change the orientation of display is ready.
    if (mDisplayRotation.updateOrientation(getOrientation(), false /* forceUpdate */)) {
        // If a transition is collecting, let the transition apply the rotation change on
        // display thread. See Transition#shouldApplyOnDisplayThread().
        if (!mTransitionController.isCollecting(this)) {
            // 2. 发送新配置
            sendNewConfiguration();
        }
        return;
    }
    
    // ...
}

play transition 前的 DisplayContent 的 update orientation,由于执行了 fixed rotation 而被推迟。

现在,transition finish,得继续执行 DisplayContent update orientation。此时,会导致系统 rotation 被更新,从而触发配置更新。

play transition 前的 apply fixed rotation,对正在启动的 ActivityRecord surface 设置了 transform,使横屏绘制的 Activity 窗口,可以"正常地"显示在竖屏的屏幕上。

现在,系统 rotation 的更新,会导致屏幕方向的更新,此时需要移除正在启动的 ActivityRecord surface 的 transform,使横屏绘制的窗口,真正地可以正常的显示在横屏的屏幕上。这就是配置更新中的 finish fixed rotation 所做的。

update orientation

java 复制代码
// DisplayRotation.java

// newOrientation 是计算出的 orientation
// forceUpdate 为 false
boolean updateOrientation(@ScreenOrientation int newOrientation, boolean forceUpdate) {
    if (newOrientation == mLastOrientation && !forceUpdate) {
        return false;
    }
    
    // 更新 orientation
    mLastOrientation = newOrientation;
    
    if (newOrientation != mCurrentAppOrientation) {
        mCurrentAppOrientation = newOrientation;
        if (isDefaultDisplay) {
            updateOrientationListenerLw();
        }
    }
    
    // 更新 rotation
    return updateRotationUnchecked(forceUpdate);
}



boolean updateRotationUnchecked(boolean forceUpdate) {
    // ...

    final int oldRotation = mRotation;
    final int lastOrientation = mLastOrientation;
    
    // 根据 orientation 计算 rotation
    int rotation = rotationForOrientation(lastOrientation, oldRotation);
    
    
    // ...

    // 保存 rotation
    mRotation = rotation;

    // 标记 DisplayContent 需要 layout
    mDisplayContent.setLayoutNeeded();
    
    // 标记 DisplayContent 正在等待配置更新
    mDisplayContent.mWaitingForConfig = true;

    if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
        // 此时已经没有处于 collecting 的 Transition
        final boolean wasCollecting = mDisplayContent.mTransitionController.isCollecting();
        
        if (!wasCollecting) {
            if (mDisplayContent.getLastHasContent()) {
                
                // 请求类型为 CHANGE 的 transition
                final TransitionRequestInfo.DisplayChange change =
                        new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
                                oldRotation, mRotation);
                mDisplayContent.requestChangeTransition(
                        ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
            }
        } else {
            // ...
        }
        return true;
    }

   
    // ...
}

update orientation 触发了 rotation 更新,导致请求了一个类型为 CHANGE transition,这是一个无缝的屏幕旋转动画。屏幕旋转动画,我在前面的文章中分析过,它会导致屏幕方向的更新。

我在前面的文章中,详细分析过屏幕旋转动画,而无缝的屏幕旋转动画,只是看不到动画而已。

更新配置

由于 update orientation 触发了 rotation 更新,因此需要更新系统配置。我在屏幕旋转动画的系列文章中,已经详细分析过配置更新,这里只展示与 Fixed Rotation 相关的配置更新

java 复制代码
// DisplayContent.java

public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
    final Configuration currOverrideConfig = getRequestedOverrideConfiguration();
    final int currRotation = currOverrideConfig.windowConfiguration.getRotation();
    final int overrideRotation = overrideConfiguration.windowConfiguration.getRotation();
    if (currRotation != ROTATION_UNDEFINED && overrideRotation != ROTATION_UNDEFINED
            && currRotation != overrideRotation) {
        // rotation 改变,执行 finish fixed rotation
        applyRotationAndFinishFixedRotation(currRotation, overrideRotation);
    }
    
    // ...
    
    // 通过基类方法,更新配置
    super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
    
    // ...
}



/**
 * If the launching rotated activity ({@link #mFixedRotationLaunchingApp}) is null, it simply
 * applies the rotation to display. Otherwise because the activity has shown as rotated, the
 * fixed rotation transform also needs to be cleared to make sure the rotated activity fits
 * the display naturally.
 */
private void applyRotationAndFinishFixedRotation(int oldRotation, int newRotation) {
    final WindowToken rotatedLaunchingApp = mFixedRotationLaunchingApp;
    if (rotatedLaunchingApp == null) {
        // ...
    }

    // finish fixed rotation,并且 apply rotation
    rotatedLaunchingApp.finishFixedRotationTransform(
            () -> applyRotation(oldRotation, newRotation));
            
    // 重置 mFixedRotationLaunchingApp 为 null
    setFixedRotationLaunchingAppUnchecked(null);
}
java 复制代码
// WindowToken.java

/**
 * Finishes the transform and apply display rotation if the action is given. If the display will
 * not rotate, the transformed containers are restored to their original states.
 */
void finishFixedRotationTransform(Runnable applyDisplayRotation) {
    final FixedRotationTransformState state = mFixedRotationTransformState;
    if (state == null) {
        return;
    }
    // 重置 FixedRotationTransformState
    state.resetTransform();
    
    // Clear the flag so if the display will be updated to the same orientation, the transform
    // won't take effect.
    state.mIsTransforming = false;
    
    // 执行回调 applyRotation()
    if (applyDisplayRotation != null) {
        applyDisplayRotation.run();
    }
    
    // 清理相关 WindowToken#mFixedRotationTransformState
    // The state is cleared at the end, because it is used to indicate that other windows can
    // use seamless rotation when applying rotation to display.
    for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
        final WindowToken token = state.mAssociatedTokens.get(i);
        token.mFixedRotationTransformState = null;
        if (applyDisplayRotation == null) {
            // ...
        }
    }
}

注意,finish fixed rotation 是在 DisplayContent 正式更新配置前执行的。而它只是清理了正在启动 ActivityRecord 的 FixedRotationTransformState。当 ActivityRecord 正式更新配置时,会 update surface position,正式清理 ActivityRecord 的 Fixed Rotation transform。

相关推荐
jzlhll1235 小时前
android MVC/MVP/MVVM/MVI架构发展历程和编写范式
android·架构
安卓开发者5 小时前
Android ConstraintLayout 使用详解
android
CV资深专家8 小时前
Android 基础入门学习目录(持续更新)
android
侧耳4298 小时前
android添加i2c-tools工具
android
我是好小孩12 小时前
Android-侧边导航栏的使用
android·gitee
吗喽对你问好12 小时前
安卓基础布局核心知识点整理
android·gitee
安卓开发者12 小时前
Android Material Components 全面解析:打造现代化 Material Design 应用
android
教程分享大师12 小时前
带root权限_中国移动创维DT541_S905L3融合机器改机顶盒刷机教程 当贝纯净版安卓9.0系统线刷包 刷机包
android
wuk99812 小时前
Android:UI:Drawable:View/ImageView与Drawable
android·ui
whysqwhw14 小时前
Kotlin 中作用域函数 let、with、run、also、apply 的核心使用指南
android