显示Activity组件的启动窗口(Starting Window)的过程分析

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

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

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

注意,Activity组件的启动窗口是由ActivityManagerService服务来控制是否显示的,也就是说,Android应用程序是无法决定是否要要Activity组件显示启动窗口的。


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

Activity组件在启动的过程中,会调用ActivityStack类的成员函数startActivityLocked。注意,在调用ActivityStack类的成员函数startActivityLocked的时候,Actvitiy组件还处于启动的过程,即它的窗口尚未显示出来,不过这时候ActivityManagerService服务会检查是否需要为正在启动的Activity组件显示一个启动窗口。如果需要的话,那么ActivityManagerService服务就会请求WindowManagerService服务为正在启动的Activity组件设置一个启动窗口。

在这里插入图片描述

Step 1. ActivityStack.startActivityLocked

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);  
        }  
    }  
  
    ......  
}  

参数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创建一个窗口令牌。

在变量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组件的操作。

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


Step 2. WindowManagerService.setAppStartingWindow

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组件设置的启动窗口是无法显示的。

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;  
            }  
        
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值