美文网首页源码分析Android开发程序员
Android窗口系统第四篇---Activity动画的设置过程

Android窗口系统第四篇---Activity动画的设置过程

作者: LooperJing | 来源:发表于2017-11-10 18:00 被阅读445次

无论是系统中窗口的动画,还是应用中某一个View的动画,它们的原理都是一样的。当一个窗口打开的时候,为了看起来更缓和一点,系统都会给每一个Activity窗口添加一个动画,关于动画的部分,我所想写的有四点。第一、动画有哪些类型;第二动画是怎么设置的,由于窗口动画和过度动画(Activity窗口动画)是不一样的,就需要分开讲,在小米手机上,可以去开发者选项中将窗口动画的播放速度降慢5倍或者10倍,可以清楚的看到动画的过程。第三,动画设置完成之后,怎么触发垂直刷新信号一帧帧显示的,由于一个窗口可能存在多个动画,比如转屏动画、过度动画、窗口动画,自身动画等等,最终交给SurfaceFlinger绘制显示的时候,需要合成为一个动画,所以在谈一下动画的合成;第四,简单总结应用动画,用一个贝塞尔曲线绘制直播间点赞效果的例子讲解一下,总结而言,系统中的动画和应用中的动画原理是一样的,这篇文章是站在系统的角度上,搞清楚动画的原理,本文基于Android7.0源码。

一、动画类型

在Apptransition.java中定义了很多动画的类型,每个类型以一个int值来表示。

动画类型 含义
TRANSIT_UNSET -1 初始值,尚未设定
TRANSIT_NONE 0 没有动画
TRANSIT_ACTIVITY_OPEN 6 在同一task中在最顶端打开一个窗口
TRANSIT_ACTIVITY_CLOSE 7 关闭当前活动窗口,恢复同一个task中的上一个窗口
TRANSIT_TASK_OPEN 8 新建任务并创建窗口
TRANSIT_TASK_CLOSE 9 关闭当前活动窗口,回到上一个任务
TRANSIT_TASK_TO_FRONT 10 将任务移至最顶
TRANSIT_TASK_TO_BACK 11 将当前任务移至最末
TRANSIT_WALLPAPER_CLOSE 12 关闭到无墙纸的应用
TRANSIT_WALLPAPER_OPEN 13 启动墙纸应用
TRANSIT_WALLPAPER_INTRA_OPEN 14 有墙纸打开
TRANSIT_WALLPAPER_INTRA_CLOSE 15 有墙纸关闭

默认是没有动画,即类型是TRANSIT_UNSET,拿Activity的启动来举例子,比如当一个Activity打开的时候,那么系统就会设置一个TRANSIT_ACTIVITY_OPEN的动画,如果你startActivity组件的时候,Intent对象带有FLAG_ACTIVITY_NO_ANIMATION这样的flag,那么系统就会给你设置一个TRANSIT_NONE,表示没有动画,不需要动画,如果你指定了lauchMode,跨Task栈新起了一个Actiivty,那么就会设置一个TRANSIT_TASK_OPEN类型,表示新建任务并创建窗口时候要用的动画,同理当Activity的关闭的时候,也类似,总之根据不同的case,设置不同的类型,后面根据这个设置好的类型,加载不同的动画。

二、动画设置

了解了动画类型了,我们看一下Activity切换的时候,动画是怎么设置的,先简单看一下Activity的切换。

1、Activiy切换
Activiy切换

什么是Activity的切换呢?
  前一个Activity从resume状态变成pause状态,后一个Activity进入到resume状态,将前一个resume状态的窗口设置成不可见,后一个窗口设置成可见。

切换的步骤

  • ActivityStack类的成员函数startActivityLocked首先会给正在启动的Activity组件准备一个切换操作,这里所说的切换操作,你可以理解成前面设置的动画类型。
  • 接着再调用其它的成员函数来通知前一个激活的Activity组件进入到Paused状态。
  • 等到前一个激活的Activity组件进入到Paused状态之后,ActivityManagerService服务就会检查用来运行正在启动的Activity组件的进程启动起来了没有。如果这个进程还没有启动,那么ActivityManagerService服务就会将该进程启动起来,然后再调用ActivityStack类的成员函数realStartActivityLocked来将正在启动的Activity组件加载起来,并且将它的状态设置为Resumed。这里面具体又分成两个小点,一是setAppVisibility 、二是通知lauch Activity。
  • 最后通知WindowManagerService服务执行前面所准备的切换操作

首先梳理一下prepareAppTransition方法。设置什么样的切换操作,其实由Activity的行为决定。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

   @Override
   public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
       if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
               "prepareAppTransition()")) {
           throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
       }
       synchronized(mWindowMap) {
           boolean prepared = mAppTransition.prepareAppTransitionLocked(
                   transit, alwaysKeepCurrent);
           //prepared为ture,说明已经被成功设置了切换操作,但是当前冻屏、熄屏、Display没有准备好的情况下,
           //设置mSkipAppTransitionAnimation等于true,表示要跳过这切换操作对应动画的执行。
           if (prepared && okToDisplay()) {
               mSkipAppTransitionAnimation = false;
           }
       }
   }
  frameworks/base/services/core/java/com/android/server/wm/AppTransition.java

   boolean prepareAppTransitionLocked(int transit, boolean alwaysKeepCurrent) {
      .....
       //isTransitionSet()表示已经设置了切换操作类型
       if (!isTransitionSet() || mNextAppTransition == TRANSIT_NONE) {
           setAppTransition(transit);
       } else if (!alwaysKeepCurrent) {
           //alwaysKeepCurrent若等于true,表示要维持上次设置的切换类型,本次新设置的不能覆盖它
           if (transit == TRANSIT_TASK_OPEN && isTransitionEqual(TRANSIT_TASK_CLOSE)) {
               // Opening a new task always supersedes a close for the anim.
               setAppTransition(transit);
           } else if (transit == TRANSIT_ACTIVITY_OPEN
                   && isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)) {
               // Opening a new activity always supersedes a close for the anim.
               setAppTransition(transit);
           }
       }
       boolean prepared = prepare();
       if (isTransitionSet()) {
           //发送一个5秒的超时消息给WMS运行的线程(android.display线程),表示要在5秒时间类完成设置的切换操作。
           mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
           mService.mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, APP_TRANSITION_TIMEOUT_MS);
       }
       return prepared;
   }

如果transit == TRANSIT_TASK_OPEN 并且isTransitionEqual(TRANSIT_TASK_CLOSE)返回true,表示上次(之前)给Activity设置的切换操作是TRANSIT_TASK_CLOSE,那么可以调用setAppTransition,因为打开的动画要比关闭的动画优先级要高。

如果transit == TRANSIT_ACTIVITY_OPEN 并且isTransitionEqual(TRANSIT_ACTIVITY_CLOSE)返回true,表示上次(之前)给Activity设置的切换操作是TRANSIT_ACTIVITY_CLOSE,那么可以调用setAppTransition,因为打开的动画要比关闭的动画优先级要高。

  frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
   private void setAppTransition(int transit) {
       mNextAppTransition = transit;
       setLastAppTransition(TRANSIT_UNSET, null, null);
   }

setAppTransition执行过后,并且前一个激活的Activity组件进入到Paused状态了,并且客户端进程已经启动了,这个时候ActivityManagerService服务就会调用ActivityStack类的成员函数realStartActivityLocked来将正在启动的Activity组件加载起来,并且将它的状态设置为Resumed,首先看一下setAppVisibility,将窗口可见性的设置。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

 @Override
   public void setAppVisibility(IBinder token, boolean visible) {
       .....
       AppWindowToken wtoken;

       synchronized(mWindowMap) {
          //通过ActivityRecord:Token找到AppWindowToken,即找到这个token对应的Activity窗口
           wtoken = findAppWindowToken(token);
           if (wtoken == null) {
               Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token);
               return;
           }
           .....
            //mOpeningApps是WMS的成员,里面存放所有打开的窗口的AppWindowToken,首先移除,后面根据visible添加
           mOpeningApps.remove(wtoken);
             //mClosingApps是WMS的成员,里面存放所有关闭的窗口的AppWindowToken,首先移除,后面根据visible添加
           mClosingApps.remove(wtoken);
           //表示等待着去显示
           wtoken.waitingToShow = false;
           wtoken.hiddenRequested = !visible;
           if (!visible) {
               // If the app is dead while it was visible, we kept its dead window on screen.
               // Now that the app is going invisible, we can remove it. It will be restarted
               // if made visible again.
               wtoken.removeAllDeadWindows();
               wtoken.setVisibleBeforeClientHidden();
           } else if (visible) {
               if (!mAppTransition.isTransitionSet() && mAppTransition.isReady()) {
                   // Add the app mOpeningApps if transition is unset but ready. This means
                   // we're doing a screen freeze, and the unfreeze will wait for all opening
                   // apps to be ready.
                   mOpeningApps.add(wtoken);
               }
               wtoken.startingMoved = false;
               // If the token is currently hidden (should be the common case), or has been
               // stopped, then we need to set up to wait for its windows to be ready.
               if (wtoken.hidden || wtoken.mAppStopped) {
                   wtoken.clearAllDrawn();

                   // If the app was already visible, don't reset the waitingToShow state.
                  //如果hidden的值等于false,说明Activity组件当前是不可见的。又由于上面visible为true,表示Activity将要被设置成可见的,
                  //因此,这时候就需要将AppWindowToken对象wtoken的成员变量waitingToShow的值设置为true。
                   if (wtoken.hidden) {
                       wtoken.waitingToShow = true;
                   }

                   if (wtoken.clientHidden) {
                       // In the case where we are making an app visible
                       // but holding off for a transition, we still need
                       // to tell the client to make its windows visible so
                       // they get drawn.  Otherwise, we will wait on
                       // performing the transition until all windows have
                       // been drawn, they never will be, and we are sad.
                       wtoken.clientHidden = false;
                       //通知应用程序进程将参数token所描述的Activity组件设置为true
                       wtoken.sendAppVisibilityToClients();
                   }
               }
               wtoken.requestUpdateWallpaperIfNeeded();

               if (DEBUG_ADD_REMOVE) Slog.v(
                       TAG_WM, "No longer Stopped: " + wtoken);
               wtoken.mAppStopped = false;
           }
          //这个if分之在动画设置完成并且屏幕不冻屏,亮屏、Display OK的情况下才会走
           if (okToDisplay() && mAppTransition.isTransitionSet()) {
               if (wtoken.mAppAnimator.usingTransferredAnimation
                       && wtoken.mAppAnimator.animation == null) {
                   Slog.wtf(TAG_WM, "Will NOT set dummy animation on: " + wtoken
                           + ", using null transfered animation!");
               }
               if (!wtoken.mAppAnimator.usingTransferredAnimation &&
                       (!wtoken.startingDisplayed || mSkipAppTransitionAnimation)) {
                   if (DEBUG_APP_TRANSITIONS) Slog.v(
                           TAG_WM, "Setting dummy animation on: " + wtoken);
                  //设置哑动画,可以理解是一个站位的作用,后面会对它设置真正的动画
                   wtoken.mAppAnimator.setDummyAnimation();
               }
               wtoken.inPendingTransaction = true;
               if (visible) {
               //可见,把wtoken加入到mOpeningApps
                   mOpeningApps.add(wtoken);
                   wtoken.mEnteringAnimation = true;
               } else {
               //不可见,把wtoken加入到mClosingApps
                   mClosingApps.add(wtoken);
                   wtoken.mEnteringAnimation = false;
               }
               if (mAppTransition.getAppTransition() == AppTransition.TRANSIT_TASK_OPEN_BEHIND) {
                   // We're launchingBehind, add the launching activity to mOpeningApps.
                   final WindowState win = findFocusedWindowLocked(getDefaultDisplayContentLocked());
                   if (win != null) {
                       final AppWindowToken focusedToken = win.mAppToken;
                       if (focusedToken != null) {
                           focusedToken.hidden = true;
                           mOpeningApps.add(focusedToken);
                       }
                   }
               }
               return;
           }
           final long origId = Binder.clearCallingIdentity();
           wtoken.inPendingTransaction = false;
             //将参数token所描述的Activity组件的可见性设置为参数visible所描述的值;
           setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,true, wtoken.voiceInteraction);
          //向WMS服务上报参数token所描述的Activity组件的可见性
           wtoken.updateReportedVisibilityLocked();
           Binder.restoreCallingIdentity(origId);
       }
   }

这个方法变量比较多,要全部弄明白,还是要花费一些功夫的。setAppVisibility设置好之后,就可以通知客户端启动APP进程了,(所以这样看来,当一个Activity的实例还不存在的时候,它的窗口的token就已经被确定了)接着往下走,completeResumeLocked方法主要是从上下到检查哪些Activity组件是需要设置为可见的,哪些Activity组件是需要设置为不可见的,找到栈顶部第一个全屏显示的Activity组件,调用setAppVisibility设置为true,这个全屏显示Activity组件下面的所有Activity组件的可见性设置为false。
最后通知WindowManagerService服务调用executeAppTransition方法执行前面所准备的切换操作,执行这个切换操作跟Activity窗口动画(过度动画)有关系,现在就开始第二节内容。

2、过度动画设置
过度动画设置的设置过程

粗略的看一共19个步骤,前面prepareAppTransition设置切换操作和sendAppVisibility方法设置哪个Activity要隐藏,哪个Activity的要显示,已经解释过了,现在从sendAppVisibilityToClient开始。sendAppVisibilityToClient/dispatchAppVibility 这两个函数就是通知上层应用窗口可见性发生变化。如果下一个Activity是冷启动,那么这个函数并不能通知下一个Activity的窗口变为可见,因为此时该函数调用时,下一个Activity的窗口还没加到到WMS中来,Activity的窗口添加是Activity 的onResume方法中添加的。然后到第四步finishDrawingWindow下一个被Resume起来后,添加窗口、measure、layout、draw等一系列操作完成后便会调用WMS.finishDrawingWindow()来通知WMS,该窗口已经绘制好了,可以开始做动画了。WMS.finishDrawingWindow()会调用WindowStateAnimator.finishDrawingLocked()更新窗口状态mDrawState为COMMIT_DRAW_PENDING。其次WindowSurfacePlacer的requestTraversal方法,WindowSurfacePlacer的requestTraversal方法只是向WMS的主线程发送了一个DO_TRAVERSAL消息,WMS收到这个消息后,performSurfacePlacement方法就会执行。

frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
final void performSurfacePlacement() {
       if (mDeferDepth > 0) {
           return;
       }
       int loopCount = 6;
       do {
           mTraversalScheduled = false;
           performSurfacePlacementLoop();
           mService.mH.removeMessages(DO_TRAVERSAL);
           loopCount--;
       } while (mTraversalScheduled && loopCount > 0);
       mWallpaperActionPending = false;
 }

序列图中performSurfacePlacement、performSurfacePlacementLoop、performSurfacePlacementInner三个方法都是跟渲染相关的。performSurfacePlacement中调用了performSurfacePlacementLoop,performSurfacePlacementLoop中调用了performSurfacePlacementInner。(todolist:梳理performSurfacePlacement方法)

第十步commitFinishDrawingLocked是applySurfaceChangesTransaction方法调用进来的,该函数将窗口状态为COMMIT_DRAW_PENDING或READY_TO_SHOW的窗口,全部更新到READY_TO_SHOW状态

第十一步updateAllDrawnLocked函数更新AppWindowToken.allDrawn值。只有属于该AppWindowToken的所有窗口都是绘制完成状态(一般情况下只有一个窗口,有时候会有父窗口、子窗口,这时属于该AppWindowToken的窗口数量就不止一个了),AppWindowToken.allDrawn才会置为true。AppWindowToken.allDrawn为true才会使得第十步中的WMS.handleAppTransitionReadyLocked()完整的执行。
第十二步handleAppTransitionReadyLocked主要做了以下几件事情。

private int handleAppTransitionReadyLocked(WindowList windows) {
       //获取系统中所有打开的activity数量
       int appsCount = mService.mOpeningApps.size();
       //transitionGoodToGo会判断多种case情况下,不用执行动画的情况,
        //比如正在做转屏动画,mOpeningApps中任何一个allDrawn不等于true等
       if (!transitionGoodToGo(appsCount)) {
           return 0;
       }
       Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");

       if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
           //获取前面设置好的切换操作
       int transit = mService.mAppTransition.getAppTransition();
        //如果因为动画没有成功设置好,或者因为冻屏等原因,导致的WMS中mSkipAppTransitionAnimation为true的话,切换操作类型设置为TRANSIT_UNSET
       if (mService.mSkipAppTransitionAnimation) {
           transit = AppTransition.TRANSIT_UNSET;
       }
       mService.mSkipAppTransitionAnimation = false;
       mService.mNoAnimationNotifyOnTransitionFinished.clear();
      //这个时候可以移除prepareAppTransition中设置的超时消息
       mService.mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
      //重新进行窗口的排序,防止乱序
       mService.rebuildAppWindowListLocked();

       mWallpaperMayChange = false;

       // The top-most window will supply the layout params,
       // and we will determine it below.
       //用来保存窗口参数
       LayoutParams animLp = null;
       int bestAnimLayer = -1;
       boolean fullscreenAnim = false;
      //是否有语音交互
       boolean voiceInteraction = false;

       int i;
       for (i = 0; i < appsCount; i++) {
           final AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
           // Clearing the mAnimatingExit flag before entering animation. It's set to
           // true if app window is removed, or window relayout to invisible.
           // This also affects window visibility. We need to clear it *before*
           // maybeUpdateTransitToWallpaper() as the transition selection depends on
           // wallpaper target visibility.
           wtoken.clearAnimatingFlags();

       }
       // Adjust wallpaper before we pull the lower/upper target, since pending changes
       // (like the clearAnimatingFlags() above) might affect wallpaper target result.
       final DisplayContent displayContent = mService.getDefaultDisplayContentLocked();
       if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
               mWallpaperControllerLocked.adjustWallpaperWindows()) {
            //上面执行了clearAnimatingFlags,会影响Z-order.这里重新调整一下
           mService.mLayersController.assignLayersLocked(windows);
           displayContent.layoutNeeded = true;
       }

        //在调整壁纸窗口在窗口堆栈的位置的时候,如果碰到系统在执行两个Activity组件的切换操作,
       //并且这两个Activity组件都需要显示壁纸,
       //那么Z轴位置较低的窗口就会lowerWallpaperTarget中,
       //而Z轴位置较高的窗口就会保存在upperWallpaperTarget中。

       final WindowState lowerWallpaperTarget =
               mWallpaperControllerLocked.getLowerWallpaperTarget();
       final WindowState upperWallpaperTarget =
               mWallpaperControllerLocked.getUpperWallpaperTarget();

       boolean openingAppHasWallpaper = false;
       boolean closingAppHasWallpaper = false;
       final AppWindowToken lowerWallpaperAppToken;
       final AppWindowToken upperWallpaperAppToken;
       if (lowerWallpaperTarget == null) {
           lowerWallpaperAppToken = upperWallpaperAppToken = null;
       } else {
           lowerWallpaperAppToken = lowerWallpaperTarget.mAppToken;
           upperWallpaperAppToken = upperWallpaperTarget.mAppToken;
       }

       // Do a first pass through the tokens for two
       // things:
       // (1) Determine if both the closing and opening
       // app token sets are wallpaper targets, in which
       // case special animations are needed
       // (since the wallpaper needs to stay static
       // behind them).
       // (2) Find the layout params of the top-most
       // application window in the tokens, which is
       // what will control the animation theme.
          //获取关闭的activiy数量
       final int closingAppsCount = mService.mClosingApps.size();
          //获取打开的activiy数量
       appsCount = closingAppsCount + mService.mOpeningApps.size();
         //这段代码的for循环就是要从参与切换操作的Activity组件的窗口的WindowManager.LayoutParams对象中挑选出一个来创建切换动画
        //要求是主窗口,它是所有候选窗口中Z轴位置最高的
       for (i = 0; i < appsCount; i++) {
           final AppWindowToken wtoken;
           if (i < closingAppsCount) {
               wtoken = mService.mClosingApps.valueAt(i);
               if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
           //Activity关闭的时候,要显示墙纸窗口
                   closingAppHasWallpaper = true;
               }
           } else {
               wtoken = mService.mOpeningApps.valueAt(i - closingAppsCount);
               if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
                //Activity打开的时候,要显示墙纸窗口
                   openingAppHasWallpaper = true;
               }
           }

           voiceInteraction |= wtoken.voiceInteraction;
             //是否是全屏
           if (wtoken.appFullscreen) {
            //找到主窗口,类型为TYPE_BASE_APPLICATION或者TYPE_APPLICATION_STARTING类型的
               WindowState ws = wtoken.findMainWindow();
               if (ws != null) {
                   animLp = ws.mAttrs;
                   bestAnimLayer = ws.mLayer;
                   fullscreenAnim = true;
               }
           } else if (!fullscreenAnim) {
               WindowState ws = wtoken.findMainWindow();
               if (ws != null) {
                   if (ws.mLayer > bestAnimLayer) {
                       animLp = ws.mAttrs;
                       bestAnimLayer = ws.mLayer;
                   }
               }
           }
       }

        //判断切换操作跟墙纸类型是否相关,调整窗口的类型(mayUpdateTransitionToWallpaper)
       transit = maybeUpdateTransitToWallpaper(transit, openingAppHasWallpaper,
               closingAppHasWallpaper, lowerWallpaperTarget, upperWallpaperTarget);

       // If all closing windows are obscured, then there is
       // no need to do an animation.  This is the case, for
       // example, when this transition is being done behind
       // the lock screen.
       if (!mService.mPolicy.allowAppAnimationsLw()) {
           if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
                   "Animations disallowed by keyguard or dream.");
           animLp = null;
       }

       processApplicationsAnimatingInPlace(transit);

       mTmpLayerAndToken.token = null;
       // MIUI ADD:
       mService.mAppTransition.updateAllowCustomAnimationIfNeeded(mService.mClosingApps);
      //处理关闭的Activity
       handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
       final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
       final int topClosingLayer = mTmpLayerAndToken.layer;
        //处理打开的Activity,下面会说
       final AppWindowToken topOpeningApp = handleOpeningApps(transit,
               animLp, voiceInteraction, topClosingLayer);

       mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);

       final AppWindowAnimator openingAppAnimator = (topOpeningApp == null) ?  null :
               topOpeningApp.mAppAnimator;
       final AppWindowAnimator closingAppAnimator = (topClosingApp == null) ? null :
               topClosingApp.mAppAnimator;

       mService.mAppTransition.goodToGo(openingAppAnimator, closingAppAnimator,
               mService.mOpeningApps, mService.mClosingApps);
       mService.mAppTransition.postAnimationCallback();
       mService.mAppTransition.clear();

       mService.mOpeningApps.clear();
       mService.mClosingApps.clear();

       .....

       return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_CONFIG;
   }

总体来说大致有八个步骤

  • 1、调用条件:首先判断是否超时,超时了不执行,判断mOpeningApps中每一个AppWindowToken的allDrawn值是否为true
  • 2、判断墙纸是否需要可见,如果需要,先绘制墙纸,在走切换动画逻辑
  • 3、取出mAppTransition的切换操作,移除超时消息
  • 4、窗口堆栈顺序重排,rebuildAppWindowListLocked
  • 5、取得顶层全屏窗口的mAttr值(LayoutParams),记录在animLp
  • 6、判断切换操作跟墙纸类型是否相关,调整类型(mayUpdateTransitionToWallpaper)
  • 7、分别处理handleClosingApps/handleOpeningApps
  • 8、清理工作

第十三步handleOpeningApps这个函数用来设置APPWindowToken.hidden的可见性、设置Activity切换动画(如果参数transit==AppTransition.TRANSIT_UNSET,那就是会设置窗口动画,否则就会设置Activity切换动画),如果存在Activity切换动画或属于该Activity的窗口正在做窗口动画,那么返回值为true,handleOpeningApps中调用了setTokenVisibilityLocked方法。

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
           boolean visible, int transit, boolean performLayout, boolean isVoiceInteraction) {
       boolean delayed = false;

       if (wtoken.clientHidden == visible) {
           wtoken.clientHidden = !visible;
            //再次通知应用程序端设置窗口可见性
           wtoken.sendAppVisibilityToClients();
       }
       boolean visibilityChanged = false;
       if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
               (visible && wtoken.waitingForReplacement())) {
           boolean changed = false;
           boolean runningAppAnimation = false;
           if (transit != AppTransition.TRANSIT_UNSET) {
               if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
                   //把前面的哑动画清空
                   wtoken.mAppAnimator.setNullAnimation();
               }
               //创建动画
               if (applyAnimationLocked(wtoken, lp, transit, visible, isVoiceInteraction)) {
                   delayed = runningAppAnimation = true;
               }
               WindowState window = wtoken.findMainWindow();
               if (window != null && mAccessibilityController != null
                       && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
                   mAccessibilityController.onAppWindowTransitionLocked(window, transit);
               }
               changed = true;
           }
        .......
     }
     .......
       return delayed;
   }

创建动画是applyAnimationLocked方法干的事情。

frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
           int transit, boolean enter, boolean isVoiceInteraction) {
       // 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.
       Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WM#applyAnimationLocked");
       if (okToDisplay()) {
           ....
           //传入各种参数,用AppTransition的loadAnimation创建一个动画
           Animation a = mAppTransition.loadAnimation(lp, transit, enter, mCurConfiguration.uiMode,
                   mCurConfiguration.orientation, frame, displayFrame, insets, surfaceInsets,
                   isVoiceInteraction, freeform, atoken.mTask.mTaskId, mIsInMultiWindowMode);
          ....
           if (a != null) {
                  ....
               atoken.mAppAnimator.setAnimation(a, containingWidth, containingHeight,
                       mAppTransition.canSkipFirstFrame(), mAppTransition.getAppStackClipMode());
               ....
           }
       } else {
           atoken.mAppAnimator.clearAnimation();
       }
       return atoken.mAppAnimator.animation != null;
   }

applyAnimationLocked内部实质上还是调用loadAnimation。

frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, int uiMode,
           int orientation, Rect frame, Rect displayFrame, Rect insets,
           @Nullable Rect surfaceInsets, boolean isVoiceInteraction, boolean freeform,
           int taskId, boolean isInMultiWindowMode) {

       if (transit == TRANSIT_WALLPAPER_OPEN && mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM
               && !mAllowCustomAnimation) {
           mNextAppTransitionType = AppTransitionInjector.NEXT_TRANSIT_TYPE_BACK_HOME;
       }
       Animation a;
       if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_OPEN
               || transit == TRANSIT_TASK_OPEN
               || transit == TRANSIT_TASK_TO_FRONT)) {
           a = loadAnimationRes(lp, enter
                   ? com.android.internal.R.anim.voice_activity_open_enter
                   : com.android.internal.R.anim.voice_activity_open_exit);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation voice:"
                   + " anim=" + a + " transit=" + appTransitionToString(transit)
                   + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
       } else if (isVoiceInteraction && (transit == TRANSIT_ACTIVITY_CLOSE
               || transit == TRANSIT_TASK_CLOSE
               || transit == TRANSIT_TASK_TO_BACK)) {
           a = loadAnimationRes(lp, enter
                   ? com.android.internal.R.anim.voice_activity_close_enter
                   : com.android.internal.R.anim.voice_activity_close_exit);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation voice:"
                   + " anim=" + a + " transit=" + appTransitionToString(transit)
                   + " isEntrance=" + enter + " Callers=" + Debug.getCallers(3));
       } else if (transit == TRANSIT_ACTIVITY_RELAUNCH) {
           a = createRelaunchAnimation(frame, insets);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation:"
                   + " anim=" + a + " nextAppTransition=" + mNextAppTransition
                   + " transit=" + appTransitionToString(transit)
                   + " Callers=" + Debug.getCallers(3));
       } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
           a = loadAnimationRes(mNextAppTransitionPackage, enter ?
                   mNextAppTransitionEnter : mNextAppTransitionExit);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation:"
                   + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
                   + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                   + " Callers=" + Debug.getCallers(3));
       } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM_IN_PLACE) {
           a = loadAnimationRes(mNextAppTransitionPackage, mNextAppTransitionInPlace);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation:"
                   + " anim=" + a + " nextAppTransition=ANIM_CUSTOM_IN_PLACE"
                   + " transit=" + appTransitionToString(transit)
                   + " Callers=" + Debug.getCallers(3));
       } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL) {
           a = createClipRevealAnimationLocked(transit, enter, frame, displayFrame);
           mLauncherAnimationRect.setEmpty();
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation:"
                           + " anim=" + a + " nextAppTransition=ANIM_CLIP_REVEAL"
                           + " transit=" + appTransitionToString(transit)
                           + " Callers=" + Debug.getCallers(3));
       } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
           a = createScaleUpAnimationLocked(transit, enter, frame);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation:"
                   + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
                   + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                   + " Callers=" + Debug.getCallers(3));
       } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
               mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
           mNextAppTransitionScaleUp =
                   (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
           a = createThumbnailEnterExitAnimationLocked(getThumbnailTransitionState(enter),
                   frame, transit, taskId);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
               String animName = mNextAppTransitionScaleUp ?
                       "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
               Slog.v(TAG, "applyAnimation:"
                       + " anim=" + a + " nextAppTransition=" + animName
                       + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                       + " Callers=" + Debug.getCallers(3));
           }
       } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP ||
               mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN) {
           mNextAppTransitionScaleUp =
                   (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_UP);
           a = createAspectScaledThumbnailEnterExitAnimationLocked(
                   getThumbnailTransitionState(enter), uiMode, orientation, transit, frame,
                   insets, surfaceInsets, freeform, taskId);
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
               String animName = mNextAppTransitionScaleUp ?
                       "ANIM_THUMBNAIL_ASPECT_SCALE_UP" : "ANIM_THUMBNAIL_ASPECT_SCALE_DOWN";
               Slog.v(TAG, "applyAnimation:"
                       + " anim=" + a + " nextAppTransition=" + animName
                       + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                       + " Callers=" + Debug.getCallers(3));
           }
       } else {
           int animAttr = 0;
           switch (transit) {
               case TRANSIT_ACTIVITY_OPEN:
                   animAttr = enter
                           ? WindowAnimation_activityOpenEnterAnimation
                           : WindowAnimation_activityOpenExitAnimation;
                   break;
               case TRANSIT_ACTIVITY_CLOSE:
                   animAttr = enter
                           ? WindowAnimation_activityCloseEnterAnimation
                           : WindowAnimation_activityCloseExitAnimation;
                   break;
               case TRANSIT_DOCK_TASK_FROM_RECENTS:
               case TRANSIT_TASK_OPEN:
                   animAttr = enter
                           ? WindowAnimation_taskOpenEnterAnimation
                           : WindowAnimation_taskOpenExitAnimation;
                   break;
               case TRANSIT_TASK_CLOSE:
                   animAttr = enter
                           ? WindowAnimation_taskCloseEnterAnimation
                           : WindowAnimation_taskCloseExitAnimation;
                   break;
               case TRANSIT_TASK_TO_FRONT:
                   animAttr = enter
                           ? WindowAnimation_taskToFrontEnterAnimation
                           : WindowAnimation_taskToFrontExitAnimation;
                   break;
               case TRANSIT_TASK_TO_BACK:
                   animAttr = enter
                           ? WindowAnimation_taskToBackEnterAnimation
                           : WindowAnimation_taskToBackExitAnimation;
                   break;
               case TRANSIT_WALLPAPER_OPEN:
                   animAttr = enter
                           ? WindowAnimation_wallpaperOpenEnterAnimation
                           : WindowAnimation_wallpaperOpenExitAnimation;
                   break;
               case TRANSIT_WALLPAPER_CLOSE:
                   animAttr = enter
                           ? WindowAnimation_wallpaperCloseEnterAnimation
                           : WindowAnimation_wallpaperCloseExitAnimation;
                   break;
               case TRANSIT_WALLPAPER_INTRA_OPEN:
                   animAttr = enter
                           ? WindowAnimation_wallpaperIntraOpenEnterAnimation
                           : WindowAnimation_wallpaperIntraOpenExitAnimation;
                   break;
               case TRANSIT_WALLPAPER_INTRA_CLOSE:
                   animAttr = enter
                           ? WindowAnimation_wallpaperIntraCloseEnterAnimation
                           : WindowAnimation_wallpaperIntraCloseExitAnimation;
                   break;
               case TRANSIT_TASK_OPEN_BEHIND:
                   animAttr = enter
                           ? WindowAnimation_launchTaskBehindSourceAnimation
                           : WindowAnimation_launchTaskBehindTargetAnimation;
           }
           a = animAttr != 0 ? loadAnimationAttr(lp, animAttr) : null;
           if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation:"
                   + " anim=" + a
                   + " animAttr=0x" + Integer.toHexString(animAttr)
                   + " transit=" + appTransitionToString(transit) + " isEntrance=" + enter
                   + " Callers=" + Debug.getCallers(3));
       }
       return a;
   }

关于loadAnimation这个方法代码也是很多,但是逻辑非常简单了,就是根据设置的操作类型, 根据参数,使用loadAnimationRes()或loadAnimationAttr()或其他创建Animation接口来加载一个Animation出来。加载动画的时候需要注意一个优先级的问题。

  • 如果开启语音交互,则根据mNextAppTransition类型返回对应动画,由AMS设置的动画类型(由Activity发生的行为决定)如TRANSIT_ACTIVITY_OPEN表示当前发生了Activity打开的行为
  • 如果设置了某种mNextAppTransitionType类型,则根据此类型返回对应动画。:由客户端进程设置的动画类型(由客户端决定) 如Activity#overridePendingTransition,在决定要为当前窗口设置何种动画时,此类型的优先级高于第一种。
  • 根据mNextAppTransition类型返回对应动画。
  • 没有任何条件满足,返回空

比如Activity关闭的时候,加载的动画资源是下面这样

<set xmlns:android="http://schemas.android.com/apk/res/android" android:zAdjustment="normal">
   <alpha android:fromAlpha="0.7" android:toAlpha="1.0"
           android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
           android:interpolator="@interpolator/linear_out_slow_in"
           android:duration="250"/>
</set>

动画设置好了之后,就会通过setAnimation方法将动画anim保存在AppWindowAnimator的成员变量animation中,动画的执行时候,就会来取这个animation。

frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java
public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame,
           int stackClip) {
       animation = anim;
       animating = false;
       if (!anim.isInitialized()) {
           anim.initialize(width, height, width, height);
       }
     ....
   }

刚刚在说动画的优先级的时候,说过如果设置了某种mNextAppTransitionType类型,就跟以这个mNextAppTransitionType类型作为返回,优先级高于第一种。这个过程的时序图如下。


过度动画
frameworks/base/services/core/java/com/android/server/wm/AppTransition.java

 void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
           IRemoteCallback startedCallback) {
       if (isTransitionSet()) {
           clear();
           mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
           mNextAppTransitionPackage = packageName;
           mNextAppTransitionEnter = enterAnim;
           mNextAppTransitionExit = exitAnim;
           postAnimationCallback();
           mNextAppTransitionCallback = startedCallback;
       } else {
           postAnimationCallback();
       }
   }

mNextAppTransitionType被覆盖了之后,创建动画的时候就会优先返回设置了这种类型的动画。

3、窗口动画设置
image.png

相对与过度动画,窗口动画的设置过程会简单一些,从commitFinishDrawingLocked方法说起,commitFinishDrawingLocked是也是从performSurfacePlacementInner里面调用而来的。

   boolean commitFinishDrawingLocked() {
        .....
       mDrawState = READY_TO_SHOW;
       boolean result = false;
       final AppWindowToken atoken = mWin.mAppToken;
       //去取出atoken,如果atoken等于null,那么说明不是Activity窗口,就可以调用performShowLocked,进行窗口动画的设置
       if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
           result = performShowLocked();
       }
       return result;
   }

performShowLocked中主要是调用了applyEnterAnimationLocked方法进行创建动画。

   void applyEnterAnimationLocked() {
       // If we are the new part of a window replacement transition and we have requested
       // not to animate, we instead want to make it seamless, so we don't want to apply
       // an enter transition.
       if (mWin.mSkipEnterAnimationForSeamlessReplacement) {
           return;
       }
       final int transit;
       if (mEnterAnimationPending) {
           mEnterAnimationPending = false;
           transit = WindowManagerPolicy.TRANSIT_ENTER;
       } else {
           transit = WindowManagerPolicy.TRANSIT_SHOW;
       }
       applyAnimationLocked(transit, true);
     .....
   }

mEnterAnimationPending的值等于true,说明当前所描述的窗口正在等待显示,也就是正处于不可见到可见状态的过程中,那WindowManagerService类的成员函数applyEnterAnimationLocked就会对该窗口设置一个类型为WindowManagerPolicy.TRANSIT_ENTER的动画,否则的话,就会对该窗口设置一个类型为WindowManagerPolicy.TRANSIT_SHOW的动画。后面会根据这个类型,确定styleable, 将参数transit的值转化为一个对应的动画样式名称。

frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
boolean applyAnimationLocked(int transit, boolean isEntrance) {
       ......
       if (mService.okToDisplay()) {
           int anim = mPolicy.selectAnimationLw(mWin, transit);
           int attr = -1;
           Animation a = null;
           if (anim != 0) {
               a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
           } else {
               switch (transit) {
                   case WindowManagerPolicy.TRANSIT_ENTER:
                       attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                       break;
                   case WindowManagerPolicy.TRANSIT_EXIT:
                       attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                       break;
                   case WindowManagerPolicy.TRANSIT_SHOW:
                       attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                       break;
                   case WindowManagerPolicy.TRANSIT_HIDE:
                       attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                       break;
               }
               if (attr >= 0) {
                   a = mService.mAppTransition.loadAnimationAttr(mWin.mAttrs, attr);
               }
           }
           if (DEBUG_ANIM) Slog.v(TAG,
                   "applyAnimation: win=" + this
                   + " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
                   + " a=" + a
                   + " transit=" + transit
                   + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
           if (a != null) {
               if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
               setAnimation(a);
               mAnimationIsEntrance = isEntrance;
           }
       } else {
           clearAnimation();
       }
       Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);

       if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
           mService.adjustForImeIfNeeded(mWin.mDisplayContent);
           if (isEntrance) {
               mWin.setDisplayLayoutNeeded();
               mService.mWindowPlacerLocked.requestTraversal();
           }
       }
       return mAnimation != null;
   }

首先会调用PhoneWindowManager的selectAnimationLw方法去查找特殊窗口的动画类型,这里特殊窗口主要是StatusBar、NavigationBar或者窗口的类型是TYPE_DOCK_DIVIDER(分屏)等,如果是这些窗口的话,就会直接返回一个动画类型(transit)保存在anim中,接下来会判断anim是否为-1,因为selectAnimationLw在窗口是Keyguard或者DREAM类型的时候会返回-1,如果不是-1,说明查找到了,返回到WindowStateAnimator中使用AnimationUtils的loadAnimation方法去查找出一个动画a保存在Animation所描述的变量a中。

如果上面都不是,那么就根据transit类型,确定attr,调用AppTransition的loadAnimationAttr方法加载一个动画

frameworks/base/services/core/java/com/android/server/wm/AppTransition.java
   Animation loadAnimationAttr(WindowManager.LayoutParams lp, int animAttr) {
       int anim = 0;
       Context context = mContext;
       if (animAttr >= 0) {
           AttributeCache.Entry ent = getCachedAnimations(lp);
           if (ent != null) {
               context = ent.context;
               anim = ent.array.getResourceId(animAttr, 0);
           }
       }
       if (anim != 0) {
           return AnimationUtils.loadAnimation(context, anim);
       }
       return null;
   }
frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
 public void setAnimation(Animation anim, long startTime, int stackClip) {
       if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);
       mAnimating = false;
       mLocalAnimating = false;
       mAnimation = anim;
       ...
   }

最后动画被保存在WindowStateAnimator的成员变量mAnimation中。对比前面的过度动画,最后是
通过setAnimation方法将动画anim保存在AppWindowAnimator的成员变量animation中。当动画的执行时候,就会来取这个animation,动画的执行,放在接下来一节中更新。

相关文章

网友评论

  • CharlesCT:大神 求教怎样hook TextView的setText方法
    LooperJing:@落月孤灯 参考http://www.jianshu.com/p/dfc6e3989511,找一下hook点吧
  • Android之路:看的我好晕啊~大兄弟:mask:
    LooperJing:@Android之路 多看点就好多了

本文标题:Android窗口系统第四篇---Activity动画的设置过程

本文链接:https://www.haomeiwen.com/subject/kkfjmxtx.html