Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析

简介:

   在Android系统中,Activity组件在启动之后,并且在它的窗口显示出来之前,可以显示一个启动窗口。这个启动窗口可以看作是Activity组件的预览窗口,是由WindowManagerService服务统一管理的,即由WindowManagerService服务负责启动和结束。在本文中,我们就详细分析WindowManagerService服务启动和结束Activity组件的启动窗口的过程。

       Activity组件的启动窗口是由ActivityManagerService服务来决定是否要显示的。如果需要显示,那么ActivityManagerService服务就会通知WindowManagerService服务来为正在启动的Activity组件显示一个启动窗口,而WindowManagerService服务又是通过窗口管理策略类PhoneWindowManager来创建这个启动窗口的。这个过程如图1所示。


图1 Activity窗口的启动窗品的创建过程

       窗口管理策略类PhoneWindowManager创建完成Activity组件的启动窗口之后,就会请求WindowManagerService服务将该启动窗口显示出来。当Activity组件启动完成,并且它的窗口也显示出来的时候,WindowManagerService服务就会结束显示它的启动窗口。

       注意,Activity组件的启动窗口是由ActivityManagerService服务来控制是否显示的,也就是说,Android应用程序是无法决定是否要要Activity组件显示启动窗口的。接下来,我们就分别分析Activity组件的启动窗口的显示和结束过程。

       一. Activity组件的启动窗口的显示过程

       从前面Android应用程序启动过程源代码分析一文可以知道,Activity组件在启动的过程中,会调用ActivityStack类的成员函数startActivityLocked。注意,在调用ActivityStack类的成员函数startActivityLocked的时候,Actvitiy组件还处于启动的过程,即它的窗口尚未显示出来,不过这时候ActivityManagerService服务会检查是否需要为正在启动的Activity组件显示一个启动窗口。如果需要的话,那么ActivityManagerService服务就会请求WindowManagerService服务为正在启动的Activity组件设置一个启动窗口。这个过程如图2所示。


图2 Activity组件的启动窗口的显示过程

      这个过程可以分为6个步骤,接下来我们就详细分析每一个步骤。

      Step 1. ActivityStack.startActivityLocked


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
public  class  ActivityStack {
     ......
     // Set to false to disable the preview that is shown while a new activity
     // is being started.
     static  final  boolean  SHOW_APP_STARTING_PREVIEW =  true ;
     ......
     private  final  void  startActivityLocked(ActivityRecord r,  boolean  newTask,
             boolean  doResume) {
         final  int  NH = mHistory.size();
         ......
         int  addPos = - 1 ;
         ......
         // Place a new activity at top of stack, so it is next to interact
         // with the user.
         if  (addPos <  0 ) {
             addPos = NH;
         }
         ......
         // Slot the activity into the history stack and proceed
         mHistory.add(addPos, r);
         ......
         if  (NH >  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  (proc ==  null  || proc.thread ==  null ) {
                 showStartingIcon =  true ;
             }
             ......
             mService.mWindowManager.addAppToken(
                     addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen);
             boolean  doShow =  true ;
             if  (newTask) {
                 // Even though this activity is starting fresh, we still need
                 // to reset it to make sure we apply affinities to move any
                 // existing activities from other tasks in to it.
                 // If the caller has requested that the target task be
                 // reset, then do so.
                 if  ((r.intent.getFlags()
                         &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) !=  0 ) {
                     resetTaskIfNeededLocked(r, r);
                     doShow = topRunningNonDelayedActivityLocked( null ) == r;
                 }
             }
             if  (SHOW_APP_STARTING_PREVIEW && doShow) {
                 // Figure out if we are transitioning from another activity that is
                 // "has the same starting icon" as the next one.  This allows the
                 // window manager to keep the previous window it had previously
                 // created, if it still had one.
                 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 ;
                 }
                 mService.mWindowManager.setAppStartingWindow(
                         r, r.packageName, r.theme, r.nonLocalizedLabel,
                         r.labelRes, r.icon, prev, showStartingIcon);
             }
         else  {
             // If this is the first activity, don't do any fancy animations,
             // because there is nothing for it to animate on top of.
             mService.mWindowManager.addAppToken(addPos, r, r.task.taskId,
                     r.info.screenOrientation, r.fullscreen);
         }
         ......
         if  (doResume) {
             resumeTopActivityLocked( null );
         }
     }
     ......
}


       这个函数定义在文件frameworks/base/services/java/com/android/server/am/ActivityStack.java中。

       参数r描述的就是正在启动的Activity组件,而参数newTask和doResume描述的是是否要将该Activity组件放在一个新的任务中启动,以及是否要马上将该Activity组件启动起来。

       ActivityStack类的成员变量mHistory指向的是一个ArrayList,它描述的便是系统的Activity组件堆栈。ActivityStack类的成员函数startActivityLocked首先找到正在启动的Activity组件r在系统的Activity组件堆栈中的位置addPos,然后再将正在启动的Activity组件r保存在这个位置上。

       变量NH记录的是将正在启动的Activity组件r插入到系统的Activity组件堆栈中之前系统中已经启动了的Activity组件的个数。如果变量NH的值大于0,那么就说明系统需要执行一个Activity组件切换操作,即需要在系统当前激活的Activity组件和正在启动的Activity组件r之间执行一个切换操作,使得正在启动的Activity组件r成为系统接下来要激活的Activity组件。在切换的过程,需要显示切换动画,即给系统当前激活的Activity组件显示一个退出动画,而给正在启动的Activity组件r显示一个启动动画,以及需要为正在启动的Activity组件r显示一个启动窗口。另一方面,如果变量NH的值等于0,那么系统就不需要执行Activity组件切换操作,或者为为正在启动的Activity组件r显示一个启动窗口,这时候只需要为正在启动的Activity组件r创建一个窗口令牌即可。

      ActivityStack类的成员变量mService指向的是一个ActivityManagerService对象,这个ActivityManagerService对象就是系统的Activity组件管理服务,它的成员变量mWindowManager指向的是一个WindowManagerService对象,这个WindowManagerService对象也就是系统的Window管理服务。通过调用WindowManagerService类的成员函数addAppToken就可以为正在启动的Activity组件r创建一个窗口令牌,这个过程可以参考前面Android窗口管理服务WindowManagerService对窗口的组织方式分析一文。

      在变量NH的值大于0的情况下,ActivityStack类的成员函数startActivityLocked首先检查用来运行Activity组件r的进程是否已经启动起来了。如果已经启动起来,那么用来描述这个进程的ProcessRecord对象proc的值就不等于null,并且这个ProcessRecord对象proc的成员变量thread的值也不等于null。如果用来运行Activity组件r的进程还没有启动起来,或者Activity组件r需要运行在一个新的任务中,那么变量showStartingIcon的值就会等于true,用来描述在系统当前处于激活状态的Activity组件没有启动窗口的情况下,要为Activity组件r创建一个新的启动窗口,否则的话,就会将系统当前处于激活状态的Activity组件的启动窗口复用为Activity组件r的启动窗口。

       系统当前处于激活状态的Activity组件是通过ActivityStack类的成员变量mResumedActivity来描述的,它的启动窗口可以复用为Activity组件r的启动窗口还需要满足两个额外的条件:

       1. Activity组件mResumedActivity与Activity组件r运行在同一个任务中,即它们的成员变量task指向的是同一个TaskRecord对象;

       2. Activity组件mResumedActivity当前是不可见的,即它的成员变量nowVisible的值等于false。

       这两个条件意味着Activity组件mResumedActivity与Activity组件r运行在同一个任务中,并且Activity组件mResumedActivity的窗口还没有显示出来就需要切换到Activity组件r去。

       ActivityStack类的静态成员变量SHOW_APP_STARTING_PREVIEW是用描述系统是否可以为正在启动的Activity组件显示启动窗口,只有在它的值等于true,以及正在启动的Activity组件的窗口接下来是要显示出来的情况下,即变量doShow的值等于true,ActivityManagerService服务才会请求WindowManagerService服务为正在启动的Activity组件设置启动窗口。

       一般来说,一个正在启动的Activity组件的窗口接下来是需要显示的,但是正在启动的Activity组件可能会设置一个标志位,用来通知ActivityManagerService服务在它启动的时候,对它运行在的任务进行重置。一个任务被重置之后,可能会导致其它的Activity组件转移到这个任务中来,并且位于位于这个任务的顶端。在这种情况下,系统接下来要显示的窗口就不是正在启动的Activity组件的窗口的了,而是位于正在启动的Activity组件所运行在的任务的顶端的那个Activity组件的窗口。正在启动的Activity组件所运行在的任务同时也是一个前台任务,即它顶端的Activity组件就是系统Activity组件堆栈顶端的Activity组件。

       调用参数r所指向一个ActivityRecord对象的成员变量intent所描述的一个Intent对象的成员函数getFlags就可以获得正在启动的Activity组件的标志值,当这个标志值的FLAG_ACTIVITY_RESET_TASK_IF_NEEDED位等于1的时候,就说明正在启动的Activity组件通知ActivityManagerService服务对它运行在的任务进行重置。重置一个任务是通过调用ActivityStack类的成员函数resetTaskIfNeededLocked来实现的。重置了正在启动的Activity组件所运行在的任务之后,再调用ActivityStack类的成员函数topRunningNonDelayedActivityLocked来检查位于系统Activity组件堆栈顶端的Activity组件是否就是正在启动的Activity组件,就可以知道正在启动的Activity组件的窗口接下来是否是需要显示的。如果需要显示的话,那么变量doShow的值就等于true。

       ActivityManagerService服务请求WindowManagerService服务为正在启动的Activity组件设置启动窗口是通过调用WindowManagerService类的成员函数setAppStartingWindow来实现的。注意,ActivityManagerService服务在请求WindowManagerService服务为正在启动的Activity组件设置启动窗口之前,同样会调用WindowManagerService类的成员函数addAppToken来创建窗口令牌。

       ActivityManagerService服务请求WindowManagerService服务为正在启动的Activity组件设置启动窗口之后,如果参数doResume的值等于true,那么就会调用ActivityStack类的成员函数resumeTopActivityLocked继续执行启动参数r所描述的一个Activity组件的操作,这个过程可以参考前面Android应用程序启动过程源代码分析一文。

       接下来,我们就继续分析WindowManagerService类的成员函数setAppStartingWindow的实现,以便可以了解WindowManagerService服务是如何为正在启动的Activity组件设置启动窗口的。

       Step 2. WindowManagerService.setAppStartingWindow

       WindowManagerService类的成员函数setAppStartingWindow定义在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中,它的实现比较长,我们分段来阅读:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  class  WindowManagerService  extends  IWindowManager.Stub
         implements  Watchdog.Monitor {
     ......
     public  void  setAppStartingWindow(IBinder token, String pkg,
             int  theme, CharSequence nonLocalizedLabel,  int  labelRes,  int  icon,
             IBinder transferFrom,  boolean  createIfNeeded) {
         ......
         synchronized (mWindowMap) {
             ......
             AppWindowToken wtoken = findAppWindowToken(token);
             ......
             // 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  (mDisplayFrozen || !mPolicy.isScreenOn()) {
                 return ;
             }
             if  (wtoken.startingData !=  null ) {
                 return ;
             }

       参数token描述的是要设置启动窗口的Activity组件,而参数transferFrom描述的是要将启动窗口转移给Activity组件token的Activity组件。从Step 1可以知道,这两个Activity组件是运行在同一个任务中的,并且参数token描述的Activity组件Activity组件是正在启动的Activity组件,而参数transferFrom描述的Activity组件是系统当前激活的Activity组件。


       这段代码首先调用WindowManagerService类的成员函数findAppWindowToken来获得与参数token对应的一个类型为AppWindowToken的窗口令牌wtoken。如果这个AppWindowToken对象的成员变量startingData的值不等于null,那么就说明参数token所描述的Activity组件已经设置过启动窗口了,因此,WindowManagerService类的成员函数setAppStartingWindow就不用往下处理了。

       这段代码还会检查系统屏幕当前是否处于冻结状态,即WindowManagerService类的成员变量mDisplayFrozen的值是否等于true,或者系统屏幕当前是否处于黑屏状态,即indowManagerService类的成员变量mPolicy所指向的一个PhoneWindowManager对象的成员函数isScreenOn的返回值是否等于false。如果是处于上述两种状态的话,那么WindowManagerService类的成员函数setAppStartingWindow就不用往下处理的。因为在这两种状态下,为token所描述的Activity组件设置的启动窗口是无法显示的。

       我们接着往下阅读代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
if  (transferFrom !=  null ) {
     AppWindowToken ttoken = findAppWindowToken(transferFrom);
     if  (ttoken !=  null ) {
         WindowState startingWindow = ttoken.startingWindow;
         if  (startingWindow !=  null ) {
             if  (mStartingIconInTransition) {
                 // In this case, the starting icon has already
                 // been displayed, so start letting windows get
                 // shown immediately without any more transitions.
                 mSkipAppTransitionAnimation =  true ;
             }
             ......
             final  long  origId = Binder.clearCallingIdentity();
             // Transfer the starting window over to the new
             // token.
             wtoken.startingData = ttoken.startingData;
             wtoken.startingView = ttoken.startingView;
             wtoken.startingWindow = startingWindow;
             ttoken.startingData =  null ;
             ttoken.startingView =  null ;
             ttoken.startingWindow =  null ;
             ttoken.startingMoved =  true ;
             startingWindow.mToken = wtoken;
             startingWindow.mRootToken = wtoken;
             startingWindow.mAppToken = wtoken;
             ......
             mWindows.remove(startingWindow);
             mWindowsChanged =  true ;
             ttoken.windows.remove(startingWindow);
             ttoken.allAppWindows.remove(startingWindow);
             addWindowToListInOrderLocked(startingWindow,  true );
             // Propagate other interesting state between the
             // tokens.  If the old token is displayed, we should
             // immediately force the new one to be displayed.  If
             // it is animating, we need to move that animation to
             // the new one.
             if  (ttoken.allDrawn) {
                 wtoken.allDrawn =  true ;
             }
             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();
             }
             if  (ttoken.animation !=  null ) {
                 wtoken.animation = ttoken.animation;
                 wtoken.animating = ttoken.animating;
                 wtoken.animLayerAdjustment = ttoken.animLayerAdjustment;
                 ttoken.animation =  null ;
                 ttoken.animLayerAdjustment =  0 ;
                 wtoken.updateLayers();
                 ttoken.updateLayers();
             }
             updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES);
             mLayoutNeeded =  true ;
             performLayoutAndPlaceSurfacesLocked();
             Binder.restoreCallingIdentity(origId);
             return ;
         else  if  (ttoken.startingData !=  null ) {
             // The previous app was getting ready to show a
             // starting window, but hasn't yet done so.  Steal it!
             ......
             wtoken.startingData = ttoken.startingData;
             ttoken.startingData =  null ;
             ttoken.startingMoved =  true ;
             Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
             // Note: we really want to do sendMessageAtFrontOfQueue() because we
             // want to process the message ASAP, before any other queued
             // messages.
             mH.sendMessageAtFrontOfQueue(m);
             return ;
         }
     }
}

       如果参数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组件创建一个新的启动窗口,如最后一段代码所示:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
             // There is no existing starting window, and the caller doesn't
             // want us to create one, so that's it!
             if  (!createIfNeeded) {
                 return ;
             }
             // If this is a translucent or wallpaper window, then don't
             // show a starting window -- the current effect (a full-screen
             // opaque starting window that fades away to the real contents
             // when it is ready) does not work for this.
             if  (theme !=  0 ) {
                 AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                         com.android.internal.R.styleable.Window);
                 if  (ent.array.getBoolean(
                         com.android.internal.R.styleable.Window_windowIsTranslucent,  false )) {
                     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 )) {
                     return ;
                 }
             }
             mStartingIconInTransition =  true ;
             wtoken.startingData =  new  StartingData(
                     pkg, theme, nonLocalizedLabel,
                     labelRes, icon);
             Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
             // Note: we really want to do sendMessageAtFrontOfQueue() because we
             // want to process the message ASAP, before any other queued
             // messages.
             mH.sendMessageAtFrontOfQueue(m);
         }
     }
                                                             
     ......
}

       如果参数createIfNeeded的值等于false,那么就说明不可以为参数token所描述的Activity组件创建一个新的启动窗口,因此,这时候WindowManagerService类的成员函数setAppStartingWindow就直接返回而不往下处理了。

       另一方面,如果参数token所描述的Activity组件的窗口设置有一个主题,即参数theme的值不等于0,那么该窗口就有可能是:

       1. 背景是半透明的;

       2. 浮动窗口,即是一个壁纸窗口或者一个输入法窗口;

       3. 需要显示壁纸(背景也是半透明的)。

       由于浮动窗口和背景半透明的窗口是不可以显示启动窗口的,因此,在上述三种情况下,WindowManagerService类的成员函数setAppStartingWindow也是直接返回而不往下处理了。

       通过了上面的检查之后,这段代码就可以为参数token所描述的Activity组件创建一个启动窗口了,不过这个启动窗口不是马上就创建的,而通过一个类型为ADD_STARTING的消息来驱动创建的。这个类型为ADD_STARTING的消息是需要发送到WindowManagerService服务所运行在的线程的消息队列的头部去的。在发送这个类型为ADD_STARTING的消息之前,这段代码首先会创建一个StartingData对象,并且保存在AppWindowToken对象wtoken的成员变量startingData中,用来封装创建启动窗口所需要的数据。

       如上所述,通过调用WindowManagerService类的成员变量mH的成员函数sendMessageAtFrontOfQueue可以向WindowManagerService服务所运行在的线程的消息队列的头部发送一个类型为ADD_STARTING的消息。注意,在发送这个消息之前,这段代码还会将WindowManagerService类的成员变量mStartingIconInTransition的值设置为true,以便可以表示WindowManagerService服务正在为正在启动的Activity组件创建启动窗口。

       接下来,我们就继续分析定义在WindowManagerService内部的H类的成员函数sendMessageAtFrontOfQueue的实现,以便可以了解Activity组件的启动窗口的创建过程。

       Step 3. H.sendMessageAtFrontOfQueue

       H类的成员函数sendMessageAtFrontOfQueue是从父类Handler继承下来的,因此,这一步调用的实际上是Handler类的成员函数sendMessageAtFrontOfQueue。Handler类的成员函数sendMessageAtFrontOfQueue用来向消息队列头部的插入一个新的消息,以便这个消息可以下一次消息循环中就能得到处理。在前面Android应用程序线程消息循环模型分析一文中,我们已经分析过往消息队列发送消息的过程了,这里不再详述。

       从上面的调用过程可以知道,这一步所发送的消息的类型为ADD_STARTING,并且是向WindowManagerService服务所运行在的线程的消息队列发送的。当这个消息得到处理的时候,H类的成员函数handleMessage就会被调用,因此,接下来我们就继续分析H类的成员函数handleMessage的实现,以便可以了解Activity组件的启动窗口的创建过程。

       Step 4. H.handleMessage


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19