Android T 远程动画显示流程其二——动画的添加流程(更新中)

前言

接着上篇文章分析
Android T 远程动画显示流程其一

切入点——处理应用的显示过渡

下面,我们以从桌面点击一个应用启动的场景来分析远程动画的流程,窗口添加的流程见Android T WMS窗口相关流程
这里我们从AppTransitionController.handleAppTransitionReady方法开始跟踪代码流程

代码路径:framework/services/core/java/com/android/server/wm/AppTransitionController.java

    /*** Handle application transition for given display.*/void handleAppTransitionReady() {......//通过getTransitCompatType方法获取transit的值@TransitionOldType final int transit = getTransitCompatType(mDisplayContent.mAppTransition, mDisplayContent.mOpeningApps,mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers,mWallpaperControllerLocked.getWallpaperTarget(), getOldWallpaper(),mDisplayContent.mSkipAppTransitionAnimation);......//方法收集正在打开 (mOpeningApps)、关闭 (mClosingApps) 和切换 (mChangingContainers) 的应用的activity类型//并将它们存储在 activityTypes 集合中。final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,mDisplayContent.mClosingApps, mDisplayContent.mChangingContainers);//被用于查找与给定transit和activityTypes相关的 ActivityRecord//也就是我们当前打开的应用的ActivityRecordfinal ActivityRecord animLpActivity = findAnimLayoutParamsToken(transit, activityTypes,mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,mDisplayContent.mChangingContainers);//获取正在打开的应用列表 (mOpeningApps) 中的顶层应用。//ignoreHidden 参数设置为 false,意味着即使应用是隐藏的,也会被考虑在内final ActivityRecord topOpeningApp =getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);//获取正在关闭的应用列表 (mClosingApps) 中的顶层应用final ActivityRecord topClosingApp =getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);//获取正在切换的应用列表 (mChangingContainers) 中的顶层应用//其取决于参数DisplayContent.mChangingContainers中是否有值/** 有三种情况会给DisplayContent.mChangingContainers中添加值1.{@link Task}在全屏和浮窗之间发生切换2.{@link TaskFragment}已组织好并且正在更改窗口边界3.{@link ActivityRecord}被重新分配到一个有组织的{@link TaskFragment}中**/final ActivityRecord topChangingApp =getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);//从之前找到的animLpActivity(正在打开的应用的ActivityRecord)的窗口中获取布局参数final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);......try {/*1.1应用app transition动画(远程动画)*/applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit,animLp, voiceInteraction);/*1.2.1处理closing activity可见性*/handleClosingApps();/*1.2.2处理opening actvity可见性*/handleOpeningApps();//处理用于处理正在切换的应用handleChangingApps(transit);//处理正在关闭或更改的容器handleClosingChangingContainers();//设置与最后一次应用过渡动画相关的信息appTransition.setLastAppTransition(transit, topOpeningApp,topClosingApp, topChangingApp);final int flags = appTransition.getTransitFlags();/*1.3播放远程动画*/layoutRedo = appTransition.goodToGo(transit, topOpeningApp);//处理非应用窗口的过渡动画handleNonAppWindowsInTransition(transit, flags);//执行动画回调appTransition.postAnimationCallback()} finally {mService.mSurfaceAnimationRunner.continueStartingAnimations();}......// This has changed the visibility of windows, so perform// a new layout to get them all up-to-date./*2.由于activity的可见性变更,将DisplayContent.mLayoutNeeded标志位置为true*/mDisplayContent.setLayoutNeeded();......}

这个方法主要处理这三件事:
1.处理activity的过渡动画(远程动画)
2.分别调用 handleClosingApps以及handleOpeningApps对要关闭的和要打开的Activity进行可见性更新。
3.调用AppTransition.goodToGo方法走播放远程动画流程。
4.由于activity的可见性变更,将DisplayContent.mLayoutNeeded设置为true,该标志位在DisplayContent.performLayoutNoTrace中用来判断是否对当前DisplayContent下的所有窗口进行刷新。
这里我们主要关注远程动画的流程,主要分为两个部分。

  1. 处理并创建远程动画流程
    applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);
  2. 播放显示远程动画流程
    layoutRedo = appTransition.goodToGo(transit, topOpeningApp);

动画创建流程

applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);

基于一组ActivityRecord来应用动画,这些ActivityRecord表示正在进行切换的应用。
mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps这两个参数分别代表正在打开和关闭的应用;
transit通过前面getTransitCompatType方法中获取,是TRANSIT_OLD_WALLPAPER_CLOSE(12);
animLp通过前面getAnimLp方法中获取,用于定义窗口的布局参数。这里就是代表正在打开的应用的ActivityRecord的窗口布局参数;
voiceInteraction:表示是否为语音交互。

处理并创建远程动画

代码路径:framework/services/core/java/com/android/server/wm/AppTransitionController.java

    /*** Apply an app transition animation based on a set of {@link ActivityRecord}** @param openingApps The list of opening apps to which an app transition animation applies.* @param closingApps The list of closing apps to which an app transition animation applies.* @param transit The current transition type.* @param animLp Layout parameters in which an app transition animation runs.* @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice*                         interaction session driving task.*/private void applyAnimations(ArraySet<ActivityRecord> openingApps,ArraySet<ActivityRecord> closingApps, @TransitionOldType int transit,LayoutParams animLp, boolean voiceInteraction) {//方法检查过渡类型是否未设置,或者打开和关闭的应用程序是否都为空。如果是,则方法直接返回,不执行任何动画。if (transit == WindowManager.TRANSIT_OLD_UNSET|| (openingApps.isEmpty() && closingApps.isEmpty())) {return;}//调用getAnimationTargets方法获取打开和关闭的应用的窗口容器(WindowContainer)final ArraySet<WindowContainer> openingWcs = getAnimationTargets(openingApps, closingApps, true /* visible */);final ArraySet<WindowContainer> closingWcs = getAnimationTargets(openingApps, closingApps, false /* visible */);//打开和关闭的窗口应用动画。这是通过调重载的applyAnimations方法完成的,传递相应的参数,如动画的目标、过渡类型等。applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,voiceInteraction);applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,voiceInteraction);//如果存在最近任务动画控制器(RecentsAnimationController),则发送任务出现任务final RecentsAnimationController rac = mService.getRecentsAnimationController();if (rac != null) {rac.sendTasksAppeared();}//遍历打开和关闭的应用,并设置mOverrideTaskTransition为falsefor (int i = 0; i < openingApps.size(); ++i) {openingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;}for (int i = 0; i < closingApps.size(); ++i) {closingApps.valueAtUnchecked(i).mOverrideTaskTransition = false;}//如果存在辅助功能控制器(AccessibilityController)且有回调,则调用其onAppWindowTransition方法。final AccessibilityController accessibilityController =mDisplayContent.mWmService.mAccessibilityController;if (accessibilityController.hasCallbacks()) {accessibilityController.onAppWindowTransition(mDisplayContent.getDisplayId(), transit);}}

传递关键参数,处理应用程序窗口的打开和关闭动画。
通过getAnimationTargets方法获取当前打开和关闭的应用的容器,即ActivityRecord的容器。
最关键的方法是调用的applyAnimations方法:

        applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,voiceInteraction);applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,voiceInteraction);

我们这里openingWcsclosingWcs实际上表示的是应用的容器,即Task;openingAppsclosingApps就是前面传递的mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,分别代表正在打开和关闭的应用,也是挂在对应Task下面的ActivityRecord。并且传递了应用的可见性visible,true可见,false不可见。
因此在我们桌面点击打开应用的流程中,openingWcs实际上指的是应用的Task,openingApps是应用的ActivityRecord(其实就是应用的主界面),其可见性为true;closingWcs对应的是桌面的Task,closingApps是桌面的ActivityRecord,其可见性为false。

这也对应了我们前面创建动画图层的堆栈中所打印的,先创建了应用的动画图层,后创建桌面的动画图层。

注:
从这里开始后续流程执行了两次,第一次是打开的应用流程,第二次是关闭的应用流程(一个应用的启动,伴随这另一个应用的退出,浮窗等特殊场景除外)。
从桌面点击开启应用的场景来说,一次是启动的应用角度执行流程,另一次是桌面角度执行流程。
从代码逻辑上来说,唯一的不同点是传递的可见性的值不同。

这个方法调用的是重载的applyAnimations方法

获取需要做动画的容器

	  /*** Apply animation to the set of window containers.** @param wcs The list of {@link WindowContainer}s to which an app transition animation applies.* @param apps The list of {@link ActivityRecord}s being transitioning.* @param transit The current transition type.* @param visible {@code true} if the apps becomes visible, {@code false} if the apps becomes*                invisible.* @param animLp Layout parameters in which an app transition animation runs.* @param voiceInteraction {@code true} if one of the apps in this transition belongs to a voice*                         interaction session driving task.*/private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,@TransitionOldType 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.//对于每一个应用的窗口容器,检查正在进行切换的应用(apps)中哪些是该窗口容器的后代。//就比如应用的ActivityRecord是是应用的Task的后代final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();for (int j = 0; j < apps.size(); ++j) {final ActivityRecord app = apps.valueAt(j);//app如果是wc的后代,将其添加到一个列表中。if (app.isDescendantOf(wc)) {transitioningDescendants.add(app);}}//调用每个应用的窗口容器的applyAnimation方法,传入相应的参数//这些参数包含动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表。wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);}}

入参含义:
wcs: 一个WindowContainer对象的集合,这些对象是需要应用动画的窗口容器。
apps: 一个ActivityRecord对象的集合,这些对象表示正在进行切换的应用程序。
transit: 当前的过渡类型,例如淡入淡出、滑动等。
visible: 一个布尔值,表示应用是否变为可见。
animLp: 布局参数,定义了动画运行时的布局。
voiceInteraction: 一个布尔值,表示是否有语音交互。

关键代码解读:

  • final WindowContainer wc = wcs.valueAt(i);获取窗口容器
    wcs是前面传递过来的是Task,wc就是依次获取当前应用的Task和桌面Task。

  • transitioningDescendants存储的就是需要做动画的ActivityRecord。

  • 传递动画参数
    通过wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);方法,传递参数动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表。
    wc就是Task,其没有applyAnimation方法,向上找父类WindowContainer.applyAnimation方法调用。

判断是否应用动画,传递相关参数

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

/*** Applies the app transition animation according the given the layout properties in the* window hierarchy.** @param lp The layout parameters of the window.* @param transit The app transition type indicates what kind of transition to be applied.* @param enter Whether the app transition is entering transition or not.* @param isVoiceInteraction Whether the container is participating in voice interaction or not.* @param sources {@link ActivityRecord}s which causes this app transition animation.** @return {@code true} when the container applied the app transition, {@code false} if the*         app transition is disabled or skipped.** @see #getAnimationAdapter*/boolean applyAnimation(WindowManager.LayoutParams lp, @TransitionOldType int transit,boolean enter, boolean isVoiceInteraction,@Nullable ArrayList<WindowContainer> sources) {//判断是否禁用过渡动画if (mWmService.mDisableTransitionAnimation) {ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,"applyAnimation: transition animation is disabled or skipped. "+ "container=%s", this);//取消当前动画cancelAnimation();return false;}// Only apply an animation if the display isn't frozen. If it is frozen, there is no reason// to animate and it can cause strange artifacts when we unfreeze the display if some// different animation is running.try {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");//会判断是否有冻结,屏幕是否开启if (okToAnimate()) {ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,"applyAnimation: transit=%s, enter=%b, wc=%s",AppTransition.appTransitionOldToString(transit), enter, this);//传递相关参数,创建AnimationAdapter和AnimationRunnerBuilder,准备启动动画applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);} else {//取消当前动画cancelAnimation();}} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}//检查指定的窗口容器是否正在进行动画return isAnimating();}

下面说说里面的几个关键点:

  • 判断是否禁用过渡动画
    mWmService.mDisableTransitionAnimation
    这个变量是在WindowManagerService的构造方法中初始化的

    mDisableTransitionAnimation = context.getResources().getBoolean(com.android.internal.R.bool.config_disableTransitionAnimation);
    

    可以发现是读取config_disableTransitionAnimation配置项
    代码路径:frameworks/base/core/res/res/values/symbols.xml

     <java-symbol type="bool" name="config_disableTransitionAnimation" />
    

    定义了这个symbol
    代码路径:frameworks/base/core/res/res/values/config.xml

        <!-- Flag to disable all transition animations --><bool name="config_disableTransitionAnimation">false</bool>
    

    定义了默认值为false,不禁用过渡动画

  • 取消当前动画
    cancelAnimation();

        void cancelAnimation() {//处理动画结束时的一些后续操作doAnimationFinished(mSurfaceAnimator.getAnimationType(), mSurfaceAnimator.getAnimation());//调用SurfaceAnimator.cancelAnimation方法来取消当前正在进行的动画mSurfaceAnimator.cancelAnimation();//调用unfreeze方法解除对显示的冻结状态,允许显示继续正常更新和渲染mSurfaceFreezer.unfreeze(getSyncTransaction());}
    

    doAnimationFinished方法在动画播放结束时处理回调逻辑中会调用到,具体见后面【动画移除流程】。

  • 准备动画
    applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
    把前面传递的参数动画的布局、过渡类型、是否可见、是否有语音交互以及需要做动画的ActivityRecord应用的列表,再次传递到applyAnimationUnchecked方法中。
    注意,这里调用的是Task中重写的applyAnimationUnchecked方法,而不是直接调用的WindowContainer中的applyAnimationUnchecked方法。
    因为我们前面是通过前面AppTransitionController.applyAnimations中wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);调用过来的,因此此时的this指针指的是变量wc,即应用对应的Task。
    后面细讲applyAnimationUnchecked方法。

  • 检查动画
    代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

        final boolean isAnimating(int flags, int typesToCheck) {return getAnimatingContainer(flags, typesToCheck) != null;}
    

    flags 用于确定要检查的动画类型和范围。
    typesToCheck 用于确定哪些类型的动画需要检查。
    方法内部调用 getAnimatingContainer 方法来获取正在进行动画的窗口容器,并根据返回值判断是否存在符合条件和目标标志的动画。
    如果返回值为 true,则说明存在符合条件的动画;如果返回值为 false,则说明不存在符合条件的动画。

处理最近任务状态的动画

applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);
其中参数enter代表的其实就应用的可见性,从前面AppTransitionController.applyAnimations方法中逐步传递过来值有两个

applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp, voiceInteraction);
applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp, voiceInteraction);

启动的应用的可见性为true,桌面的可见性为false
代码路径:frameworks/base/services/core/java/com/android/server/wm/Task.java

@Overrideprotected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,@TransitionOldType int transit, boolean isVoiceInteraction,@Nullable ArrayList<WindowContainer> sources) {//获取RecentsAnimationController//只有在最近任务中,切换到另一个应用时才会创建final RecentsAnimationController control = mWmService.getRecentsAnimationController();//RecentsAnimationController不为空if (control != null) {// We let the transition to be controlled by RecentsAnimation, and callback task's// RemoteAnimationTarget for remote runner to animate.//应用可见性为true,且当前activity不是桌面或者最近任务if (enter && !isActivityTypeHomeOrRecents()) {ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,"applyAnimationUnchecked, control: %s, task: %s, transit: %s",control, asTask(), AppTransition.appTransitionOldToString(transit));//执行最近任务动画逻辑control.addTaskToTargets(this, (type, anim) -> {for (int i = 0; i < sources.size(); ++i) {sources.get(i).onAnimationFinished(type, anim);}});}//判断是否有返回手势} else if (mBackGestureStarted) {// Cancel playing transitions if a back navigation animation is in progress.// This bit is set by {@link BackNavigationController} when a back gesture is started.// It is used as a one-off transition overwrite that is cleared when the back gesture// is committed and triggers a transition, or when the gesture is cancelled.//返回手势mBackGestureStarted标志位置为falsemBackGestureStarted = false;//设置一个标志为true,表示应跳过应用的过渡动画。mDisplayContent.mSkipAppTransitionAnimation = true;ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Skipping app transition animation. task=%s", this);} else {//调用父类WindowContainer的applyAnimationUnchecked方法super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);}}

只有在最近任务中,切换到另一个应用时才会创建RecentsAnimationController,因此control的值为空。如果不为空,应用可见性为true,且当前activity不是桌面或者最近任务,则会进入到最近任务的动画处理逻辑。
我们在操作过程中也没有返回手势,因此mBackGestureStarted为false。
所以调用了父类WindowContainer的applyAnimationUnchecked方法。

获取AnimationAdapter并创建动画图层

接着前面super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, sources);进行分析
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,@TransitionOldType int transit, boolean isVoiceInteraction,@Nullable ArrayList<WindowContainer> sources) {//获取当前Taskfinal Task task = asTask();//当前Task不为空,应用变为不可见状态,且应用Task不为桌面或者最近任务//可以理解为应用按home键回到桌面的场景if (task != null && !enter && !task.isActivityTypeHomeOrRecents()) {//对输入法相关insets做处理final InsetsControlTarget imeTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING);final boolean isImeLayeringTarget = imeTarget != null && imeTarget.getWindow() != null&& imeTarget.getWindow().getTask() == task;// Attach and show the IME screenshot when the task is the IME target and performing// task closing transition to the next task.if (isImeLayeringTarget && AppTransition.isTaskCloseTransitOld(transit)) {mDisplayContent.showImeScreenshot();}}//创建AnimationAdapterfinal Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,transit, enter, isVoiceInteraction);//adapters.first指的是创建startBounds为空情况RemoteAnimationAdapterWrapper的对象AnimationAdapter adapter = adapters.first;//adapters.second指的是创建startBounds不为空情况RemoteAnimationAdapterWrapper的对象AnimationAdapter thumbnailAdapter = adapters.second;if (adapter != null) {if (sources != null) {//把需要做的动画的ActivityRecord添加到mSurfaceAnimationSourcesmSurfaceAnimationSources.addAll(sources);}//创建AnimationRunnerBuilderAnimationRunnerBuilder animationRunnerBuilder = new AnimationRunnerBuilder();//isTaskTransitOld方法中根据transit的值判断返回值,//从桌面启动应用时transit为12,表示TRANSIT_OLD_WALLPAPER_CLOSEif (isTaskTransitOld(transit)) {//设置过渡动画背景色animationRunnerBuilder.setTaskBackgroundColor(getTaskAnimationBackgroundColor());// TODO: Remove when we migrate to shell (b/202383002)//mTaskTransitionSpec不为nullif (mWmService.mTaskTransitionSpec != null) {//隐藏Insets溢出的部分animationRunnerBuilder.hideInsetSourceViewOverflows(mWmService.mTaskTransitionSpec.animationBoundInsets);}}// Check if the animation requests to show background color for Activity and embedded// TaskFragment.//获取当前Activity,但我们当前只有Task,因此为空final ActivityRecord activityRecord = asActivityRecord();//TaskFragment为Task父类,获取到了当前Taskfinal TaskFragment taskFragment = asTaskFragment();//设置过渡动动画背景色逻辑if (adapter.getShowBackground()// Check if it is Activity transition.&& ((activityRecord != null && isActivityTransitOld(transit))// Check if it is embedded TaskFragment transition.|| (taskFragment != null && taskFragment.isEmbedded()&& isTaskFragmentTransitOld(transit)))) {final @ColorInt int backgroundColorForTransition;if (adapter.getBackgroundColor() != 0) {// If available use the background color provided through getBackgroundColor// which if set originates from a call to overridePendingAppTransition.backgroundColorForTransition = adapter.getBackgroundColor();} else {......}animationRunnerBuilder.setTaskBackgroundColor(backgroundColorForTransition);}//进入创建动画图层逻辑animationRunnerBuilder.build().startAnimation(getPendingTransaction(), adapter, !isVisible(),ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);//判断是否在动画中显示壁纸if (adapter.getShowWallpaper()) {//更新pendingLayoutChanges添加标志FINISH_LAYOUT_REDO_WALLPAPER,表示需要重新处理壁纸布局。getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;}}}

这个方法主要就是获取AnimationAdapter,创建对应的动画图层,我们下面主要讨论这两个点。

创建RemoteAnimationAdapterWrapper实现AnimationAdapter接口

final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,transit, enter, isVoiceInteraction);

调用的是WindowContainer中的getAnimationAdapter方法,传递了参数动画的布局、过渡类型、是否可见、是否有语音交互。

/*** Gets the {@link AnimationAdapter} according the given window layout properties in the window* hierarchy.** @return The return value will always contain two elements, one for normal animations and the* other for thumbnail animation, both can be {@code null}.* @See com.android.server.wm.RemoteAnimationController.RemoteAnimationRecord* @See LocalAnimationAdapter*/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.//获取屏幕大小,参数appRootTaskClipMode在这个方法中无实际意义final Rect screenBounds = getAnimationBounds(appRootTaskClipMode);//mTmpRect大小赋值为屏幕大小mTmpRect.set(screenBounds);//transit值为12,TRANSIT_OLD_WALLPAPER_CLOSE,不进入该流程if (this.asTask() != null && isTaskTransitOld(transit)) {this.asTask().adjustAnimationBoundsForTransition(mTmpRect);}//设置动画位置为左上角顶点(0, 0)getAnimationPosition(mTmpPoint);mTmpRect.offsetTo(0, 0);final AppTransition appTransition = getDisplayContent().mAppTransition;//创建RemoteAnimationControllerfinal RemoteAnimationController controller = appTransition.getRemoteAnimationController();//AppTransition.isChangeTransitOld(transit),transit值为12,返回false//enter 应用可见性,启动应用的可见性为true,桌面可见性为false//isChangingAppTransition(),判断当前应用是否切换过渡,//其取决于参数DisplayContent.mChangingContainers中是否有值/** 有三种情况会给DisplayContent.mChangingContainers中添加值1.{@link Task}在全屏和浮窗之间发生切换2.{@link TaskFragment}已组织好并且正在更改窗口边界3.{@link ActivityRecord}被重新分配到一个有组织的{@link TaskFragment}中**///这里我们isChangingAppTransition()值为false,即isChanging值为falsefinal boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter&& isChangingAppTransition();// Delaying animation start isn't compatible with remote animations at all.//mSurfaceAnimator.isAnimationStartDelayed()判断动画是否延迟开启,//false表示不延迟,true表示延迟,我们这里值为falseif (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {// Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.boolean showBackdrop = false;// Optionally set backdrop color if App explicitly provides it through// {@link Activity#overridePendingTransition(int, int, int)}.@ColorInt int backdropColor = 0;//isFromActivityEmbedding(),返回的是RemoteAnimationController中的变量mIsActivityEmbedding//我们这里前面通过getRemoteAnimationController()创建RemoteAnimationController会对其赋值为false//因此下面流程不进行if (controller.isFromActivityEmbedding()) {......}//创建一个和mTmpRect相同大小和位置的矩阵final Rect localBounds = new Rect(mTmpRect);localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);final RemoteAnimationController.RemoteAnimationRecord adapters;//前面isChanging的值为false,因此!isChanging为true//enter 应用可见性,启动应用的可见性为true,桌面可见性为false//这里主要判断的是isClosingWhenResizing()方法的值,//这个值同样取决于参数DisplayContent.mChangingContainers中是否有值//这里我们isClosingWhenResizing()值为falseif (!isChanging && !enter && isClosingWhenResizing()) {// Container that is closing while resizing. Pass in the closing start bounds, so// the animation can start with the correct bounds, there won't be a snapshot.// Cleanup the mClosingChangingContainers so that when the animation is finished, it// will reset the surface.//调整大小时正在关闭的容器。传入关闭的开始边界,这样动画就可以从正确的边界开始,不会有快照。//清理mClosingChangingContainers,以便在动画完成时重置surface。final Rect closingStartBounds = getDisplayContent().mClosingChangingContainers.remove(this);adapters = controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds, screenBounds, closingStartBounds,showBackdrop, false /* shouldCreateSnapshot */);} else {//isChanging为false,所以startBounds为nullfinal Rect startBounds = isChanging ? mSurfaceFreezer.mFreezeBounds : null;//创建RemoteAnimationRecord,之后创建RemoteAnimationAdapterWrapper//RemoteAnimationAdapterWrapper实现了AnimationAdapter接口adapters = controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);}if (backdropColor != 0) {adapters.setBackDropColor(backdropColor);}if (!isChanging) {//根据enter的值设置mMode的值adapters.setMode(enter? RemoteAnimationTarget.MODE_OPENING: RemoteAnimationTarget.MODE_CLOSING);}//RemoteAnimationAdapterWrapper会根据startBounds变量是否为空进行不同的创建方式//startBounds变量为空的情况,保存到Pair类的first变量中//startBounds变量不为空的情况,保存到Pair类的second变量中resultAdapters = new Pair<>(adapters.mAdapter, adapters.mThumbnailAdapter);} else if (isChanging) {//创建的是LocalAnimationAdapter,非远程动画流程......} else {//创建的是LocalAnimationAdapter,非远程动画流程......}return resultAdapters;}

通过创建RemoteAnimationRecord来创建RemoteAnimationAdapterWrapper

adapters = controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds, screenBounds, startBounds, showBackdrop);

根据前面设置的参数我们可以知道传递的值分别为:
this指的是应用Task,以及桌面Task;
startBounds为 null ;mTmpPoint为(0,0) ;localBounds为屏幕分辨率的矩形; showBackdrop为false。

代码路径:frameworks/base/services/core/java/com/android/server/wm/RemoteAnimationController.java

    /*** Creates an animation record for each individual {@link WindowContainer}.** @param windowContainer The windows to animate.* @param position        The position app bounds relative to its parent.* @param localBounds     The bounds of the app relative to its parent.* @param endBounds       The end bounds after the transition, in screen coordinates.* @param startBounds     The start bounds before the transition, in screen coordinates.* @param showBackdrop    To show background behind a window during animation.* @return The record representing animation(s) to run on the app.*/RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,Point position, Rect localBounds, Rect endBounds, Rect startBounds,boolean showBackdrop) {return createRemoteAnimationRecord(windowContainer, position, localBounds, endBounds,startBounds, showBackdrop, startBounds != null /* shouldCreateSnapshot */);}/*** Creates an animation record for each individual {@link WindowContainer}.** @param windowContainer The windows to animate.* @param position        The position app bounds relative to its parent.* @param localBounds     The bounds of the app relative to its parent.* @param endBounds       The end bounds after the transition, in screen coordinates.* @param startBounds     The start bounds before the transition, in screen coordinates.* @param showBackdrop    To show background behind a window during animation.* @param shouldCreateSnapshot   Whether this target should create a snapshot animation.* @return The record representing animation(s) to run on the app.*/RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,Point position, Rect localBounds, Rect endBounds, Rect startBounds,boolean showBackdrop, boolean shouldCreateSnapshot) {ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",windowContainer);final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,localBounds, endBounds, startBounds, showBackdrop, shouldCreateSnapshot);mPendingAnimations.add(adapters);return adapters;}

传递参数,创建RemoteAnimationRecord,其中shouldCreateSnapshot参数的值取决于startBounds是否为空,我们这里startBounds为空,所以shouldCreateSnapshot为false。

/*** Contains information about a remote-animation for one WindowContainer. This keeps track of,* potentially, multiple animating surfaces (AdapterWrappers) associated with one* Window/Transition. For example, a change transition has an adapter controller for the* main window and an adapter controlling the start-state snapshot.* <p>* This can be thought of as a bridge between the information that the remote animator sees (via* {@link RemoteAnimationTarget}) and what the server sees (the* {@link RemoteAnimationAdapterWrapper}(s) interfacing with the moving surfaces).*/public class RemoteAnimationRecord {......RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,Rect endBounds, @Nullable Rect startBounds, boolean showBackdrop,boolean shouldCreateSnapshot) {mWindowContainer = windowContainer;mShowBackdrop = showBackdrop;if (startBounds != null) {......} else {mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,new Rect(), mShowBackdrop);mStartBounds = null;}}......}

RemoteAnimationAdapterWrapper会根据startBounds变量是否为空进行不同的创建方式,我们这里主要关注其为空的流程。
startBounds为空,走else流程

 mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,new Rect(), mShowBackdrop);

这里通过new Rect()空矩阵(上下左右四点均为0),创建了一个startBounds

    class RemoteAnimationAdapterWrapper implements AnimationAdapter {......RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,Rect localBounds, Rect endBounds, Rect startBounds, boolean showBackdrop) {mRecord = record;mPosition.set(position.x, position.y);mLocalBounds = localBounds;mEndBounds.set(endBounds);mStartBounds.set(startBounds);mShowBackdrop = showBackdrop;}......

把传递的参数赋值给了RemoteAnimationAdapterWrapper的成员变量。
这里我们的RemoteAnimationAdapterWrapper实现了AnimationAdapter接口。

通过startAnimation方法创建动画图层

            animationRunnerBuilder.build().startAnimation(getPendingTransaction(), adapter, !isVisible(),ANIMATION_TYPE_APP_TRANSITION, thumbnailAdapter);

getPendingTransaction():指的是一个事务。
adapter:是前面赋值AnimationAdapter adapter = adapters.first;,即创建的startBounds变量为空情况的RemoteAnimationAdapterWrapper对象。
!isVisible():获取当前可见性后取反。应用当前可见性为false,即传递true;桌面当前可见性为true,即传递false。
ANIMATION_TYPE_APP_TRANSITION:过渡动画类型。
thumbnailAdapter:是前面赋值AnimationAdapter thumbnailAdapter = adapters.second;,即创建的startBounds变量不为空情况的RemoteAnimationAdapterWrapper对象。

animationRunnerBuilder是AnimationRunnerBuilder对象,调用了其build方法中的startAnimation方法。我们这里调用的startAnimation方法是一个函数式接口。

    private interface IAnimationStarter {void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,@AnimationType int type, @Nullable AnimationAdapter snapshotAnim);}

startAnimation方法的实现在AnimationRunnerBuilder中的build方法中return的 (Transaction t, AnimationAdapter adapter, boolean hidden, AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {......}

    private class AnimationRunnerBuilder {/*** Runs when the surface stops animating*/private final List<Runnable> mOnAnimationFinished = new LinkedList<>();/*** Runs when the animation is cancelled but the surface is still animating*/private final List<Runnable> mOnAnimationCancelled = new LinkedList<>();......private IAnimationStarter build() {return (Transaction t, AnimationAdapter adapter, boolean hidden,@AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {startAnimation(getPendingTransaction(), adapter, !isVisible(), type,(animType, anim) -> mOnAnimationFinished.forEach(Runnable::run),() -> mOnAnimationCancelled.forEach(Runnable::run), snapshotAnim);};}}

除了传递之前的参数之外,在return的(Transaction t, AnimationAdapter adapter, boolean hidden, AnimationType int type, @Nullable AnimationAdapter snapshotAnim) -> {......}中调用了startAnimation方法,传递了回调函数(animType, anim) -> mOnAnimationFinished.forEach(Runnable::run)() -> mOnAnimationCancelled.forEach(Runnable::run),会执行mOnAnimationFinished和mOnAnimationCancelled这两个变量中的Runnable。

继续查看startAnimation方法

    /*** Starts an animation on the container.** @param anim                       The animation to run.* @param hidden                     Whether our container is currently hidden. TODO This should use isVisible at*                                   some point but the meaning is too weird to work for all containers.* @param type                       The type of animation defined as {@link AnimationType}.* @param animationFinishedCallback  The callback being triggered when the animation finishes.* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a*                                   cancel call to the underlying AnimationAdapter.* @param snapshotAnim               The animation to run for the snapshot. {@code null} if there is no*                                   snapshot.*/void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,@AnimationType int type,@Nullable OnAnimationFinishedCallback animationFinishedCallback,@Nullable Runnable animationCancelledCallback,@Nullable AnimationAdapter snapshotAnim) {ProtoLog.v(WM_DEBUG_ANIM, "Starting animation on %s: type=%d, anim=%s",this, type, anim);// TODO: This should use isVisible() but because isVisible has a really weird meaning at// the moment this doesn't work for all animatable window containers.mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,animationCancelledCallback, snapshotAnim, mSurfaceFreezer);}
  • 入参含义
    t:这是一个对象,用于描述一系列的窗口操作,例如移动、调整大小、绘制等。这些操作在WMS中排队,并在适当的时机应用到窗口上。
    anim:这是对动画进行封装的类。它包含了一些关于如何开始、更新和结束动画的信息。传递的也就是前面WindowContainer.getAnimationAdapter中创建startBounds变量为空情况的RemoteAnimationAdapterWrapper对象。
    hidden:这个布尔值表示窗口是否隐藏。如果窗口是隐藏的,那么就不会显示动画。前面传递的!isVisible()值为false。
    type:这个整数代表了动画的类型。这里我们传递的是ANIMATION_TYPE_APP_TRANSITION,即图层上显示的app_transition。
    animationFinishedCallbackanimationCancelledCallback:这两个是回调函数,分别在动画完成和动画取消时被调用。
    snapshotAnim:这个参数是给定动画的快照。如果参数为null,那么就表示没有快照。传递过来的是变量thumbnailAdapter,即startBounds变量不为空情况的RemoteAnimationAdapterWrapper对象。

  • 代码含义
    关键的代码只有这一句mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback, animationCancelledCallback, snapshotAnim, mSurfaceFreezer);
    这行代码调用了SurfaceAnimator的startAnimation方法来启动动画。SurfaceAnimator的作用主要是控制窗口动画,它是窗口动画的中控,通过操控mLeash对象来实现窗口的大小、位置、透明度等动画属性的改变。这个方法需要一系列参数,包括上面解释的所有参数,还有一个SurfaceFreezer对象mSurfaceFreezer,它可以在动画开始时冻结窗口的更新,以防止在动画过程中窗口的内容闪烁。
    mSurfaceAnimator和mSurfaceFreezer是在WindowContainer的构造方法中初始化的

    class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,InsetsControlTarget {......WindowContainer(WindowManagerService wms) {mWmService = wms;mTransitionController = mWmService.mAtmService.getTransitionController();mPendingTransaction = wms.mTransactionFactory.get();mSyncTransaction = wms.mTransactionFactory.get();mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);mSurfaceFreezer = new SurfaceFreezer(this, wms);}......
    }
    

通过SurfaceAnimator中创建leash

代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java

    /*** Starts an animation.** @param anim The object that bridges the controller, {@link SurfaceAnimator}, with the*             component responsible for running the animation. It runs the animation with*             {@link AnimationAdapter#startAnimation} once the hierarchy with*             the Leash has been set up.* @param hidden Whether the container holding the child surfaces is currently visible or not.*               This is important as it will start with the leash hidden or visible before*               handing it to the component that is responsible to run the animation.* @param animationFinishedCallback The callback being triggered when the animation finishes.* @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a*                                   cancel call to the underlying AnimationAdapter.* @param snapshotAnim The animation to run for the snapshot. {@code null} if there is no*                     snapshot.*/void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,@AnimationType int type,@Nullable OnAnimationFinishedCallback animationFinishedCallback,@Nullable Runnable animationCancelledCallback,@Nullable AnimationAdapter snapshotAnim, @Nullable SurfaceFreezer freezer) {//开始新的动画之前,取消之前的动画//参数含义:t(是一个事务对象),true(表示动画正在重新启动),和true(表示向前取消)cancelAnimation(t, true /* restarting */, true /* forwardCancel */);//初始化参数,把WindowContainer.startAnimation中传递的参数赋值给对应变量mAnimation = anim;mAnimationType = type;mSurfaceAnimationFinishedCallback = animationFinishedCallback;mAnimationCancelledCallback = animationCancelledCallback;//获取当前窗口的SurfaceControlfinal SurfaceControl surface = mAnimatable.getSurfaceControl();//没有surface,则取消当前的动画if (surface == null) {Slog.w(TAG, "Unable to start animation, surface is null or no children.");cancelAnimation();return;}//调用SurfaceFreezer中takeLeashForAnimation()获取mLeash,但是SurfaceFreezer中没有被初始化,所以这里的mLeash还是为nullmLeash = freezer != null ? freezer.takeLeashForAnimation() : null;if (mLeash == null) {//创建mLeashmLeash = createAnimationLeash(mAnimatable, surface, t, type,mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,0 /* y */, hidden, mService.mTransactionFactory);//创建动画“leash”后执行的一些操作,包括重置图层、重新分配图层以及重置Surface的位置mAnimatable.onAnimationLeashCreated(t, mLeash);}//处理动画开始时进行一些设置和准备工作mAnimatable.onLeashAnimationStarting(t, mLeash);if (mAnimationStartDelayed) {ProtoLog.i(WM_DEBUG_ANIM, "Animation start delayed for %s", mAnimatable);return;}//将leash传给AnimationAdaptermAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);if (ProtoLogImpl.isEnabled(WM_DEBUG_ANIM)) {StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw);mAnimation.dump(pw, "");ProtoLog.d(WM_DEBUG_ANIM, "Animation start for %s, anim=%s", mAnimatable, sw);}//获取一个快照,并使用该快照来执行动画,我们这里snapshotAnim为null,因此不涉及if (snapshotAnim != null) {mSnapshot = freezer.takeSnapshotForAnimation();if (mSnapshot == null) {Slog.e(TAG, "No snapshot target to start animation on for " + mAnimatable);return;}mSnapshot.startAnimation(t, snapshotAnim, type);}}

入参就是前面WindowContainer.startAnimation中传递的参数。

获取当前窗口的surface

final SurfaceControl surface = mAnimatable.getSurfaceControl();
mAnimatable是Animatable接口的对象,WindowContainer实现了Animatable接口。
在WindowContainer构造方法中初始化mSurfaceAnimator = new SurfaceAnimator(this, this::onAnimationFinished, wms);
而SurfaceAnimator的构造方法是

    SurfaceAnimator(Animatable animatable,@Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,WindowManagerService service) {mAnimatable = animatable;mService = service;mStaticAnimationFinishedCallback = staticAnimationFinishedCallback;mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);}

也就是说实际上是把this赋值给了mAnimatable,因此mAnimatable就代表了当前的窗口。

从前面的代码可以看出远程动画涉及到打开的应用和关闭的应用,这两个应用的动画。

        applyAnimations(openingWcs, openingApps, transit, true /* visible */, animLp,voiceInteraction);applyAnimations(closingWcs, closingApps, transit, false /* visible */, animLp,voiceInteraction);

即我们这里第一次获取当前窗口mAnimatable时,代表的是正在打开的应用,即应用Task;第二次获取的则是桌面,即桌面Task。

获取Leash

mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
根据freezer是否为null来确定mLeash的值,我们这里freezer是从WindowContainer.startAnimation方法中传递过来的mSurfaceFreezer,这个变量在WindowContainer的构造方法中初始化mSurfaceFreezer = new SurfaceFreezer(this, wms);,因此mSurfaceFreezer不为null,即freezer不为nullfreezer != null true,所以走freezer.takeLeashForAnimation()
代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceFreezer.java

	SurfaceControl mLeash;/*** Used by {@link SurfaceAnimator}. This "transfers" the leash to be used for animation.* By transferring the leash, this will no longer try to clean-up the leash when finished.*/SurfaceControl takeLeashForAnimation() {SurfaceControl out = mLeash;mLeash = null;return out;}

mLeash在SurfaceFreezer类中并没有初始化,因此我们的mLeash值为null,所以out的值同样为null,最终SurfaceAnimator类中的mLeash获取到的值为null

创建Leash

mLeash为null,使用createAnimationLeash方法创建Leash

            mLeash = createAnimationLeash(mAnimatable, surface, t, type,mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,0 /* y */, hidden, mService.mTransactionFactory);

入参含义
mAnimatable:当前窗口。
surface:当前窗口的surface。
t:一个事务对象,用于执行一系列操作。
type:动画类型。
mAnimatable.getSurfaceWidth()mAnimatable.getSurfaceHeight():窗口surface尺寸的参数。
0 /* x */0 /* y */:坐标位置
hidden:一个布尔值,表示是否隐藏。
mService.mTransactionFactory:一个事务工厂对象,用于创建新的事务。

SurfaceAnimator.createAnimationLeash()
代码路径:frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java

    static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,Transaction t, @AnimationType int type, int width, int height, int x, int y,boolean hidden, Supplier<Transaction> transactionFactory) {/* log add start*/Slog.i("WindowManager:","createAnimationLeash type = " + animationTypeToString(type) , new Exception());/* log add end*/ProtoLog.i(WM_DEBUG_ANIM, "Reparenting to leash for %s", animatable);//通过SurfaceControl.Builder创建leashfinal SurfaceControl.Builder builder = animatable.makeAnimationLeash().setParent(animatable.getAnimationLeashParent()).setName(surface + " - animation-leash of " + animationTypeToString(type))// TODO(b/151665759) Defer reparent calls// We want the leash to be visible immediately because the transaction which shows// the leash may be deferred but the reparent will not. This will cause the leashed// surface to be invisible until the deferred transaction is applied. If this// doesn't work, you will can see the 2/3 button nav bar flicker during seamless// rotation..setHidden(hidden).setEffectLayer().setCallsite("SurfaceAnimator.createAnimationLeash");//通过前面的SurfaceControl.Builder创建leashfinal SurfaceControl leash = builder.build();//其他属性设置t.setWindowCrop(leash, width, height);t.setPosition(leash, x, y);t.show(leash);t.setAlpha(leash, hidden ? 0 : 1);//当前窗口的surface重新绑定到新创建的leash上t.reparent(surface, leash);return leash;}

传递当前窗口animatable,为其和其父节点之间添加surface。第一次创建的是正在打开的应用的动画,第二次是桌面关闭时的动画。

下面我们解读一下,leash是如何创建并加入其中的

通过SurfaceControl.Builder创建leash
  • animatable.makeAnimationLeash()
    代码路径: frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

        public Builder makeAnimationLeash() {return makeSurface().setContainerLayer();}
    

    创建一个图层作为容器layer

  • setParent(animatable.getAnimationLeashParent())
    这段代码我们分成两个部分来看,即setParent()getAnimationLeashParent()
    1.setParent()
    代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

            /*** Set a parent surface for our new SurfaceControl.** Child surfaces are constrained to the onscreen region of their parent.* Furthermore they stack relatively in Z order, and inherit the transformation* of the parent.** @param parent The parent control.*/@NonNullpublic Builder setParent(@Nullable SurfaceControl parent) {mParent = parent;return this;}
    

    这个段代码很简单,就是给当前SurfaceControl设置一个父SurfaceControl。
    2.getAnimationLeashParent()
    代码路径: frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

        @Overridepublic SurfaceControl getAnimationLeashParent() {return getParentSurfaceControl();}/** @return The SurfaceControl parent for this containers SurfaceControl.*         The SurfaceControl must be valid if non-null.*/@Overridepublic SurfaceControl getParentSurfaceControl() {final WindowContainer parent = getParent();if (parent == null) {return null;}return parent.getSurfaceControl();}/*** @return The SurfaceControl for this container.* The SurfaceControl must be valid if non-null.*/@Overridepublic SurfaceControl getSurfaceControl() {return mSurfaceControl;}
    

    简单来说,就是获取当前窗口父SurfaceControl。

    那么合起来setParent(animatable.getAnimationLeashParent())的意思就是,把当前新创建的SurfaceControl(leash)的父亲设置为当前窗口父亲的SurfaceControl
    即此时leash图层和当前窗口应用Task的父亲均是DefaultTaskDsiplayArea,两人还在当兄弟。
    简图如下:
    在这里插入图片描述
    此时创建的是应用Task的动画,桌面Task的动画尚未创建,待应用Task动画创建完成后,才会去走正在关闭的应用(桌面)的动画逻辑。
    桌面Task是一直挂在DefaultTaskDsiplayArea上的,这里我们先不关注桌面Task节点。

  • setEffectLayer()
    代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

            /*** Indicate whether an 'EffectLayer' is to be constructed.** An effect layer behaves like a container layer by default but it can support* color fill, shadows and/or blur. These layers will not have an associated buffer.* When created, this layer has no effects set and will be transparent but the caller* can render an effect by calling:*  - {@link Transaction#setColor(SurfaceControl, float[])}*  - {@link Transaction#setBackgroundBlurRadius(SurfaceControl, int)}*  - {@link Transaction#setShadowRadius(SurfaceControl, float)}** @hide*/public Builder setEffectLayer() {mFlags |= NO_COLOR_FILL;//清空缓冲区设置unsetBufferSize();return setFlags(FX_SURFACE_EFFECT, FX_SURFACE_MASK);}
    

    设置为EffectLayer。它是一种特殊类型的SurfaceControl层,它默认表现得像一个容器层,但可以支持颜色填充、阴影和/或模糊效果。
    这个EffectLayer主要就是用于实现一些视觉效果。
    默认的注释里面也说明可以使用这些方法来渲染一个效果:
    Transaction#setColor(SurfaceControl, float[]):使用给定的颜色数组设置该层的颜色。
    Transaction#setBackgroundBlurRadius(SurfaceControl, int):设置背景模糊的半径。
    Transaction#setShadowRadius(SurfaceControl, float):设置阴影的半径。

final SurfaceControl leash = builder.build();
最后通过build()方法创建leash

当前窗口的surface重新绑定到新创建的leash上

t.reparent(surface, leash);
这里的surface指的就是从前面传递的当前窗口的SurfaceControl。
代码路径:frameworks/base/core/java/android/view/SurfaceControl.java

        /*** Re-parents a given layer to a new parent. Children inherit transform (position, scaling)* crop, visibility, and Z-ordering from their parents, as if the children were pixels within the* parent Surface.** @param sc The SurfaceControl to reparent* @param newParent The new parent for the given control.* @return This Transaction*/@NonNullpublic Transaction reparent(@NonNull SurfaceControl sc,@Nullable SurfaceControl newParent) {//检查传入的SurfaceControl对象是否满足某些预设条件checkPreconditions(sc);long otherObject = 0;if (newParent != null) {//检查新父对象是否被释放。如果已经被释放,那么它会抛出异常。newParent.checkNotReleased();//新父对象不为null且未被释放,那么将新父对象的Native对象赋值给otherObject。otherObject = newParent.mNativeObject;}//传入了三个参数:1.当前对象的Native对象 2.被重新设置父对象的SurfaceControl的Native对象 3.新父对象的Native对象。//用于实现重新设置父对象的具体操作。nativeReparent(mNativeObject, sc.mNativeObject, otherObject);//把被重新设置父对象的SurfaceControl和新父对象存储到mReparentedSurfaces这个map中。mReparentedSurfaces.put(sc, newParent);return this;}

@NonNull SurfaceControl sc: 表示要被重新设置父对象的SurfaceControl对象。这个参数不能为null。
@Nullable SurfaceControl newParent: 表示新的父SurfaceControl对象。可以为null,表示没有新的父对象。
这个方法主要就是把当前窗口的SurfaceControl的父亲,修改为leash
mReparentedSurfaces是ArrayMap对象,以键值对的形式临时存储父子关系,key值存储SurfaceControl对象,value为其父SurfaceControl对象。

即此时leash图层变成了当前窗口应用Task图层的父亲,如简易图所示:
曾经我们是兄弟,如今我是你爸爸~
在这里插入图片描述

需要注意的是,Task和DefaultTaskDsiplayArea容器之前的关系并未改变,创建leash图层的过程只是改变的是surface之间的关系
即leash图层是DefaultTaskDsiplayArea图层的父亲,Task的图层是leash图层的父亲,但是Task的父亲仍然是DefaultTaskDsiplayArea。
实际关系情况如下图所示:
在这里插入图片描述

所以DefaultTaskDsiplayArea和leash实际上是表面父子,leash和Task也是表面父子

leash的surface调整

mAnimatable.onAnimationLeashCreated(t, mLeash);
入参是一个事务(用于操作窗口系统的底层API)和一个SurfaceControl对象(表示一个可以控制和操作Surface的接口)
把创建好的mLeash传递到onAnimationLeashCreated方法中,做一些Surface调整操作。
该方法实现在WindowContainer中。
代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

    void reassignLayer(Transaction t) {final WindowContainer parent = getParent();if (parent != null) {parent.assignChildLayers(t);}}void resetSurfacePositionForAnimationLeash(Transaction t) {t.setPosition(mSurfaceControl, 0, 0);final SurfaceControl.Transaction syncTransaction = getSyncTransaction();if (t != syncTransaction) {// Avoid restoring to old position if the sync transaction is applied later.syncTransaction.setPosition(mSurfaceControl, 0, 0);}mLastSurfacePosition.set(0, 0);}@Overridepublic void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {mLastLayer = -1;mAnimationLeash = leash;reassignLayer(t);// Leash is now responsible for position, so set our position to 0.resetSurfacePositionForAnimationLeash(t);}

这段代码主要用于在创建新的leash时,重置动画目标的位置,并初始化一些动画相关的状态。同时,可能还用于重新分配或者设置子容器的图层。
首先,在新的动画(leash)被创建时被调用。在这个方法中,首先将mLastLayer(可能表示上一个图层或者上一个动画目标)设置为-1,然后保存传入的leashmAnimationLeash(后面removeLeash流程中会用到mAnimationLeash)。
之后,调用reassignLayer(t)方法,这个方法获取这个视图的父容器,如果父容器存在,那么就调用父容器的assignChildLayers(t)方法(用于调整其所有child的z-order)。
最后,为了确保leash现在位置的控制,调用resetSurfacePositionForAnimationLeash(t)方法将Surface的位置重置为(0,0),重置界面元素的位置以便进行动画。
注:Z-order也被称为深度顺序(depth order)或Z轴顺序,它用于确定图层(Layers)在屏幕上的堆叠顺序。简单来说,Z-order就是图层在Z轴上的位置,Z轴位置越低,图层越在底层,Z轴位置越高,图层越在顶层。

处理动画开始时进行一些设置和准备工作

mAnimatable.onLeashAnimationStarting(t, mLeash);
入参同样是一个事务(用于操作窗口系统的底层API)和一个SurfaceControl对象(表示一个可以控制和操作Surface的接口)
onLeashAnimationStarting方法是在ActivityRecord中实现的。
代码路径:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

    @Overridepublic void onLeashAnimationStarting(Transaction t, SurfaceControl leash) {if (mAnimatingActivityRegistry != null) {//1.将正在启动或者有动画效果的Activity添加到列表中,以便于管理和控制这些Activity的动画效果。mAnimatingActivityRegistry.notifyStarting(this);}// If the animation needs to be cropped then an animation bounds layer is created as a// child of the root pinned task or animation layer. The leash is then reparented to this// new layer.//2.否需要创建一个动画边界层if (mNeedsAnimationBoundsLayer) {//设置临时矩形为空mTmpRect.setEmpty();//调用方法检查当前的活动转移是否在任务内部。//如果是,则获取任务的边界到临时矩形mTmpRect。如果不是,则获取RootTask的边界。if (getDisplayContent().mAppTransitionController.isTransitWithinTask(getTransit(), task)) {task.getBounds(mTmpRect);} else {final Task rootTask = getRootTask();if (rootTask == null) {return;}// Set clip rect to root task bounds.rootTask.getBounds(mTmpRect);}//创建动画边界层mAnimationBoundsLayer = createAnimationBoundsLayer(t);// Crop to root task bounds.//设置leash的层为0//leash将被放置在Z轴的最底层,如果有其他层级的SurfaceControl对象,它们将会覆盖在leash之上。t.setLayer(leash, 0);//并设置AnimationBoundsLayer的层为上一个层的值,保证leash在AnimationBoundsLayer下面t.setLayer(mAnimationBoundsLayer, getLastLayer());// Reparent leash to animation bounds layer.//重新将leash的父节点设置为动画边界层。t.reparent(leash, mAnimationBoundsLayer);}}private SurfaceControl createAnimationBoundsLayer(Transaction t) {ProtoLog.i(WM_DEBUG_APP_TRANSITIONS_ANIM, "Creating animation bounds layer");final SurfaceControl.Builder builder = makeAnimationLeash()//给AnimationBoundsLayer设置父节点为Leash的父节点//即把动画边界层的父节点设置为windowToken.setParent(getAnimationLeashParent()).setName(getSurfaceControl() + " - animation-bounds").setCallsite("ActivityRecord.createAnimationBoundsLayer");final SurfaceControl boundsLayer = builder.build();t.show(boundsLayer);return boundsLayer;}

这个方法其实主要就是做了两件事:
1.根据mAnimatingActivityRegistry的值判断,是否需要把有动画效果的Activity添加到列表中
2.根据mNeedsAnimationBoundsLayer的值判断,否需要创建一个动画边界层

createAnimationBoundsLayer就是创建了一个SurfaceControl。
getLastLayer()用于返回当前窗口的最高(或最后)层级。假设我们有一个窗口管理系统中,窗口的层级从0开始编号。当一个新窗口创建时,它可能被赋予层级0。然后,如果这个新窗口被另一个窗口覆盖,那么新窗口的层级可能会更新为1,依此类推。
通过使用AnimationBoundsLayer,可以定义一个矩形区域,该区域可以作为动画的边界。当动画开始时,它只在该定义的区域内显示,不会超出这个边界。AnimationBoundsLayer的主要作用是限制动画的显示区域,以确保动画不会影响到应用程序的其他部分。

将leash传给RemoteAnimationAdapterWrapper,执行动画

mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
mAnimation是AnimationAdapter接口的对象,调用其startAnimation方法,传递mLeash(动画)、t(事务)、type(动画类型)和mInnerAnimationFinishedCallback(回调函数)。
mAnimation的值是前面WindowContainer.startAnimation传递的anim,这个anim实际上就是传递的也就是前面WindowContainer.getAnimationAdapter中创建startBounds变量为空情况的RemoteAnimationAdapterWrapper对象。因此这里调用接口AnimationAdapter的方法startAnimation正是在RemoteAnimationAdapterWrapper中实现的。
代码路径:frameworks/base/services/core/java/com/android/server/wm/RemoteAnimationController.java

        @Overridepublic void startAnimation(SurfaceControl animationLeash, Transaction t,@AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation");//设置动画的起始位置和窗口裁剪区域if (mStartBounds.isEmpty()) {// Restore position and stack crop until client has a chance to modify it.t.setPosition(animationLeash, mPosition.x, mPosition.y);t.setWindowCrop(animationLeash, mEndBounds.width(), mEndBounds.height());} else {// Offset the change animation leash to the relative start position in parent.// (mPosition) is the relative end position in parent container.// (mStartBounds - mEndBounds) is the position difference between start and end.// (mPosition + mStartBounds - mEndBounds) will be the relative start position.t.setPosition(animationLeash, mPosition.x + mStartBounds.left - mEndBounds.left,mPosition.y + mStartBounds.top - mEndBounds.top);t.setWindowCrop(animationLeash, mStartBounds.width(), mStartBounds.height());}//保存动画图层到mCapturedLeashmCapturedLeash = animationLeash;//当动画完成时的回调函数 finishCallback保存到mCapturedFinishCallback。mCapturedFinishCallback = finishCallback;//保存动画类型mAnimationType = type;}

这个方法的主要目的是根据提供的起始和结束边界来设置动画的起始位置和窗口裁剪区域,并保存相关信息到RemoteAnimationAdapterWrapper的成员变量中,以供后续使用。

启动并显示动画流程

layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
在最开始的AppTransitionController.handleAppTransitionReady方法中调用AppTransition.goodToGo方法开始启动并显示动画的流程。
入参同样都是在handleAppTransitionReady方法中初始化的:
参数transit的值通过getTransitCompatType方法中获取,是TRANSIT_OLD_WALLPAPER_CLOSE(12);
参数topOpeningApp指的是正在打开的应用列表 (mOpeningApps) 中的顶层应用,即应用Task对应的ActivityRecord,也就是我们在桌面启动的应用的ActivityRecord。

判断是否需要重新进行布局,RemoteAnimationController状态

代码路径:frameworks/base/services/core/java/com/android/server/wm/AppTransitionController.java

/*** @return bit-map of WindowManagerPolicy#FINISH_LAYOUT_REDO_* to indicate whether another*         layout pass needs to be done*/int goodToGo(@TransitionOldType int transit, ActivityRecord topOpeningApp) {mNextAppTransitionFlags = 0;mNextAppTransitionRequests.clear();setAppTransitionState(APP_STATE_RUNNING);//如果topOpeningApp不为空,即应用ActivityRecord不为空,//那么获取其ActivityRecord的容器,即应用Taskfinal WindowContainer wc =topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;//应用ActivityRecord不为空,获取AnimationAdapter,即RemoteAnimationAdapterWrapperfinal AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;//通知所有的AppTransitionListener,应用过渡动画即将开始//根据其返回值判断是否需要重新进行布局//我们这里所有的调用AppTransitionListener的redoLayout返回值都0//即redoLayout返回值都0int redoLayout = notifyAppTransitionStartingLocked(AppTransition.isKeyguardGoingAwayTransitOld(transit),AppTransition.isKeyguardOccludeTransitOld(transit),topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,topOpeningAnim != null? topOpeningAnim.getStatusBarTransitionsStartTime(): SystemClock.uptimeMillis(),AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);//mRemoteAnimationController不为空if (mRemoteAnimationController != null) {mRemoteAnimationController.goodToGo(transit);}//如果mRemoteAnimationController为空//transit值为TRANSIT_OLD_WALLPAPER_CLOSE//topOpeningAnim,应用ActivityRecord不为空时else if ((isTaskOpenTransitOld(transit) || transit == TRANSIT_OLD_WALLPAPER_CLOSE)&& topOpeningAnim != null) {//shouldAttachNavBarToAppDuringTransition(),在动画过渡期间将导航栏附加到应用程序//getRecentsAnimationController(),RecentsAnimationController为空if (mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()&& mService.getRecentsAnimationController() == null) {//创建一个新的NavBarFadeAnimationControllerfinal NavBarFadeAnimationController controller =new NavBarFadeAnimationController(mDisplayContent);// For remote animation case, the nav bar fades out and in is controlled by the// remote side. For non-remote animation case, we play the fade out/in animation// here. We play the nav bar fade-out animation when the app transition animation// starts and play the fade-in animation sequentially once the fade-out is finished.//使用fadeOutAndInSequentially方法使其淡出并淡入controller.fadeOutAndInSequentially(topOpeningAnim.getDurationHint(),null /* fadeOutParent */, topOpeningApp.getSurfaceControl());}}return redoLayout;}

我们RemoteAnimationController已经创建,所以mRemoteAnimationController != null为true,进入该流程mRemoteAnimationController.goodToGo(transit);,调用RemoteAnimationController的goodToGo方法,传递的参数值为TRANSIT_OLD_WALLPAPER_CLOSE

准备开始动画

 /*** Called when the transition is ready to be started, and all leashes have been set up.*/void goodToGo(@WindowManager.TransitionOldType int transit) {ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "goodToGo()");//如果动画已取消,进入动画播放完成的流程if (mCanceled) {ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,"goodToGo(): Animation canceled already");onAnimationFinished();invokeAnimationCancelled("already_cancelled");return;}// Scale the timeout with the animator scale the controlling app is using.//一个定时器,超过TIMEOUT_MS则执行mTimeoutRunnable//实际上就是执行cancelAnimation方法取消动画mHandler.postDelayed(mTimeoutRunnable,(long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));//创建动画完成时的回调函数mFinishedCallback = new FinishedCallback(this);// Create the app targets//创建类型为App的RemoteAnimationTargetfinal RemoteAnimationTarget[] appTargets = createAppAnimations();//判断如果appTargets为空,且transit的值与锁屏相关//则取消动画播放进入动画播放完成的流程if (appTargets.length == 0 && !AppTransition.isKeyguardOccludeTransitOld(transit)) {// Keyguard occlude transition can be executed before the occluding activity becomes// visible. Even in this case, KeyguardService expects to receive binder call, so we// don't cancel remote animation.ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,"goodToGo(): No apps to animate, mPendingAnimations=%d",mPendingAnimations.size());onAnimationFinished();invokeAnimationCancelled("no_app_targets");return;}//运行mOnRemoteAnimationReady,做一些播放动画前的检查if (mOnRemoteAnimationReady != null) {mOnRemoteAnimationReady.run();mOnRemoteAnimationReady = null;}// Create the remote wallpaper animation targets (if any)//创建壁纸类型的RemoteAnimationTargetfinal RemoteAnimationTarget[] wallpaperTargets = createWallpaperAnimations();// Create the remote non app animation targets (if any)//创建非app类型的RemoteAnimationTarget,例如状态栏,导航栏等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();}if (ProtoLogImpl.isEnabled(WM_DEBUG_REMOTE_ANIMATIONS)) {ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "startAnimation(): Notify animation start:");writeStartDebugStatement();}});setRunningRemoteAnimation(true);}

进入桌面进程启动动画

RemoteAnimationAdapter.getRunner().onAnimationStart(transit, appTargets,wallpaperTargets, nonAppTargets, mFinishedCallback);

动画移除流程

处理和响应动画完成的逻辑

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java

		private void doAnimationFinished(@AnimationType int type, AnimationAdapter anim) {for (int i = 0; i < mSurfaceAnimationSources.size(); ++i) {//mSurfaceAnimationSources中每个容器,做对应的onAnimationFinishedmSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);}//清除动画源列表mSurfaceAnimationSources.clear();if (mDisplayContent != null) {//调用DisplayContent的onWindowAnimationFinished方法//从当前源码上看,主要是针对输入法相关做了一些操作mDisplayContent.onWindowAnimationFinished(this, type);}}/*** Called when an animation has finished running.*/protected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {//主要用于 清空 mSurfaceAnimationSources 列表doAnimationFinished(type, anim);//WindowManagerService中实现onAnimationFinished()//用于唤醒所有等待mGlobalLock对象的线程,确保多个线程能够正确地执行任务mWmService.onAnimationFinished();//将 mNeedsZBoost 设置为 false,表示不再需要Z轴增强mNeedsZBoost = false;}

我们这里mSurfaceAnimationSources是保存的是需要做动画的ActivityRecord,mSurfaceAnimationSources的值是在applyAnimationUnchecked方法中添加的。
mSurfaceAnimationSources.valueAt(i).onAnimationFinished(type, anim);调用了不同容器onAnimationFinished方法,在ActivityRecord和WindowState中都重写了这个方法。我们这里是远程动画,主要调用的就是ActivityRecord中重写的onAnimationFinished方法。
代码路径:frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java

    @Overrideprotected void onAnimationFinished(@AnimationType int type, AnimationAdapter anim) {super.onAnimationFinished(type, anim);Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "AR#onAnimationFinished");mTransit = TRANSIT_OLD_UNSET;mTransitFlags = 0;setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER,"ActivityRecord");clearThumbnail();setClientVisible(isVisible() || mVisibleRequested);getDisplayContent().computeImeTargetIfNeeded(this);ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"+ ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",this, reportedVisible, okToDisplay(), okToAnimate(),isStartingWindowDisplayed());// clean up thumbnail windowif (mThumbnail != null) {mThumbnail.destroy();mThumbnail = null;}// WindowState.onExitAnimationDone might modify the children list, so make a copy and then// traverse the copy.final ArrayList<WindowState> children = new ArrayList<>(mChildren);children.forEach(WindowState::onExitAnimationDone);// The starting window could transfer to another activity after app transition started, in// that case the latest top activity might not receive exit animation done callback if the// starting window didn't applied exit animation success. Notify animation finish to the// starting window if needed.if (task != null && startingMoved) {final WindowState transferredStarting = task.getWindow(w ->w.mAttrs.type == TYPE_APPLICATION_STARTING);if (transferredStarting != null && transferredStarting.mAnimatingExit&& !transferredStarting.isSelfAnimating(0 /* flags */,ANIMATION_TYPE_WINDOW_ANIMATION)) {transferredStarting.onExitAnimationDone();}}getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);scheduleAnimation();// Schedule to handle the stopping and finishing activities which the animation is done// because the activities which were animating have not been stopped yet.mTaskSupervisor.scheduleProcessStoppingAndFinishingActivitiesIfNeeded();Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/264466.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习python的第7天,她不再开放她的听歌榜单

我下午登录上小号&#xff0c;打开聊天消息看到了她的回复&#xff0c;我很开心兴奋&#xff0c;可是她不再开放她的听歌榜单了&#xff0c;我感觉得到&#xff0c;我要失恋了。 “因为当年电视上看没有王菲版本的” “行”。 “那你以后还会开放听歌榜单吗&#xff1f;”我…

【监控】grafana图表使用快速上手

目录 1.前言 2.连接 3.图表 4.job和path 5.总结 1.前言 上一篇文章中&#xff0c;我们使用spring actuatorPrometheusgrafana实现了对一个spring boot应用的可视化监控。 【监控】Spring BootPrometheusGrafana实现可视化监控-CSDN博客 其中对grafana只是打开了一下&am…

【Azure 架构师学习笔记】- Azure Databricks (10) -- UC 使用

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (9) – UC权限 在前面的文章&#xff1a;【Azure 架构师学习笔记】- Azure Databricks (6) - 配置Unity Catalog中演示了如何配置一个UC。 本文…

SpringBoot 学习笔记

文章目录 一、IoC二、AOP三、bean3.1 bean 生命周期3.2 三种依赖注入方式3.3 bean 线程安全 四、SpringMVC五、常用注解5.1 Scope5.2 PostConstruct 和 PreDestroy5.3 Component 和 Bean5.4 Autowired 和 Resource 六、基于 ApplicationContextAware 实现工厂模式七、事务失效八…

【了解机器学习的定义与发展历程】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述概要知识图谱 简述概要 了解机器学习的定义与发展历程 知识图谱 机器学习&#xff08;Machine Learning&#xff0c;ML&#xff09;是一门跨学科的学科&#xff0c;它使用计算机模拟或实现人类学习行为&#xff0c;通…

设计模式: 策略模式

文章目录 一、什么是策略模式二、策略模式结构三、使用场景案例分析1、使用场景2、案例分析&#xff08;1&#xff09;消除条件分支 一、什么是策略模式 策略模式是一种行为型设计模式&#xff0c;它允许定义一组算法&#xff0c;并将每个算法封装在独立的类中&#xff0c;使它…

解决内嵌帆软报表出现重定向问题

最近收到反馈&#xff0c;某些程序的前端通过iframe标签内嵌finebi帆软报表时&#xff0c;出现一系列问题。 问题1: 如下图所示&#xff0c;单点登录(单点登录地址schema是https)后service地址的schema协议是http, 浏览器内核的安全测试不允许http访问https。 解决方案&#xf…

【C进阶】顺序表详解

文章目录 &#x1f4dd;线性表的概念&#x1f320; 顺序表&#x1f309;顺序表的概念 &#x1f320;声明--接口&#x1f309;启动&#x1f320;初始化&#x1f309;扩容&#x1f320;尾插&#x1f309; 打印&#x1f320;销毁&#x1f309; 尾删&#x1f320;头插&#x1f309;…

matlab 线性四分之一车体模型

1、内容简介 略 57-可以交流、咨询、答疑 路面采用公式积分来获得&#xff0c;计算了车体位移、非悬架位移、动载荷等参数 2、内容说明 略 3、仿真分析 略 线性四分之一车体模型_哔哩哔哩_bilibili 4、参考论文 略

Redis高并发分布锁实战

Redis高并发分布锁实战 问题场景 场景一: 没有捕获异常 // 仅仅加锁 // 读取 stock15 Boolean ret stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v) // TODO 业务代码 stock-- stringRedisTemplate.delete(&quo…

php脚本输出中文在浏览器中显示乱码

问题说明 这个问题一般出现在较低版本的php中&#xff0c;原因是php和浏览器的字符解析方式不对应 &#xff0c;导致中文字符被错误解析成乱码 &#xff08;注&#xff0c;此处的php版本任意切换是依赖于小皮面板&#xff08;phpstudy&#xff09;实现的&#xff0c;感兴趣可以…

Docker容器故障排查与解决方案

Docker是一种相对使用较简单的容器&#xff0c;我们可以通过以下几种方式获取信息&#xff1a; 1、通过docker run执行命令&#xff0c;或许返回信息 2、通过docker logs 去获取日志&#xff0c;做有针对性的筛选 3、通过systemctl status docker查看docker服务状态 4、通过…

【牛牛送书 | 第四期】《高效使用Redis:一书学透数据存储与高可用集群》带你快速学习使用Redis

前言&#xff1a; 当今互联网技术日新月异&#xff0c;随着数据量的爆炸式增长&#xff0c;如何高效地存储和管理数据成为了每个公司都必须面对的挑战。与此同时&#xff0c;用户对于应用程序的响应速度和稳定性要求也越来越高。在这个背景下&#xff0c;Redis 作为一个…

【lv14 day10内核模块参数传递和依赖】

一、模块传参 module_param(name,type,perm);//将指定的全局变量设置成模块参数 /* name:全局变量名 type&#xff1a; 使用符号 实际类型 传参方式 bool bool insmod xxx.ko 变量名0 或 1 invbool bool insmod xxx.ko 变量名0 或 1 charp char * insmod xxx.ko 变量名“字符串…

解析Hadoop三大核心组件:HDFS、MapReduce和YARN

目录 HadoopHadoop的优势 Hadoop的组成HDFS架构设计Yarn架构设计MapReduce架构设计 总结 在大数据时代&#xff0c;Hadoop作为一种开源的分布式计算框架&#xff0c;已经成为处理大规模数据的首选工具。它采用了分布式存储和计算的方式&#xff0c;能够高效地处理海量数据。Had…

基于51单片机的智能监护与健康检测[proteus仿真]

基于51单片机的自行车测速系统设计[proteus仿真] 个人健康检测系统这个题目算是课程设计和毕业设计中常见的题目了&#xff0c;本期是一个基于51单片机的智能监护与健康检测 需要的源文件和程序的小伙伴可以关注公众号【阿目分享嵌入式】&#xff0c;赞赏任意文章 2&#xff…

Liunx--nginx负载均衡--前后端分离项目部署

一.nginx简介 Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;它以其轻量级、占用资源少、并发能力强而广受欢迎。 详细介绍 开发背景与特点&#xff1a;Nginx由俄罗斯人Igor Sysoev开发&#xff0c;它是一个自由的、开源的软件。Nginx设计上注重性能和效率&#xff0c;能…

图像读取裁剪与人脸识别

图像读取 Image read ⇒ \Rightarrow ⇒ torchvision.datasets from torchvision import datasets dataset datasets.ImageFolder(data_dir, transformtransforms.Resize((512, 512)))Return value illustration dataset[0][0]是PIL.Image objects&#xff0c;这利用IPyth…

音视频技术-电脑连接调音台时交流声的产生与消除

当电脑&#xff08;笔记本/台式机&#xff09;声卡通过音频线与调音台&#xff08;或扩音机&#xff09;连接时&#xff0c;能听到“交流声”。有时很轻微&#xff0c;有时很明显&#xff0c;甚至干扰正常的演讲或发言。 很多时候&#xff0c;我们在台上演讲时&#xff0c;都会…

Easy-Jmeter: 性能测试平台

目录 写在开始1 系统架构2 表结构设计3 测试平台生命周期4 分布式压测5 压力机管理6 用例管理6.1 新增、编辑用例6.2 调试用例6.3 启动测试6.4 动态控量6.5 测试详情6.6 环节日志6.7 实时数据6.8 测试结果 7 测试记录7 用例分析8 系统部署8.1普通部署8.2容器化部署 写在最后 写…