什么是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参数解析:
-
transit : 过渡动画类型,由WMS计算后回调给app,app应根据transit类型启动相应动效
-
3个RemoteAnimationTarget[]: apps、wallpapers、nonApps: RemoteAnimation封装了实际要执行动画的对象的Surface、bounds等信息,APP端可以对这些Surface应用动画的Transform
-
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执行流程 :
在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());
}