Android6.0 WMS(八) 显示Activity的启动窗口
在Android系统中,Activity组件在启动之后,并且在它的窗口显示出来之前,可以显示一个启动窗口。这个启动窗口可以看作是Activity组件的预览窗口,是由WindowManagerService服务统一管理的,即由WindowManagerService服务负责启动和结束。在本文中,我们就详细分析WindowManagerService服务启动和结束Activity组件的启动窗口的过程。
Activity组件的启动窗口是由ActivityManagerService服务来决定是否要显示的。如果需要显示,那么ActivityManagerService服务就会通知WindowManagerService服务来为正在启动的Activity组件显示一个启动窗口,而WindowManagerService服务又是通过窗口管理策略类PhoneWindowManager来创建这个启动窗口的。
注意,Activity组件的启动窗口是由ActivityManagerService服务来控制是否显示的,也就是说,Android应用程序是无法决定是否要要Activity组件显示启动窗口的。接下来,我们就分别分析Activity组件的启动窗口的显示和结束过程。
一. Activity组件的启动窗口的显示过程
从前面Android应用程序启动过程源代码分析一文可以知道,Activity组件在启动的过程中,会调用ActivityStack类的成员函数startActivityLocked。注意,在调用ActivityStack类的成员函数startActivityLocked的时候,Actvitiy组件还处于启动的过程,即它的窗口尚未显示出来,不过这时候ActivityManagerService服务会检查是否需要为正在启动的Activity组件显示一个启动窗口。如果需要的话,那么ActivityManagerService服务就会请求WindowManagerService服务为正在启动的Activity组件设置一个启动窗口。这个过程如图所示。
图 Activity组件的启动窗口的显示过程
这个过程可以分为6个步骤,接下来我们就详细分析每一个步骤。
1.1 ActivityStack的startActivityLocked
final void startActivityLocked(ActivityRecord r, boolean newTask,boolean doResume, boolean keepCurTransition, Bundle options) {......if (!isHomeStack() || numActivities() > 0) {// We want to show the starting preview window if we are// switching to a new task, or the next activity's process is// not currently running.boolean showStartingIcon = newTask;ProcessRecord proc = r.app;if (proc == null) {proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid);}.....if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);//设置app变化相关mNoAnimActivities.add(r);} else {mWindowManager.prepareAppTransition(newTask? r.mLaunchTaskBehind? AppTransition.TRANSIT_TASK_OPEN_BEHIND: AppTransition.TRANSIT_TASK_OPEN: AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);mNoAnimActivities.remove(r);}mWindowManager.addAppToken(task.mActivities.indexOf(r),//调用WMS增加AppTokenr.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,(r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);boolean doShow = true;if (newTask) {if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {resetTaskIfNeededLocked(r, r);doShow = topRunningNonDelayedActivityLocked(null) == r;}} else if (options != null && new ActivityOptions(options).getAnimationType()== ActivityOptions.ANIM_SCENE_TRANSITION) {doShow = false;}if (r.mLaunchTaskBehind) {mWindowManager.setAppVisibility(r.appToken, true);ensureActivitiesVisibleLocked(null, 0);} else if (SHOW_APP_STARTING_PREVIEW && doShow) {ActivityRecord prev = mResumedActivity;if (prev != null) {// We don't want to reuse the previous starting preview if:// (1) The current activity is in a different task.if (prev.task != r.task) {prev = null;}// (2) The current activity is already displayed.else if (prev.nowVisible) {prev = null;}}mWindowManager.setAppStartingWindow(r.appToken, r.packageName, r.theme,mService.compatibilityInfoForPackageLocked(r.info.applicationInfo), r.nonLocalizedLabel,r.labelRes, r.icon, r.logo, r.windowFlags,prev != null ? prev.appToken : null, showStartingIcon);r.mStartingWindowShown = true;}} else {mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,(r.info.flags & ActivityInfo.FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId,r.info.configChanges, task.voiceSession != null, r.mLaunchTaskBehind);ActivityOptions.abort(options);options = null;}......if (doResume) {mStackSupervisor.resumeTopActivitiesLocked(this, r, options);}}
上面这个函数先调用了WMS的addAppToken函数增加一个AppToken。
ActivityStack类的静态成员变量SHOW_APP_STARTING_PREVIEW是用描述系统是否可以为正在启动的Activity组件显示启动窗口,只有在它的值等于true,以及正在启动的Activity组件的窗口接下来是要显示出来的情况下,即变量doShow的值等于true,ActivityManagerService服务才会请求WindowManagerService服务为正在启动的Activity组件设置启动窗口。
1.2 WindowManagerService.setAppStartingWindow
setAppStartingWindow函数比较长我们来分段解析:
AppWindowToken wtoken = findAppWindowToken(token);if (wtoken == null) {Slog.w(TAG, "Attempted to set icon of non-existing app token: " + token);return;}// If the display is frozen, we won't do anything until the// actual window is displayed so there is no reason to put in// the starting window.if (!okToDisplay()) {return;}if (wtoken.startingData != null) {return;}
参数token描述的是要设置启动窗口的Activity组件,而参数transferFrom描述的是要将启动窗口转移给Activity组件token的Activity组件。这两个Activity组件是运行在同一个任务中的,并且参数token描述的Activity组件Activity组件是正在启动的Activity组件,而参数transferFrom描述的Activity组件是系统当前激活的Activity组件。
这段代码首先调用WindowManagerService类的成员函数findAppWindowToken来获得与参数token对应的一个类型为AppWindowToken的窗口令牌wtoken。如果这个AppWindowToken对象的成员变量startingData的值不等于null,那么就说明参数token所描述的Activity组件已经设置过启动窗口了,因此,WindowManagerService类的成员函数setAppStartingWindow就不用往下处理了。
函数okToDisplay如下:
boolean okToDisplay() {return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOn();}
这段代码还会检查系统屏幕当前是否处于冻结状态,即WindowManagerService类的成员变量mDisplayFrozen的值是否等于true,或者系统屏幕当前是否处于黑屏状态,即indowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数isScreenOn的返回值是否等于false。以及mDisplayEnabled是否被允许显示。如果是处于上述三种状态的话,那么WindowManagerService类的成员函数setAppStartingWindow就不用往下处理的。因为在这几种种状态下,为token所描述的Activity组件设置的启动窗口是无法显示的。
继续分析:
if (transferFrom != null) {AppWindowToken ttoken = findAppWindowToken(transferFrom);if (ttoken != null) {WindowState startingWindow = ttoken.startingWindow;if (startingWindow != null) {//有启动窗口// In this case, the starting icon has already been displayed, so start// letting windows get shown immediately without any more transitions.mSkipAppTransitionAnimation = true;if (DEBUG_STARTING_WINDOW) Slog.v(TAG,"Moving existing starting " + startingWindow + " from " + ttoken+ " to " + wtoken);final long origId = Binder.clearCallingIdentity();// Transfer the starting window over to the new token.wtoken.startingData = ttoken.startingData;wtoken.startingView = ttoken.startingView;wtoken.startingDisplayed = ttoken.startingDisplayed;ttoken.startingDisplayed = false;wtoken.startingWindow = startingWindow;wtoken.reportedVisible = ttoken.reportedVisible;ttoken.startingData = null;ttoken.startingView = null;ttoken.startingWindow = null;ttoken.startingMoved = true;startingWindow.mToken = wtoken;startingWindow.mRootToken = wtoken;startingWindow.mAppToken = wtoken;startingWindow.getWindowList().remove(startingWindow);mWindowsChanged = true;ttoken.windows.remove(startingWindow);ttoken.allAppWindows.remove(startingWindow);addWindowToListInOrderLocked(startingWindow, true);if (ttoken.allDrawn) {wtoken.allDrawn = true;wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn;}if (ttoken.firstWindowDrawn) {wtoken.firstWindowDrawn = true;}if (!ttoken.hidden) {wtoken.hidden = false;wtoken.hiddenRequested = false;wtoken.willBeHidden = false;}if (wtoken.clientHidden != ttoken.clientHidden) {wtoken.clientHidden = ttoken.clientHidden;wtoken.sendAppVisibilityToClients();}ttoken.mAppAnimator.transferCurrentAnimation(wtoken.mAppAnimator, startingWindow.mWinAnimator);updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,true /*updateInputWindows*/);getDefaultDisplayContentLocked().layoutNeeded = true;performLayoutAndPlaceSurfacesLocked();//刷新系统Binder.restoreCallingIdentity(origId);return;//结束} else if (ttoken.startingData != null) {//没有启动窗口,但有启动数据wtoken.startingData = ttoken.startingData;ttoken.startingData = null;ttoken.startingMoved = true;Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);mH.sendMessageAtFrontOfQueue(m);return;//结束}final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator;final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator;if (tAppAnimator.thumbnail != null) {if (wAppAnimator.thumbnail != null) {wAppAnimator.thumbnail.destroy();}wAppAnimator.thumbnail = tAppAnimator.thumbnail;wAppAnimator.thumbnailX = tAppAnimator.thumbnailX;wAppAnimator.thumbnailY = tAppAnimator.thumbnailY;wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;tAppAnimator.thumbnail = null;}}}if (!createIfNeeded) {return;}if (theme != 0) {AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,com.android.internal.R.styleable.Window, mCurrentUserId);if (ent == null) {return;}final boolean windowIsTranslucentDefined = ent.array.hasValue(com.android.internal.R.styleable.Window_windowIsTranslucent);final boolean windowIsTranslucent = ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent, false);final boolean windowSwipeToDismiss = ent.array.getBoolean(com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);if (windowIsTranslucent || (!windowIsTranslucentDefined && windowSwipeToDismiss)) {return;}if (ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false)) {return;}if (ent.array.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {if (mWallpaperTarget == null) {windowFlags |= FLAG_SHOW_WALLPAPER;} else {return;}}}wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,labelRes, icon, logo, windowFlags);Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);mH.sendMessageAtFrontOfQueue(m);}
如果参数transferFrom的值不等于null,那么就需要检查它所描述的Activity组件是否设置有启动窗口。如果设置有的话,那么就需要将它的启动窗口设置为参数token所描述的Activity组件的启动窗口。
参数transferFrom所描述的Activity组件所设置的启动窗口保存在与它所对应的一个类型为AppWindowToken的窗口令牌的成员变量startingWindow或者startingData中,因此,这段代码首先调用WindowManagerService类的成员函数findAppWindowToken来获得与参数transferFrom对应的一个AppWindowToken对象ttoken。如果AppWindowToken对象ttoken的成员变量startingWindow的值不等于null,那么就说明参数transferFrom所描述的Activity组件的启动窗口已经创建出来了。另一方面,如果AppWindowToken对象ttoken的成员变量startingData的值不等于null,那么就说明用来描述参数transferFrom所描述的Activity组件的启动窗口的相关数据已经准备好了,但是这个启动窗口还未创建出来。接下来我们就分别分析这两种情况。
我们首先分析AppWindowToken对象ttoken的成员变量startingWindow的值不等于null的情况。
这时候如果WindowManagerService类的成员变量mStartingIconInTransition的值等于true,那么就说明参数transferFrom所描述的Activity组件所设置的启动窗口已经在启动的过程中了。在这种情况下,就需要跳过参数token所描述的Activity组件和参数transferFrom所描述的Activity组件的切换过程,即将WindowManagerService类的成员变量mSkipAppTransitionAnimation的值设置为true,这是因为接下来除了要将参数transferFrom所描述的Activity组件的启动窗口转移给参数token所描述的Activity组件之外,还需要将参数transferFrom所描述的Activity组件的窗口状态转移给参数token所描述的Activity组件的窗口。
将参数transferFrom所描述的Activity组件的启动窗口转移给参数token所描述的Activity组件需要执行以下几个操作:
1. 将AppWindowToken对象ttoken的成员变量startingData、startingView和startingWindow的值设置到AppWindowToken对象wtoken的对应成员变量中去,其中,成员变量startingData指向的是一个StartingData对象,它描述的是用来创建启动窗口的相关数据,成员变量startingView指向的是一个View对象,它描述的是启动窗口的顶层视图,成员变量startingWindow指向的是一个WindowState对象,它描述的就是启动窗口。
2. 将AppWindowToken对象ttoken的成员变量startingData、startingView和startingWindow的值设置为null,这是因为参数transferFrom所描述的Activity组件的启动窗口已经转移给参数token所描述的Activity组件了。
3. 将原来属于参数transferFrom所描述的Activity组件的启动窗口startingWindow的成员变量mToken、mRootToken和mAppToken的值设置为wtoken,因为这个启动窗口现在已经属于参数token所描述的Activity组件了。
将参数transferFrom所描述的Activity组件的窗口状态转移给参数token所描述的Activity组件的窗口需要执下几个操作:
1. 将启动窗口startingWindow从窗口堆栈中删除,即从WindowManagerService类的成员变量mWindows所描述的一个ArrayList中删除。
2. 将启动窗口startingWindow从属于窗口令牌ttoken的窗口列表中删除,即从AppWindowToken对象ttoken的成员变量windows和allAppWindows所描述的两个ArrayList中删除。
3. 调用WindowManagerService类的成员函数addWindowToListInOrderLocked重新将启动窗口startingWindow插入到窗口堆栈中去。注意,因为这时候启动窗口startingWindow已经被设置为参数token所描述的Activity组件了,因此,在重新将它插入到窗口堆栈中去的时候,它就会位于参数token所描述的Activity组件的窗口的上面,这一点可以参考前面Android窗口管理服务WindowManagerService对窗口的组织方式分析一文。
4. 如果AppWindowToken对象ttoken的成员变量allDrawn和firstWindowDrawn的值等于true,那么就说明与AppWindowToken对象ttoken对应的所有窗口或者第一个窗口已经绘制好了,这时候也需要分别将AppWindowToken对象wtoken的成员变量allDrawn和firstWindowDrawn的值设置为true,以便可以迫使那些与AppWindowToken对象wtoken对应的窗口接下来可以马上显示出来。
5. 如果AppWindowToken对象ttoken的成员变量hidden的值等于false,那么就说明参数transferFrom所描述的Activity组件是处于可见状态的,这时候就需要将AppWindowToken对象wtoken的成员变量hidden、hiddenRequested和willBeHidden的值也设置为false,以便表示参数token所描述的Activity组件也是处于可见状态的。
6. AppWindowToken类的成员变量clientHidden描述的是对应的Activity组件在应用程序进程这一侧的可见状态。如果AppWindowToken对象wtoken和ttoken的成员变量clientHidden的值不相等,那么就需要将AppWindowToken对象ttoken的成员变量clientHidden的值设置给AppWindowToken对象wtoken的成员变量clientHidden,并且调用AppWindowToken对象wtoken的成员函数sendAppVisibilityToClients来通知相应的应用程序进程,运行在它里面的参数token所描述的Activity组件的可见状态。
7. 如果AppWindowToken对象ttoken的成员变量animation的值不等于null,那么就说明参数transferFrom所描述的Activity组件的窗口正在显示动画,那么就需要将该动画转移给参数token所描述的Activity组件的窗口,即将AppWindowToken对象ttoken的成员变量animation、animating和animLayerAdjustment的值设置到AppWindowToken对象wtoken的对应成员变量,并且将AppWindowToken对象ttoken的成员变量animation和animLayerAdjustment的值设置为null和0。最后还需要重新计算与AppWindowToken对象ttoken和wtoken所对应的窗口的Z轴位置。
8. 由于前面的操作导致窗口堆栈的窗口发生了变化,因此就需要调用WindowManagerService类的成员函数updateFocusedWindowLocked来重新计算系统当前可获得焦点的窗口,以及调用WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked来刷新系统的UI。
我们接着分析AppWindowToken对象ttoken的成员变量startingData的值不等于null的情况。
这时候由于WindowManagerService服务还没有参数transferFrom所描述的Activity组件创建启动窗口,因此,这段代码只需要将用创建这个启动窗口的相关数据转移给参数token所描述的Activity组件就可以了,即将AppWindowToken对象ttoken的成员变量startingData的值设置给AppWindowToken对象wtoken的成员变量startingData,并且将AppWindowToken对象ttoken的成员变量startingData的值设置为null。
由于这时候参数token所描述的Activity组件的启动窗口还没有创建出来,因此,接下来就会向WindowManagerService服务所运行在的线程的消息队列的头部插入一个类型ADD_STARTING的消息。当这个消息被处理的时候,WindowManagerService服务就会为参数token所描述的Activity组件创建一个启动窗口。
WindowManagerService类的成员变量mH指向的是一个类型为H的对象。H是WindowManagerService的一个内部类,它是从Handler类继承下来的,因此,调用它的成员函数sendMessageAtFrontOfQueue就可以往一个线程的消息队列的头部插入一个消息。又由于 WindowManagerService类的成员变量mH所指向的一个H对象是在WindowManagerService服务所运行在的线程中创建的,因此,调用它的成员函数sendMessageAtFrontOfQueue发送的消息是保存在WindowManagerService服务所运行在的线程的消息队列中的。
如果参数transferFrom所描述的Activity组件没有启动窗口或者启动窗口数据转移给参数token所描述的Activity组件,那么接下来就可能需要为参数token所描述的Activity组件创建一个新的启动窗口。
但是如果启动窗口和启动数据都没有,我们就要新建StartingData(启动数据),然后发送ADD_STARTING消息。
我们再来看这个消息的处理:
1.3 ADD_STARTING消息处理
ADD_STARTING消息处理肯定是时启动窗口没有,所以需要窗口启动窗口的。
case ADD_STARTING: {final AppWindowToken wtoken = (AppWindowToken)msg.obj;final StartingData sd = wtoken.startingData;if (sd == null) {// Animation has been canceled... do nothing.return;}View view = null;try {view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme, sd.compatInfo,sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo, sd.windowFlags);} catch (Exception e) {Slog.w(TAG, "Exception when adding starting window", e);}if (view != null) {boolean abort = false;synchronized(mWindowMap) {if (wtoken.removed || wtoken.startingData == null) {// If the window was successfully added, then// we need to remove it.if (wtoken.startingWindow != null) {wtoken.startingWindow = null;wtoken.startingData = null;abort = true;}} else {wtoken.startingView = view;}}if (abort) {try {mPolicy.removeStartingWindow(wtoken.token, view);} catch (Exception e) {Slog.w(TAG, "Exception when removing starting window", e);}}}} break;
参数msg指向一个Message对象,从前面的Step 2可以知道,它的成员变量what的值等于ADD_STARTING,而成员变量obj指向的是一个AppWindowToken对象。这个AppWindowToken对象描述的就是要创建启动窗口的Activity组件。H类的成员函数handleMessage首先获得该AppWindowToken对象,并且保存在变量wtoken中。
有了AppWindowToken对象wtoken,H类的成员函数handleMessage就可以通过它的成员变量startingData来获得一个StartingData对象,并且保存在变量sd中。StartingData对象sd里面保存了创建启动窗口所需要的参数,因此,H类的成员函数handleMessage就可以通过这些参数来调用外部类WindowManagerService的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数addStartingWindow来为AppWindowToken对象wtoken所描述的Activity组件创建启动窗口。
如果能够成功地为AppWindowToken对象wtoken所描述的Activity组件创建启动窗口,那么PhoneWindowManager类的成员函数addStartingWindow就返回该Activity组件的启动窗口的顶层视图。H类的成员函数handleMessage获得这个视图之后,就会将它保存在变量view中。
由于在创建这个启动窗口的过程中,AppWindowToken对象wtoken所描述的Activity组件可能已经被移除,即AppWindowToken对象wtoken的成员变量removed的值等于true,或者它的启动窗口已经被转移给另外一个Activity组件了,即AppWindowToken对象wtoken的成员变量startingData的值等于null。在这两种情况下,如果AppWindowToken对象wtoken的成员变量startingWindow的值不等于null,那么就说明前面不仅成功地为AppWindowToken对象wtoken所描述的Activity组件创建了启动窗口,并且这个启动窗口也已经成功地增加到WindowManagerService服务中去了,因此,就需要将该启动窗口从WindowManagerService服务中删除,这是通过调用外部类WindowManagerService的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数removeStartingWindow来实现的。注意,在删除之前,还会将AppWindowToken对象wtoken的成员变量startingWindow和startingData的值均设置为null,以表示它所描述的Activity组件没有一个关联的启动窗口。
另一方面,如果AppWindowToken对象wtoken所描述的Activity组件没有被移除,并且它的启动窗口了没有转移给另外一个Activity组件,那么H类的成员函数handleMessage就会将前面得到的启动窗口的顶层视图保存在AppWindowToken对象wtoken的成员变量startingView中。注意,这时候AppWindowToken对象wtoken的成员变量startingWindow会指向一个WindowState对象,这个WindowState对象是由PhoneWindowManager类的成员函数addStartingWindow请求WindowManagerService服务创建的。
接下来,我们就继续分析PhoneWindowManager类的成员函数addStartingWindow的实现,以便可以了解它是如何为一个Activity组件创建一个启动窗口,并且将这个启动窗口增加到WindowManagerService服务中去的。
1.4 PhoneWindowManager的addStartingWindow函数
我们再来看看PhoneWindowManager的addStartingWindow函数
public View addStartingWindow(IBinder appToken, String packageName, int theme,CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,int icon, int logo, int windowFlags) {if (!SHOW_STARTING_ANIMATIONS) {return null;}if (packageName == null) {return null;}WindowManager wm = null;View view = null;try {Context context = mContext;if (theme != context.getThemeResId() || labelRes != 0) {try {context = context.createPackageContext(packageName, 0);context.setTheme(theme);} catch (PackageManager.NameNotFoundException e) {// Ignore}}PhoneWindow win = new PhoneWindow(context);win.setIsStartingWindow(true);final TypedArray ta = win.getWindowStyle();if (ta.getBoolean(com.android.internal.R.styleable.Window_windowDisablePreview, false)|| ta.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper,false)) {return null;}Resources r = context.getResources();win.setTitle(r.getText(labelRes, nonLocalizedLabel));win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);//注意这个,type是TYPE_APPLICATION_STARTINGsynchronized (mWindowManagerFuncs.getWindowManagerLock()) {// Assumes it's safe to show starting windows of launched apps while// the keyguard is being hidden. This is okay because starting windows never show// secret information.if (mKeyguardHidden) {windowFlags |= FLAG_SHOW_WHEN_LOCKED;}}win.setFlags(windowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,windowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);win.setDefaultIcon(icon);win.setDefaultLogo(logo);win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.MATCH_PARENT);final WindowManager.LayoutParams params = win.getAttributes();params.token = appToken;params.packageName = packageName;params.windowAnimations = win.getWindowStyle().getResourceId(com.android.internal.R.styleable.Window_windowAnimationStyle, 0);params.privateFlags |=WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;if (!compatInfo.supportsScreen()) {params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;}params.setTitle("Starting " + packageName);wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);view = win.getDecorView();if (win.isFloating()) {return null;}wm.addView(view, params);return view.getParent() != null ? view : null;
PhoneWindowManager这个函数就是创建了一个窗口,然后设置type(我们注意上面的type是TYPE_APPLICATION_STARTING代表应用刚启动) flag layout,最后把这个窗口通过WMS的addWindow函数(这个我们之前的博客分析过)加入WMS中管理。
二. Activity组件的启动窗口的结束过程
Activity组件启动完成之后,它的窗口就会显示出来,这时候WindowManagerService服务就需要将它的启动窗口结束掉。我们知道,在WindowManagerService服务中,每一个窗口都对应有一个WindowState对象。每当WindowManagerService服务需要显示一个窗口的时候,就会调用一个对应的WindowState对象的成员函数performShowLocked。WindowState类的成员函数performShowLocked在执行的过程中,就会检查当前正在处理的WindowState对象所描述的窗口是否设置有启动窗口。如果有的话,那么就会将它结束掉。这个过程如图所示。
在commitFinishDrawingLocked函数中,而当mAttrs.type == TYPE_APPLICATION_STARTING就会调用performShowLocked函数,前面当我们启动一个应用的启动窗口会在PhoneWindowManager的addStartingWindow函数中新建一个窗口,并且type设置为这个类型。
boolean commitFinishDrawingLocked() {if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {return false;}mDrawState = READY_TO_SHOW;final AppWindowToken atoken = mWin.mAppToken;if (atoken == null || atoken.allDrawn || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {return performShowLocked();}return false;}
然后commitFinishDrawingLocked又在performLayoutAndPlaceSurfacesLockedInner函数中调用(这个就是WMS刷新的核心函数)。
2.1 WindowStateAnimator的performShowLocked函数
boolean performShowLocked() {if (mWin.isHiddenFromUserLocked()) {if (DEBUG_VISIBILITY) Slog.w(TAG, "hiding " + mWin + ", belonging to " + mWin.mOwnerUid);mWin.hideLw(false);return false;}if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null);mService.enableScreenIfNeededLocked();applyEnterAnimationLocked();// Force the show in the next prepareSurfaceLocked() call.mLastAlpha = -1;if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this);mDrawState = HAS_DRAWN;mService.scheduleAnimationLocked();int i = mWin.mChildWindows.size();while (i > 0) {i--;WindowState c = mWin.mChildWindows.get(i);if (c.mAttachedHidden) {c.mAttachedHidden = false;if (c.mWinAnimator.mSurfaceControl != null) {c.mWinAnimator.performShowLocked();// It hadn't been shown, which means layout not// performed on it, so now we want to make sure to// do a layout. If called from within the transaction// loop, this will cause it to restart with a new// layout.final DisplayContent displayContent = c.getDisplayContent();if (displayContent != null) {displayContent.layoutNeeded = true;}}}}if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING&& mWin.mAppToken != null) {mWin.mAppToken.firstWindowDrawn = true;if (mWin.mAppToken.startingData != null) {// If this initial window is animating, stop it -- we// will do an animation to reveal it from behind the// starting window, so there is no need for it to also// be doing its own stuff.clearAnimation();mService.mFinishedStarting.add(mWin.mAppToken);mService.mH.sendEmptyMessage(H.FINISHED_STARTING);}mWin.mAppToken.updateReportedVisibilityLocked();}return true;}return false;}
对当前正在正在处理的窗口执行以下操作:
1. 将对应的WindowState对象的成员变量mLastAlpha的值设置为-1,以便以后在显示窗口之前,都可以更新窗口的Alpha通道值。
2. 将对应的WindowState对象的成员变量mHasDrawn的值设置为true,以便可以表示窗口的UI绘制出来了。
3. 确保屏幕对WindowManagerService服务是可用的,这是通过调用WindowManagerService类的成员函数enableScreenIfNeededLocked来实现的。系统在启动完成之前,屏幕是用来显示开机动画的,这时候屏幕是被开机动画占用的。等到系统启动完成之后,屏幕就应该是被WindowManagerService服务占用的,这时候就需要停止显示开机动画。WindowManagerService类的成员函数enableScreenIfNeededLocked就是确保开机动画已经停止显示。
4. 给窗口设置一个进入动画或者显示动画,这是通过调用WindowManagerService类的成员函数applyEnterAnimationLocked来实现的。默认是设置为显示动画,但是如果窗口之前是不可见的,那么就会设置为进入动画。
5. 子窗口处理
由于当前正在处理的窗口可能有子窗口,因此就需要在显示完成当前正在处理的窗口之后,继续将它的子窗口显示出来。如果一个窗口具有子窗口,那么这些子窗口就会保存在一个对应的WindowState对象的成员变量mChildWindows所描述的一个ArrayList中。注意,只有那些父窗口上一次是不可见的,并且具有绘图表面的子窗口才需要显示。显示子窗口是通过递归调用WindowState类的成员函数performShowLocked来实现的。
6. 启动窗口处理
最后,如果当前正在处理的窗口是一个Acitivy组件相关的窗口,并且不是Acitivy组件的启动窗口,即当前正在处理的WindowState对象的成员变量mAppToken的值不等于null,并且成员变量mAttrs所指向的一个WindowManager.LayoutParams对象的成员变量type的值不等于TYPE_APPLICATION_STARTING,那么就需要检查该Acitivy组件是否设置有启动窗口。如果设置有启动窗口的话,那么就需要结束显示该启动窗口,因为与该Acitivy组件相关的其它窗口已经显示出来了。
只要当前正在处理的WindowState对象的成员变量mAppToken不等于null,并且它所指向的一个AppWindowToken对象的成员变量startingData的值也不等于null,那么就说明当前正在处理的窗口是一个Acitivy组件相关的窗口,并且这个Acitivy组件设置有一个启动窗口。在这种情况下,WindowState类的成员函数performShowLocked就会调用WindowManagerService类的成员变量mH所指向的一个H对象的成员函数sendEmptyMessage来向WindowManagerService服务所运行在的线程发送一个类型为FINISHED_STARTING的消息,表示要结束显示一个Acitivy组件的启动窗口。在发送这个消息之前,WindowState类的成员函数performShowLocked还会将用来描述要结束显示启动窗口的Activity组件的一个AppWindowToken对象增加到WindowManagerService类的成员变量mFinishedStarting所描述的一个ArrayList中去。
注意,如果这时候当前正在处理的窗口还在显示动画,即当前正在处理的WindowState对象的成员变量mAnimation的值不等于null,那么WindowState类的成员函数performShowLocked就会同时将该动画结束掉,就会调用clearAnimation即将当前正在处理的WindowState对象的成员变量mAnimation的值设置为null,但是会将另外一个成员变量mAnimating的值设置为true,以便可以在其它地方对当前正在处理的窗口的动画进行清理。
public void clearAnimation() {if (mAnimation != null) {mAnimating = true;mLocalAnimating = false;mAnimation.cancel();mAnimation = null;mKeyguardGoingAwayAnimation = false;}}
还有一个地方需要注意的是,如果当前正在处理的窗口是一个Acitivy组件相关的窗口,那么WindowState类的成员函数performShowLocked还会调用当前正在处理的WindowState对象的成员变量mAppToken所指向的一个AppWindowToken对象的成员函数updateReportedVisibilityLocked来向ActivityManagerService服务报告该Acitivy组件的可见性。
接下来,我们就继续分析在WindowManagerService类内部定义的H类的成员函数sendEmptyMessage的实现,以便可以了解Acitivy组件的启动窗口的结束过程。
2.2 FINISHED_STARTING消息处理
消息的处理是在WMS中:
case FINISHED_STARTING: {IBinder token = null;View view = null;while (true) {synchronized (mWindowMap) {final int N = mFinishedStarting.size();if (N <= 0) {break;}AppWindowToken wtoken = mFinishedStarting.remove(N-1);if (wtoken.startingWindow == null) {continue;}view = wtoken.startingView;token = wtoken.token;wtoken.startingData = null;wtoken.startingView = null;wtoken.startingWindow = null;wtoken.startingDisplayed = false;}try {mPolicy.removeStartingWindow(token, view);} catch (Exception e) {Slog.w(TAG, "Exception when removing starting window", e);}}} break;
之前在performShowLocked中会把启动窗口加入mFinishedStarting队列,这里先检查下mFinishedStarting队列是否为空,如果为空直接退出。不为空一个一个去除其中的窗口。将启动窗口的各项数据清空,然后调用PhoneWindowManager的removeStartingWindow函数。
2.3 PhoneWindowManager的removeStartingWindow函数
PhoneWindowManager的removeStartingWindow函数直接到WindowManagerImpl的removeView中了。
public void removeStartingWindow(IBinder appToken, View window) {if (window != null) {WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);wm.removeView(window);}}
WindowManagerImpl的removeView函数直接调用了WindowManagerGlobal的removeView函数了
@Overridepublic void removeView(View view) {mGlobal.removeView(view, false);}
WindowManagerGlobal的removeView函数调用了removeViewLocked函数。
public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");}synchronized (mLock) {int index = findViewLocked(view, true);View curView = mRoots.get(index).getView();removeViewLocked(index, immediate);if (curView == view) {return;}throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);}}
这个函数主要是调用了ViewRootImpl的die函数。
private void removeViewLocked(int index, boolean immediate) {ViewRootImpl root = mRoots.get(index);View view = root.getView();if (view != null) {InputMethodManager imm = InputMethodManager.getInstance();if (imm != null) {imm.windowDismissed(mViews.get(index).getWindowToken());}}boolean deferred = root.die(immediate);if (view != null) {view.assignParent(null);if (deferred) {mDyingViews.add(view);}}}
2.4 ViewRootImpl的die函数
这里传入的参数immediate是false,所以直接发送了MSG_DIE信号。
boolean die(boolean immediate) {// Make sure we do execute immediately if we are in the middle of a traversal or the damage// done by dispatchDetachedFromWindow will cause havoc on return.if (immediate && !mIsInTraversal) {doDie();return false;}if (!mIsDrawing) {destroyHardwareRenderer();} else {Log.e(TAG, "Attempting to destroy the window while drawing!\n" +" window=" + this + ", title=" + mWindowAttributes.getTitle());}mHandler.sendEmptyMessage(MSG_DIE);return true;}
MSG_DIE信号的处理直接是调用了doDie函数。
case MSG_DIE:doDie();break;
我们来看下ViewRootImpl的doDie函数
void doDie() {synchronized (this) {if (mRemoved) {return;}mRemoved = true;if (mAdded) {dispatchDetachedFromWindow();}if (mAdded && !mFirst) {destroyHardwareRenderer();if (mView != null) {int viewVisibility = mView.getVisibility();boolean viewVisibilityChanged = mViewVisibility != viewVisibility;if (mWindowAttributesChanged || viewVisibilityChanged) {// If layout params have been changed, first give them// to the window manager to make sure it has the correct// animation info.try {if ((relayoutWindow(mWindowAttributes, viewVisibility, false)& WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {mWindowSession.finishDrawing(mWindow);}} catch (RemoteException e) {}}mSurface.release();}}mAdded = false;}WindowManagerGlobal.getInstance().doRemoveView(this);}
我们先看WindowManagerGlobal的doRemoveView函数只是去除一些变量而已
void doRemoveView(ViewRootImpl root) {synchronized (mLock) {final int index = mRoots.indexOf(root);if (index >= 0) {mRoots.remove(index);mParams.remove(index);final View view = mViews.remove(index);mDyingViews.remove(view);}}if (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) {doTrimForeground();}}
ViewRootImpl类有一个成员变量mAdded,当它的值等于true的时候,就表示当前正在处理的ViewRoot对象有一个关联的View对象,因此,这时候就可以调用另外一个成员函数dispatchDetachedFromWindow来删除这个View对象。由于删除了这个View对象之后,当前正在处理的ViewRootImpl对象就不再关联有View对象了,因此,ViewRootImpl类的成员函数doDie在调用另外一个成员函数dispatchDetachedFromWindow之前,还会将成员变量mAdded的值设置为false。
void dispatchDetachedFromWindow() {......try {mWindowSession.remove(mWindow);} catch (RemoteException e) {}......}
每一个与UI相关的应用程序进程,都与WindowManagerService服务建立有一个连接,这个连接是通过一个实现了IWindowSession接口的Binder代理对象来描述的,并且这个Binder代理对象就保存在ViewRoot类的静态成员变量sWindowSession中,它引用了运行在WindowManagerService服务这一侧的一个类型为Session的Binder本地对象。
注意,由于当前进程即为WindowManagerService服务所运行在的进程,因此,这时候ViewRootImpl类的静态成员变量sWindowSession保存的其实不是一个实现了IWindowSession接口的Binder代理对象,而是一个实现了IWindowSession接口的类型为Session的Binder本地对象。这是因为Binder驱动发现Client和Service是位于同一个进程的时候,就会将Service的本地接口直接返回给Client,而不会将Service的代理对象返回给Client,这样就可以避免在同一进程中执行Binder进程间调用也会经过Binder驱动来中转。
进程中的每一个窗口都有一个对应的W对象,这个W对象就保存在ViewRootImpl类的成员变量mWindow中。有了这个W对象之后,ViewRootImpl类的成员函数dispatchDetachedFromWindow就可以调用静态成员变量sWindowSession所描述的一个Session对象的成员函数remove来通知WindowManagerService服务删除一个对应的WindowState对象。从前面的调用过程可以知道,这个WindowState对象描述的是一个Activity组件的启动窗口,因此,WindowManagerService服务删除了这个WindowState对象之后,就相当于是将一个Activity组件的启动窗口结束掉了。
接下来,我们就继续分析Session类的成员函数remove的实现,以便可以了解Activity组件的启动窗口的结束过程。
final class Session extends IWindowSession.Stubimplements IBinder.DeathRecipient {......public void remove(IWindow window) {mService.removeWindow(this, window);}
2.5 WMS的removeWindow函数
最后到了WMS的removeWindow中。找到其相关的windowState,然后调用removeWindowLocked函数。
public void removeWindow(Session session, IWindow client) {synchronized(mWindowMap) {WindowState win = windowForClientLocked(session, client, false);if (win == null) {return;}removeWindowLocked(win);}}
removeWindowLocked函数
void removeWindowLocked(WindowState win) {final boolean startingWindow = win.mAttrs.type == TYPE_APPLICATION_STARTING;final long origId = Binder.clearCallingIdentity();win.disposeInputChannel();boolean wasVisible = false;if (win.mHasSurface && okToDisplay()) {// If we are not currently running the exit animation, we// need to see about starting one.wasVisible = win.isWinVisibleLw();if (wasVisible) {final int transit = (!startingWindow)? WindowManagerPolicy.TRANSIT_EXIT: WindowManagerPolicy.TRANSIT_PREVIEW_DONE;// Try starting an animation.if (win.mWinAnimator.applyAnimationLocked(transit, false)) {win.mExiting = true;}if (mAccessibilityController != null&& win.getDisplayId() == Display.DEFAULT_DISPLAY) {mAccessibilityController.onWindowTransitionLocked(win, transit);}}final AppWindowToken appToken = win.mAppToken;final boolean isAnimating = win.mWinAnimator.isAnimating();final boolean lastWinStartingNotAnimating = startingWindow && appToken!= null&& appToken.allAppWindows.size() == 1 && !isAnimating;if (!lastWinStartingNotAnimating && (win.mExiting || isAnimating)) {// The exit animation is running... wait for it!win.mExiting = true;win.mRemoveOnExit = true;final DisplayContent displayContent = win.getDisplayContent();if (displayContent != null) {displayContent.layoutNeeded = true;}final boolean focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);performLayoutAndPlaceSurfacesLocked();if (appToken != null) {appToken.updateReportedVisibilityLocked();}if (focusChanged) {mInputMonitor.updateInputWindowsLw(false /*force*/);}Binder.restoreCallingIdentity(origId);return;}}removeWindowInnerLocked(win);......updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);......}
WindowManagerService类的成员函数removeWindowLocked在删除参数win所描述的一个窗口之前,首先检查是否需要对该窗口设置一个退出动画。只要满足以下四个条件,那么就需要对参数win所描述的一个窗口设置退出动画:
1. 参数win所描述的一个窗口具有绘图表面,即它的成员变量mSurface的值不等于null;
2. 系统屏幕当前没有被冻结,即WindowManagerService类的成员变量mDisplayFrozen的值等于false;
3. 系统屏幕当前是点亮的,即WindowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数isScreenOn的返回值为true;
4. 参数win所描述的一个窗口当前是可见的,即它的成员函数isWinVisibleLw的返回值等于true。
对参数win所描述的一个窗口设置退出动画是通过调用WindowManagerService类的成员函数applyAnimationLocked来实现的。注意,如果参数win描述的是一个启动窗口,那么退出动画的类型就为WindowManagerPolicy.TRANSIT_PREVIEW_DONE,否则的话,退出动画的类型就为WindowManagerPolicy.TRANSIT_EXIT。
一旦参数win所描述的一个窗口正处于退出动画或者其它动画状态,即它的成员变量mExiting的值等于true或者成员函数isAnimating的返回值等于true,那么WindowManagerService服务就要等它的动画显示完成之后,再删除它,这是通过将它的成员变量mExiting和mRemoveOnExit的值设置为true来完成的。由于这时候还需要显示参数win所描述的一个窗口的退出动画或者其它动画,因此,WindowManagerService类的成员函数removeWindowLocked在返回之前,还需要执行以下操作:
1. 调用WindowManagerService类的成员函数updateFocusedWindowLocked来重新计算系统当前需要获得焦点的窗口;
2. 调用WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked来重新布局和刷新系统的UI;
3. 如果参数win所描述的一个与Activity组件相关的窗口,即它的成员变量mAppToken的值不等于null,那么就会调用这个成员变量所指向的一个AppWindowToken对象的成员函数updateReportedVisibilityLocked来向ActivityManagerService服务报告该Activity组件的可见性。
如果不需要对参数win所描述的一个窗口设置退出动画,那么WindowManagerService类的成员函数removeWindowLocked就会直接调用成员函数removeWindowInnerLocked来删除该窗口,并且在删除了该窗口之后,调用成员函数updateFocusedWindowLocked来重新计算系统当前需要获得焦点的窗口以及重新布局和刷新系统的UI。
接下来,我们就继续分析WindowManagerService类的成员函数removeWindowLocked的实现,以便可以了解Activity组件的启动窗口的结束过程。
最后我们来看下WMS的removeWindowInnerLocked函数
void removeWindowInnerLocked(WindowState win, boolean performLayout) {if (win.mRemoved) {// Nothing to do.return;}for (int i=win.mChildWindows.size()-1; i>=0; i--) {//处理子窗口WindowState cwin = win.mChildWindows.get(i);Slog.w(TAG, "Force-removing child win " + cwin + " from container "+ win);removeWindowInnerLocked(cwin);}win.mRemoved = true;//设置变量if (mInputMethodTarget == win) {moveInputMethodWindowsIfNeededLocked(false);}if (false) {RuntimeException e = new RuntimeException("here");e.fillInStackTrace();Slog.w(TAG, "Removing window " + win, e);}mPolicy.removeWindowLw(win);win.removeLocked();mWindowMap.remove(win.mClient.asBinder());if (win.mAppOp != AppOpsManager.OP_NONE) {mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());}mPendingRemove.remove(win);mResizingWindows.remove(win);mWindowsChanged = true;if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);if (mInputMethodWindow == win) {mInputMethodWindow = null;} else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {mInputMethodDialogs.remove(win);}final WindowToken token = win.mToken;final AppWindowToken atoken = win.mAppToken;if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + win + " from " + token);token.windows.remove(win);if (atoken != null) {atoken.allAppWindows.remove(win);}if (localLOGV) Slog.v(TAG, "**** Removing window " + win + ": count="+ token.windows.size());if (token.windows.size() == 0) {if (!token.explicit) {mTokenMap.remove(token.token);} else if (atoken != null) {atoken.firstWindowDrawn = false;}}if (atoken != null) {if (atoken.startingWindow == win) {if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Notify removed startingWindow " + win);scheduleRemoveStartingWindowLocked(atoken);} elseif (atoken.allAppWindows.size() == 0 && atoken.startingData != null) {// If this is the last window and we had requested a starting// transition window, well there is no point now.if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling last startingWindow");atoken.startingData = null;} else if (atoken.allAppWindows.size() == 1 && atoken.startingView != null) {// If this is the last window except for a starting transition// window, we need to get rid of the starting transition.scheduleRemoveStartingWindowLocked(atoken);}}if (win.mAttrs.type == TYPE_WALLPAPER) {mLastWallpaperTimeoutTime = 0;getDefaultDisplayContentLocked().pendingLayoutChanges |=WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;} else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {getDefaultDisplayContentLocked().pendingLayoutChanges |=WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;}final WindowList windows = win.getWindowList();if (windows != null) {windows.remove(win);if (!mInLayout) {assignLayersLocked(windows);final DisplayContent displayContent = win.getDisplayContent();if (displayContent != null) {displayContent.layoutNeeded = true;}if (performLayout) {performLayoutAndPlaceSurfacesLocked();}if (win.mAppToken != null) {win.mAppToken.updateReportedVisibilityLocked();}}}mInputMonitor.updateInputWindowsLw(true /*force*/);}
由于参数win所描述的一个窗口马上就要被删除了,因此,WindowManagerService类的成员函数removeWindowLocked首先就将它的成员变量mRemoved的值设置为true。此外,如果参数win所描述的窗口是系统输入法的目标窗口,那么还需要调用WindowManagerService类的成员函数moveInputMethodWindowsIfNeededLocked来重新移动动系统输入法窗口到其它可能需要输入法的窗口的上面去。
执行完成以上两个操作之后,WindowManagerService类的成员函数removeWindowLocked接下来就可以对参数win所描述的一个窗口进行清理了,包括:
1. 调用WindowManagerService类的成员变量mPolicy的成员函数removeWindowLw来通知窗口管理策略类PhoneWindowManager,参数win所描述的一个窗口被删除了;
2. 调用参数win所指向的一个WindowState对象的成员函数removeLocked来执行自身的清理工作;
3. 将参数win所指向的一个WindowState对象从WindowManagerService类的成员变量mWindowMap和mWindows中删除,即将参数win所描述的一个窗口从窗口堆栈中删除。
执行完成以上三个清理工作之后,窗口堆栈就发生变化了,因此,就需要将WindowManagerService类的成员变量mWindowsChanged的值设置为true。
接下来,WindowManagerService类的成员函数removeWindowLocked还会检查前面被删除的窗口是否是一个输入法窗口或者一个输入法对话框。如果是一个输入法窗口,那么就会将WindowManagerService类的成员变量mInputMethodWindow的值设置为true;如果是一个输入法对话框,那么就会它从WindowManagerService类的成员变量mInputMethodDialogs所描述的一个输入法对话框列表中删除。
WindowManagerService类的成员函数removeWindowLocked的任务还没有完成,它还需要继续从参数win所描述的一个窗口从它的窗口令牌的窗口列表中删除。参数win所描述的一个窗口的窗口令牌保存在它的成员变量mToken中,这个成员变量指向的是一个WindowToken对象。这个WindowToken对象有一个成员变量windows,它指向的是一个ArrayList中。这个ArrayList即为参数win所描述的一个窗口从它的窗口令牌的窗口列表,因此,将参数win所描述的一个窗口从这个窗口列表中删除即可。
如果参数win描述的一个是与Activity组件有关的窗口,那么它的成员变量mAppToken就会指向一个AppWindowToken对象。这个AppWindowToken对象的成员变量allAppWindows所指向的一个ArrayList也会保存有参数win所描述的窗口。因此,这时候也需要将参数win所描述的一个窗口从这个ArrayList中删除。
参数win所描述的一个窗口被删除了以后,与它所对应的窗口令牌的窗口数量就会减少1。如果一个窗口令牌的窗口数量减少1之后变成0,那么就需要考虑将这个窗口令牌从WindowManagerService服务的窗口令牌列表中删除了,即从WindowManagerService类的成员变量mTokenMap和mTokenList中删除,前提是这个窗口令牌不是显式地被增加到WindowManagerService服务中去的,即用来描述这个窗口令牌的一个WindowToken对象的成员变量explicit的值等于false。
另一方面,如果参数win描述的一个是与Activity组件有关的窗口,并且当它被删除之后,与该Activity组件有关的窗口的数量变为0,那么就需要将用来描述该Activity组件的一个AppWindowToken对象的成员变量firstWindowDrawn的值设置为false,以表示该Activity组件的第一个窗口还没有被显示出来,事实上也是表示目前没有窗口与该Activity组件对应。
当参数win描述的一个是与Activity组件有关的窗口的时候,WindowManagerService类的成员函数removeWindowLocked还需要检查该Activity组件是否设置有启动窗口。如果该Activity组件设置有启动窗口的话,那么就需要对它的相应成员变量进行清理。这些检查以及清理工作包括:
1. 如果参数win所描述的窗口即为一个Activity组件的窗口,即它的值等于用来描述与它的宿主Activity组件的一个AppWindowToken对象的成员变量startingWindow的值,那么就需要将AppWindowToken对象的成员变量startingWindow的值设置为null,以便可以表示它所描述的Activity组件的启动窗口已经结束了;
2. 如果删除了参数win所描述的窗口之后,它的宿主Activity组件的窗品数量为0,但是该Activity组件又正在准备显示启动窗口,即用来描述该Activity组件的一个AppWindowToken对象的成员变量startingData的值不等于null,那么就说明这个启动窗口接下来也没有必要显示了,因此,就需要将该AppWindowToken对象的成员变量startingData的值设置为null;
3. 如果删除了参数win所描述的窗口之后,它的宿主Activity组件的窗品数量为1,并且用来描述该Activity组件的一个AppWindowToken对象的成员变量startingView的值不等于null,那么就说明该Activity组件剩下的最后一个窗口即为它的启动窗口,这时候就需要请求WindowManagerService服务结束掉这个启动窗口,因为已经没有必要显示了。
当一个Activity组件剩下的窗口只有一个,并且用来描述该Activity组件的一个AppWindowToken对象的成员变量startingView的值不等于null时,我们是如何知道这个剩下的窗口就是该Activity组件的启动窗口的呢?从前面第一个部分的内容可以知道,当一个Activity组件的启动窗口被创建出来之后,它的顶层视图就会保存在用来描述该Activity组件的一个AppWindowToken对象的成员变量startingView中。因此,如果Activity组件满足上述两个条件,我们就可以判断出它所剩下的一个窗口即为它的启动窗口。注意,在这种情况下,WindowManagerService类的成员函数removeWindowLocked不是马上删除这个启动窗口的,而是通过向WindowManagerService服务所运行在的线程发送一个类型为REMOVE_STARTING的消息,等到该消息被处理时再来删除这个启动窗口。
清理了窗口win的宿主Activity组件的启动窗口相关的数据之后,WindowManagerService类的成员函数removeWindowLocked又继续检查窗口win是否是一个壁纸窗口或者一个显示壁纸的窗口。如果是的话,那么就需要调用WindowManagerService类的成员函数adjustWallpaperWindowsLocked来重新调整系统中的壁纸窗口在窗口堆栈中的位置,即将它们移动到下一个可能需要显示壁纸窗口的其它窗口的下面去。
WindowManagerService类的成员函数removeWindowLocked的最后一个任务是检查WindowManagerService服务当前是否正处于重新布局窗口的状态,即判断WindowManagerService类的成员变量mInLayout的值是否等于true。如果不等于true的话,那么就需要调用WindowManagerService类的成员函数performLayoutAndPlaceSurfacesLocked来重新布局窗口,实际上就是刷新系统的UI。
注意,WindowManagerService类的成员函数removeWindowLocked在重新布局系统中的窗口之前,还需要调用另外一个成员函数assignLayersLocked来重新计算系统中的所有窗口的Z轴位置了。此外,WindowManagerService类的成员函数removeWindowLocked在重新布局了系统中的窗口之后,如果发现前面被删除的窗口win是一个与Activity组件相关的窗口,即它的成员变量mAppToken的值不等于null,那么还会调用这个成员变量所指向的一个AppWindowToken对象的成员函数updateReportedVisibilityLocked来向ActivityManagerService服务报告该Activity组件的可见性。
这一步执行完成之后,一个的Activity组件的启动窗口结束掉了。至此,我们就分析完成Activity组件的启动窗口的启动过程和结束过程了。
Android6.0 WMS(八) 显示Activity的启动窗口相关推荐
- Android8.0源码解析——Activity的启动过程
前言 Activity是Android的四大组件,关于Activity 的启动过程是怎么样的昵,下面我们主要通过Android8.0的源码来分析一下. 1.Activity的生命周期: Activit ...
- android wms 窗口,Android6.0 WMS(十一) WMS窗口动画生成及播放
上一篇我们我们分析到有VSync信号过来,最后会调用WindowAnimator的animateLocked函数来生成和播放动画,这篇我们我们主要从这个函数开始分析. animateLocked函数 ...
- android apk旋转,[RK3288][Android6.0] Apk设置显示旋转过程小结
Platform: Rockchip OS: Android 6.0 Kernel: 3.10.92 setRequestedOrientation -> Activity.java Activ ...
- activity的启动窗口
转自http://blog.csdn.net/windskier
- [RK3288][Android6.0] 设置中通过Sensor旋转显示画面小结
Platform: Rockchip OS: Android 6.0 Kernel: 3.10.92 Settings -> Display有个选项控制旋转屏幕时内容是否跟着旋转 这个功能是通过 ...
- 值得你关注的Android6.0上的重要变化(一)
伴随着众多新特性和新功能,Android6.0(API level 23)在系统和API上都有着诸多的改变.本文着重介绍几个关键变化,以帮助你理解这些改变对你的APP产生的影响. 一.运行时权限检查( ...
- Activity启动窗口(StartingWindow)的添加流程
本篇基于Android Q分析 在Activity启动的时候,Android系统会为它添加一个启动窗口,作用是在应用程序主Activity还没有显示出来的时候,它作为一个预览窗口先让用户能看到一个画面 ...
- 安卓activity的启动流程
文章目录 前言 一.概述 二. 启动流程 2.1 Activity.startActivity 2.2 startActivityForResult 2.3 execStartActivity 2.4 ...
- android6.0源码分析之Activity启动过程
Activity最为Android开发者最熟悉的组件,由ActivityManagerService服务进行调度管理,而ActivityManagerService的启动过程在activitymana ...
最新文章
- 管好统计信息,开启SQL优化之门
- php生成临时文件,使用PHP处理内存中的存档(无需在磁盘上创建临时文件)
- python的for语句写新的字符串_python写for循环python字符串排序方法
- 1.逐帧动画shader
- JavaScript图片库
- HTML5 兼容IE浏览器
- 项目进度控制的重要性
- js obj对象转formdata格式代码
- 第五章 线性回归 学习笔记中
- html去除重复代码,simian 查找项目中的重复代码
- 华为锁屏后微信网络连接服务器,华为P30 Pro锁屏息屏状态下微信收不到信息,显示网络不可用...
- excel流程图折线箭头_如何绘制excel箭头图形
- 坦克大战小游戏——新手练习用的
- 各种纯净版window系统下载分享
- Qt编写数据可视化大屏界面电子看板10-改造QCustomPlot
- 虚树 virtual-tree
- App Store Connect显示app已经上架(可供销售),但在App Store中没有实时更新
- Linux进程调度 - CFS调度器 LoyenWang
- 多功能音乐沙漏的设计与实现
- 国内首届中文人机对话技术评测赛果出炉,两项任务冠军团队都分享了哪些技术细节?...