Android RemoteAnimation简述

6,435 阅读3分钟

什么是RemoteAnimation

RemoteAnimation是Android 9中为实现 同步应用过渡动画 而新增的API。 同步应用过渡动画(Synchronized App Transitions)是用以改进应用过渡动画架构的一项功能。 在Android系统中应用于Launcher和SystemUI的的应用过渡动画和切换动画中。

效果演示

实现原理

source.android.google.cn/devices/tec…

当用户打开、关闭应用或在应用之间切换时,SystemUI或Launcher进程会向系统发送逐帧控制动画的请求,同时保证在View动画和窗口动画之间进行同步。SystemUI 或启动器在动画过程中绘制新的帧时,会在动画应用Surface请求一个不同的Transform,这个Transform可以确定应用在屏幕上的组成形式,并标记要与SystemUI或Launcher目前正在绘制的帧同步的请求(SurfaceControl.Transaction)。

API及用法

    @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
    @UnsupportedAppUsage
    public static ActivityOptions makeRemoteAnimation(
            RemoteAnimationAdapter remoteAnimationAdapter) {
        final ActivityOptions opts = new ActivityOptions();
        opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
        opts.mAnimationType = ANIM_REMOTE_ANIMATION;
        return opts;
    }

这个方法需要调用者声明CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS权限

<permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"
    android:protectionLevel="signature|privileged" />

此权限只有系统签名的priv-app才可以使用

oneway interface IRemoteAnimationRunner {
    /**
     * Called when the process needs to start the remote animation.
     *
     * @param transition The old transition type. Must be one of WindowManager.TRANSIT_OLD_* values.
     * @param apps The list of apps to animate.
     * @param wallpapers The list of wallpapers to animate.
     * @param nonApps The list of non-app windows such as Bubbles to animate.
     * @param finishedCallback The callback to invoke when the animation is finished.
     */
    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
    void onAnimationStart(int transit, in RemoteAnimationTarget[] apps,
            in RemoteAnimationTarget[] wallpapers, in RemoteAnimationTarget[] nonApps,
            in IRemoteAnimationFinishedCallback finishedCallback);
    /**
     * Called when the animation was cancelled. From this point on, any updates onto the leashes
     * won't have any effect anymore.
     */
    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
    void onAnimationCancelled();
}

构建RemoteAnimationAdapter时需要实现Binder接口IRemoteAnimationRunner,系统会在动画开始前回调onAnimationStart方法、动画取消时回调onAnimationCancelled方法通知到APP端。

onAnimationStart参数解析:

  1. transit : 过渡动画类型,由WMS计算后回调给app,app应根据transit类型启动相应动效

  2. 3个RemoteAnimationTarget[]: apps、wallpapers、nonApps: RemoteAnimation封装了实际要执行动画的对象的Surface、bounds等信息,APP端可以对这些Surface应用动画的Transform

  3. IRemoteAnimationFinishedCallback finishedCallback

有关通知启动动画的参考实现,请参阅 ActivityLaunchAnimator.kt

系统源码分析

以Launcher中点击图标启动Activity的过渡动画为例,分析源码流程

ActivityOptions中设置的RemoteAnimationAdapter会在ActivityRecord构建时赋值给mPendingRemoteAnimation

ActivityRecord#setOptions

    private void setOptions(@NonNull ActivityOptions options) {
        mLaunchedFromBubble = options.getLaunchedFromBubble();
        mPendingOptions = options;
        if (options.getAnimationType() == ANIM_REMOTE_ANIMATION) {
            mPendingRemoteAnimation = options.getRemoteAnimationAdapter();
        }
        mPendingRemoteTransition = options.getRemoteTransition();
    }

在resumeTopActivity时会通过ActivityRecord#applyOptionsAnimation → AppTransition#overridePendingAppTransitionRemote方法设置AppTransition的mNextAppTransitionType和mRemoteAnimationController , remoteAnimationAdapter被封装进了mRemoteAnimationController。

AppTransition#overridePendingAppTransitionRemote

    void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
        ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
                        isTransitionSet(), remoteAnimationAdapter);
        if (isTransitionSet()) {
            clear();
            mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
            mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
                    remoteAnimationAdapter, mHandler);
        }
    }

调用堆栈:

    at com.android.server.wm.AppTransition.overridePendingAppTransitionRemote(AppTransition.java:1588)
    at com.android.server.wm.ActivityRecord.applyOptionsAnimation(ActivityRecord.java:4874)
    at com.android.server.wm.TaskFragment.resumeTopActivity(TaskFragment.java:1304)
    at com.android.server.wm.Task.resumeTopActivityInnerLocked(Task.java:5772)
    at com.android.server.wm.Task.resumeTopActivityUncheckedLocked(Task.java:5698)
    at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2509)
    at com.android.server.wm.RootWindowContainer.resumeFocusedTasksTopActivities(RootWindowContainer.java:2495)
    at com.android.server.wm.TaskFragment.completePause(TaskFragment.java:1758)
    at com.android.server.wm.ActivityRecord.activityPaused(ActivityRecord.java:6293)
    at com.android.server.wm.ActivityClientController.activityPaused(ActivityClientController.java:180)

transition执行流程 :

d3c826abb92b9a774e01d4ef29b058cc_iDIh-ec7CA.webp 在transition的 ready阶段将会对mRemoteAnimationController进行处理, IRemoteAnimationRunner的onAnimationStart方法将在这个阶段进行回调。

AppTransitionController#handleAppTransitionReady

    void handleAppTransitionReady() {
      ...
            mService.mSurfaceAnimationRunner.deferStartingAnimations();
            try {
            applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,
                    animLp, voiceInteraction);
            ...
            layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
            ...
        } finally {
            mService.mSurfaceAnimationRunner.continueStartingAnimations();
        }
      ...
    }
  • 在applyAnimations流程中,会去准备RemoteAnimationController的RemoteAnimationRecord 列表,即mPendingAnimations
  • 在goodToGo流程中,将为RemoteAnimation创建RemoteAnimationTarget applyAnimations会调用getAnimationAdapter获取AnimationAdapter, createRemoteAnimationRecord方法中构建的RemoteAnimationRecord中封装了mAdapter和mThumbnailAdapter。然后将RemoteAnimationRecord添加到mPendingAnimations

WindowContainer#getAnimationAdapter

    Pair<AnimationAdapter, AnimationAdapter> getAnimationAdapter(WindowManager.LayoutParams lp,
            @TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {
        final Pair<AnimationAdapter, AnimationAdapter> resultAdapters;
        final int appRootTaskClipMode = getDisplayContent().mAppTransition.getAppRootTaskClipMode();

        // Separate position and size for use in animators.
        final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);
        mTmpRect.set(screenBounds);
        getAnimationPosition(mTmpPoint);
        mTmpRect.offsetTo(0, 0);

        final RemoteAnimationController controller =
                getDisplayContent().mAppTransition.getRemoteAnimationController();
        final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
                && isChangingAppTransition();

        // Delaying animation start isn't compatible with remote animations at all.
        if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
            final Rect localBounds = new Rect(mTmpRect);
            localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
            final RemoteAnimationController.RemoteAnimationRecord adapters =
                    controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
                            screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
            resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);
        } else if 
            ...
  }
  // services/core/java/com/android/server/wm/RemoteAnimationController.java
  RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
            Point position, Rect localBounds, Rect stackBounds, Rect startBounds) {
        ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
                windowContainer);
        final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
                localBounds, stackBounds, startBounds);
        mPendingAnimations.add(adapters);
        return adapters;
  }

RemoteAnimationTarget

core\java\android\view\RemoteAnimationTarget.java

RemoteAnimationTareget描述要作为远程动画的一部分进行动画的Activity

  • taskId:app所属的task

  • mode : app是正在打开还是关闭(openning or closing),另外还存在一个changing状态

  • leash :用来实际控制transoform的SurfaceControl

  • startLeash: 如果这个转换是MODE_CHANGING,则为目标的启动状态的surfacecontrol,否则为null。

  • position:app在屏幕空间坐标的源位置

  • localBounds:目标相对于parent的bounds。 当应用程序在它的parent上做动画时,要使用localBounds而不是使用屏幕中的位置坐标。

  • startBounds:source container在屏幕空间坐标中的起始边界。如果动画目标不是MODE_CHANGING,则为空。

RemoteAnimationController#goodToGo

    void goodToGo(@WindowManager.TransitionOldType int transit) {
        ...
        // Create the app targets
        final RemoteAnimationTarget[] appTargets = createAppAnimations();
        ...
        // Create the remote wallpaper animation targets (if any)
        final RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();

        // Create the remote non app animation targets (if any)
        final RemoteAnimationTarget[] nonAppTargets = createNonAppWindowAnimations(transit);

        mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
            try {
                linkToDeathOfRunner();
                ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo(): onAnimationStart,"
                                + " transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
                        AppTransition.appTransitionOldToString(transit), appTargets.length,
                        wallpaperTargets.length, nonAppTargets.length);
                mRemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,
                        wallpaperTargets, nonAppTargets, mFinishedCallback);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to start remote animation", e);
                onAnimationFinished();
            }
        });
        setRunningRemoteAnimation(true);
    }

createAppAnimations方法中将遍历所有的mPendingAnimations,创建RemoteAnimationTarget

ActivityRecord#createRemoteAnimationTarget

    @Override
    RemoteAnimationTarget createRemoteAnimationTarget(
            RemoteAnimationController.RemoteAnimationRecord record) {
        final WindowState mainWindow = findMainWindow();
        if (task == null || mainWindow == null) {
            return null;
        }
        final Rect insets = mainWindow.getInsetsStateWithVisibilityOverride().calculateInsets(
                task.getBounds(), Type.systemBars(), false /* ignoreVisibility */);
        InsetUtils.addInsets(insets, getLetterboxInsets());

        return new RemoteAnimationTarget(task.mTaskId, record.getMode(),
                record.mAdapter.mCapturedLeash, !fillsParent(),
                new Rect(), insets,
                getPrefixOrderIndex(), record.mAdapter.mPosition, record.mAdapter.mLocalBounds,
                record.mAdapter.mRootTaskBounds, task.getWindowConfiguration(),
                false /*isNotInRecents*/,
                record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
                record.mStartBounds, task.getTaskInfo());
    }