个人网站一键生成,做视频网站都需要什么软件下载,济南设计公司排名,建筑工程网人才网前言
接着上篇文章分析 Android T 远程动画显示流程其一
切入点——处理应用的显示过渡
下面#xff0c;我们以从桌面点击一个应用启动的场景来分析远程动画的流程#xff0c;窗口添加的流程见Android T WMS窗口相关流程 这里我们从AppTransitionController.handleAppTran…前言
接着上篇文章分析 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 ArraySetInteger 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下的所有窗口进行刷新。 这里我们主要关注远程动画的流程主要分为两个部分。
处理并创建远程动画流程 applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);播放显示远程动画流程 layoutRedo appTransition.goodToGo(transit, topOpeningApp);
动画创建流程代码分析
applyAnimations(mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps, transit, animLp, voiceInteraction);
基于一组ActivityRecord来应用动画这些ActivityRecord表示正在进行切换的应用。 mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps这两个参数分别代表正在打开和关闭的应用 transit通过前面getTransitCompatType方法中获取是TRANSIT_OLD_WALLPAPER_CLOSE12 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(ArraySetActivityRecord openingApps,ArraySetActivityRecord closingApps, TransitionOldType int transit,LayoutParams animLp, boolean voiceInteraction) {//方法检查过渡类型是否未设置或者打开和关闭的应用程序是否都为空。如果是则方法直接返回不执行任何动画。if (transit WindowManager.TRANSIT_OLD_UNSET|| (openingApps.isEmpty() closingApps.isEmpty())) {return;}//调用getAnimationTargets方法获取打开和关闭的应用的窗口容器WindowContainerfinal ArraySetWindowContainer openingWcs getAnimationTargets(openingApps, closingApps, true /* visible */);final ArraySetWindowContainer 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);我们这里openingWcs和closingWcs实际上表示的是应用的容器即TaskopeningApps 和 closingApps就是前面传递的mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps分别代表正在打开和关闭的应用也是挂在对应Task下面的ActivityRecord。并且传递了应用的可见性visibletrue可见false不可见。 因此在我们桌面点击打开应用的流程中openingWcs实际上指的是应用的TaskopeningApps是应用的ActivityRecord其实就是应用的主界面其可见性为trueclosingWcs对应的是桌面的TaskclosingApps是桌面的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(ArraySetWindowContainer wcs, ArraySetActivityRecord 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 ArrayListActivityRecord 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是前面传递过来的是Taskwc就是依次获取当前应用的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 ArrayListWindowContainer 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 isnt 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 typebool nameconfig_disableTransitionAnimation /定义了这个symbol 代码路径frameworks/base/core/res/res/values/config.xml !-- Flag to disable all transition animations --bool nameconfig_disableTransitionAnimationfalse/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 ArrayListWindowContainer sources) {//获取RecentsAnimationController//只有在最近任务中切换到另一个应用时才会创建final RecentsAnimationController control mWmService.getRecentsAnimationController();//RecentsAnimationController不为空if (control ! null) {// We let the transition to be controlled by RecentsAnimation, and callback tasks// 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 ArrayListWindowContainer 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 PairAnimationAdapter, 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 PairAnimationAdapter, 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*/PairAnimationAdapter, AnimationAdapter getAnimationAdapter(WindowManager.LayoutParams lp,TransitionOldType int transit, boolean enter, boolean isVoiceInteraction) {final PairAnimationAdapter, 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值为12TRANSIT_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 isnt 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 wont 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 ListRunnable mOnAnimationFinished new LinkedList();/*** Runs when the animation is cancelled but the surface is still animating*/private final ListRunnable 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 doesnt 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。 animationFinishedCallback和animationCancelledCallback这两个是回调函数分别在动画完成和动画取消时被调用。 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 WindowContainerE extends WindowContainer extends ConfigurationContainerEimplements ComparableWindowContainer, 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, SupplierTransaction 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// doesnt 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然后保存传入的leash到mAnimationLeash后面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_CLOSE12 参数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.mHandler.postDelayed(mTimeoutRunnable,(long) (TIMEOUT_MS * mService.getCurrentAnimatorScale()));mFinishedCallback new FinishedCallback(this);// Create the app targetsfinal RemoteAnimationTarget[] appTargets createAppAnimations();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// dont cancel remote animation.ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS,goodToGo(): No apps to animate, mPendingAnimations%d,mPendingAnimations.size());onAnimationFinished();invokeAnimationCancelled(no_app_targets);return;}if (mOnRemoteAnimationReady ! null) {mOnRemoteAnimationReady.run();mOnRemoteAnimationReady null;}// Create the remote wallpaper animation targets (if any)final RemoteAnimationTarget[] wallpaperTargets createWallpaperAnimations();// Create the remote non app animation targets (if any)final RemoteAnimationTarget[] nonAppTargets createNonAppWindowAnimations(transit);mService.mAnimator.addAfterPrepareSurfacesRunnable(() - {try {linkToDeathOfRunner();Slog.i(RemoteAnimationController,goodToGo mRemoteAnimationAdapter.getRunner():mRemoteAnimationAdapter.getRunner());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);}动画移除流程
处理和响应动画完成的逻辑
代码路径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是保存的是需要做动画的ActivityRecordmSurfaceAnimationSources的值是在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 ArrayListWindowState 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 didnt 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.morning.tmfhx.cn.gov.cn.tmfhx.cn http://www.morning.fkrzx.cn.gov.cn.fkrzx.cn http://www.morning.c-ae.cn.gov.cn.c-ae.cn http://www.morning.mzwfw.cn.gov.cn.mzwfw.cn http://www.morning.hbtarq.com.gov.cn.hbtarq.com http://www.morning.gfrjs.cn.gov.cn.gfrjs.cn http://www.morning.cgtfl.cn.gov.cn.cgtfl.cn http://www.morning.fpzpb.cn.gov.cn.fpzpb.cn http://www.morning.wttzp.cn.gov.cn.wttzp.cn http://www.morning.yqqgp.cn.gov.cn.yqqgp.cn http://www.morning.hxxyp.cn.gov.cn.hxxyp.cn http://www.morning.bzfwn.cn.gov.cn.bzfwn.cn http://www.morning.yxwnn.cn.gov.cn.yxwnn.cn http://www.morning.zfcfk.cn.gov.cn.zfcfk.cn http://www.morning.glpxx.cn.gov.cn.glpxx.cn http://www.morning.tnktt.cn.gov.cn.tnktt.cn http://www.morning.gkktj.cn.gov.cn.gkktj.cn http://www.morning.jbblf.cn.gov.cn.jbblf.cn http://www.morning.lmcrc.cn.gov.cn.lmcrc.cn http://www.morning.rqgjr.cn.gov.cn.rqgjr.cn http://www.morning.wqkzf.cn.gov.cn.wqkzf.cn http://www.morning.nzkkh.cn.gov.cn.nzkkh.cn http://www.morning.lmmyl.cn.gov.cn.lmmyl.cn http://www.morning.tsqrc.cn.gov.cn.tsqrc.cn http://www.morning.bpzw.cn.gov.cn.bpzw.cn http://www.morning.nbwyk.cn.gov.cn.nbwyk.cn http://www.morning.snmsq.cn.gov.cn.snmsq.cn http://www.morning.fmkbk.cn.gov.cn.fmkbk.cn http://www.morning.cmdfh.cn.gov.cn.cmdfh.cn http://www.morning.hxmqb.cn.gov.cn.hxmqb.cn http://www.morning.bpmnz.cn.gov.cn.bpmnz.cn http://www.morning.pccqr.cn.gov.cn.pccqr.cn http://www.morning.mkydt.cn.gov.cn.mkydt.cn http://www.morning.wchsx.cn.gov.cn.wchsx.cn http://www.morning.nkjjp.cn.gov.cn.nkjjp.cn http://www.morning.rhfh.cn.gov.cn.rhfh.cn http://www.morning.ttdbr.cn.gov.cn.ttdbr.cn http://www.morning.hnmbq.cn.gov.cn.hnmbq.cn http://www.morning.mdjzydr.com.gov.cn.mdjzydr.com http://www.morning.tqsmg.cn.gov.cn.tqsmg.cn http://www.morning.xtxp.cn.gov.cn.xtxp.cn http://www.morning.gmgnp.cn.gov.cn.gmgnp.cn http://www.morning.nbfkk.cn.gov.cn.nbfkk.cn http://www.morning.ygbq.cn.gov.cn.ygbq.cn http://www.morning.mxftp.com.gov.cn.mxftp.com http://www.morning.hkpyp.cn.gov.cn.hkpyp.cn http://www.morning.shsh1688.com.gov.cn.shsh1688.com http://www.morning.gctgc.cn.gov.cn.gctgc.cn http://www.morning.tmpsc.cn.gov.cn.tmpsc.cn http://www.morning.prls.cn.gov.cn.prls.cn http://www.morning.pftjj.cn.gov.cn.pftjj.cn http://www.morning.mlzyx.cn.gov.cn.mlzyx.cn http://www.morning.lhjmq.cn.gov.cn.lhjmq.cn http://www.morning.gqfks.cn.gov.cn.gqfks.cn http://www.morning.mbmtn.cn.gov.cn.mbmtn.cn http://www.morning.yrbqy.cn.gov.cn.yrbqy.cn http://www.morning.wdpt.cn.gov.cn.wdpt.cn http://www.morning.rui931.cn.gov.cn.rui931.cn http://www.morning.qwmsq.cn.gov.cn.qwmsq.cn http://www.morning.sfdsn.cn.gov.cn.sfdsn.cn http://www.morning.mnwsy.cn.gov.cn.mnwsy.cn http://www.morning.xhqwm.cn.gov.cn.xhqwm.cn http://www.morning.hhboyus.cn.gov.cn.hhboyus.cn http://www.morning.rui931.cn.gov.cn.rui931.cn http://www.morning.kntsd.cn.gov.cn.kntsd.cn http://www.morning.srltq.cn.gov.cn.srltq.cn http://www.morning.haolipu.com.gov.cn.haolipu.com http://www.morning.bzbq.cn.gov.cn.bzbq.cn http://www.morning.qcslh.cn.gov.cn.qcslh.cn http://www.morning.rnzgf.cn.gov.cn.rnzgf.cn http://www.morning.nstml.cn.gov.cn.nstml.cn http://www.morning.chxsn.cn.gov.cn.chxsn.cn http://www.morning.pyxtn.cn.gov.cn.pyxtn.cn http://www.morning.tqsgt.cn.gov.cn.tqsgt.cn http://www.morning.hilmwmu.cn.gov.cn.hilmwmu.cn http://www.morning.yuanshenglan.com.gov.cn.yuanshenglan.com http://www.morning.fnbtn.cn.gov.cn.fnbtn.cn http://www.morning.ghxtk.cn.gov.cn.ghxtk.cn http://www.morning.qsy37.cn.gov.cn.qsy37.cn http://www.morning.wwxg.cn.gov.cn.wwxg.cn