Activity启动窗口(StartingWindow)的添加流程

本篇基于Android Q分析
在Activity启动的时候,Android系统会为它添加一个启动窗口,作用是在应用程序主Activity还没有显示出来的时候,它作为一个预览窗口先让用户能看到一个画面,起到缓冲的作用,我们来分析下启动窗口的添加流程

ActivityStack.startActivityLocked

void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
            boolean newTask, boolean keepCurTransition, ActivityOptions options) {
			......
			//创建正在启动的Activity的AppWindowToken,并添加到Task中
			r.createAppWindowToken();
			......
			//是否显示启动窗口
		boolean doShow = true;
            if (newTask) {
            	//如果一个Activity被设置了FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
            	//并且是在一个新的task中启动,那么这个Activity就会被当作
            	//task的首个Activity来启动,即可能会显示启动窗口
                if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                    resetTaskIfNeededLocked(r, r);
                    
                    doShow = topRunningNonDelayedActivityLocked(null) == r;
                }
            } else if (options != null && options.getAnimationType()
                    == ActivityOptions.ANIM_SCENE_TRANSITION) {
                doShow = false;
            }
            if (r.mLaunchTaskBehind) {
                ....
            } else if (SHOW_APP_STARTING_PREVIEW && doShow) {
            	//当前要启动的	Activity所在的task
                TaskRecord prevTask = r.getTaskRecord();
                //此方法是寻找当前task中设置了STARTING_WINDOW_SHOWN
                //并且不是正在finishing中并且可以显示的Activity,STARTING_WINDOW_SHOWN
                //是在下面要分析的showStartingWindow方法中设置的,
                //其实也就是获取task中已经添加了启动窗口的那个Activity,所以
                //如果是第一次启动的应用,则它的task中是没有设置了STARTING_WINDOW_SHOWN
                //的Activity的,则最终获取的prev为null
                ActivityRecord prev = prevTask.topRunningActivityWithStartingWindowLocked();
                //如果prev不为空则说明,此Activity所在的task中已经有
                //其他Activity添加过了启动窗口
                if (prev != null) {
  					//如果当前正在启动的Activity和已经添加了启动窗口的Activity
  					//不属于同一个task,则让prev为null
                    if (prev.getTaskRecord() != prevTask) {
                        prev = null;
                    }
                    //当前正在启动的Activity的nowVisible为true则prev置为null
                    else if (prev.nowVisible) {
                        prev = null;
                    }
                }
                //isTaskSwitch方法判断(r,focusedTopActivity)是否在同一个task
                //focusedTopActivity是当前系统最顶层的那个Activity
                r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));
            }
            ......
	}

这里要接着往下走两个条件,SHOW_APP_STARTING_PREVIEW 和doShow都为true,SHOW_APP_STARTING_PREVIEW这个值是定义在ActivityStack中写死了为true,所以控制启动窗口是否显示依靠doShow

首先,如果当前正在启动的Activity是在一个新的Task中启动并且被设置了FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,则调用topRunningNonDelayedActivityLocked方法判断是否可以添加启动窗口,从Launcher启动的应用都会添加这个flag

launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                     | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);

topRunningNonDelayedActivityLocked

这段代码将会遍历当前系统的所以task,从最顶层的task开始,再遍历每个task中的Activity,也从最顶层的Activity开始,直到返回满足(!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked())这几个条件的Activity

   ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
   		//mTaskHistory存储了当前系统的所有task
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            final TaskRecord task = mTaskHistory.get(taskNdx);
            //mActivities存储了task中的所有Activity
            final ArrayList<ActivityRecord> activities = task.mActivities;
            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                ActivityRecord r = activities.get(activityNdx);
                //如果此Activity不是正在结束中,并且不是延迟显示,并且
                //不为null,并且是要显示的,则返回此Activity
                if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
                    return r;
                }
            }
        }
        return null;
    }

如果通过topRunningNonDelayedActivityLocked方法的判断得到的Activity就是当前正在启动的Activity则doShow为true,表示满足显示启动窗口

showStartingWindow

	//按第一次启动应用则prev = null,newTask = true,taskSwitch = true
	void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
        showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
    }

    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
            boolean fromRecents) {
        	......
        				//添加启动窗口
        final boolean shown = addStartingWindow(packageName, theme,
                compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                allowTaskSnapshot(),
                mState.ordinal() >= RESUMED.ordinal() && mState.ordinal() <= STOPPED.ordinal(),
                fromRecents);
                //如果启动窗口成功显示则将状态置为STARTING_WINDOW_SHOWN
        if (shown) {
            mStartingWindowState = STARTING_WINDOW_SHOWN;
        }
    }

addStartingWindow

boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
        
        	......
        return mAppWindowToken.addStartingWindow(pkg, theme, compatInfo, nonLocalizedLabel,
                labelRes, icon, logo, windowFlags, transferFrom, newTask, taskSwitch,
                processRunning, allowTaskSnapshot, activityCreated, fromRecents);
    }

AppWindowToken.addStartingWindow

boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
        
        if (!okToDisplay()) {
            return false;
        }

        if (mStartingData != null) {
            return false;
        }
		//找到主窗口,等下分析
        final WindowState mainWin = findMainWindow();
        //如果能够找到一个mainWin,并且正在显示动画
        if (mainWin != null && mainWin.mWinAnimator.getShown()) {
            // 就说明当前App已经有一个可见窗口了,则不需要启动窗口
            return false;
        }
		//创建任务快照类型窗口,和启动窗口类似的作用
        final ActivityManager.TaskSnapshot snapshot =
                mWmService.mTaskSnapshotController.getSnapshot(
                        getTask().mTaskId, getTask().mUserId,
                        false /* restoreFromDisk */, false /* reducedResolution */);
                        //获取启动窗口类型以判断是否添加启动窗口,等下分析
        final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
                allowTaskSnapshot, activityCreated, fromRecents, snapshot);
		//如果是任务快照类型窗口则走createSnapshot,我们主要分析启动窗口,就不看了
        if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
            return createSnapshot(snapshot);
        }

        //启动窗口数据封装SplashScreenStartingData
        mStartingData = new SplashScreenStartingData(mWmService, pkg,
                theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                getMergedOverrideConfiguration());
       //如果获取的不是STARTING_WINDOW_TYPE_SPLASH_SCREEN则不会添加
       if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
            return false;
        }
         //准备添加启动窗口
        scheduleAddStartingWindow();
        return true;
    }

首先来看下findMainWindow的实现,从AppWindowToken和WindowToken的添加流程和排序规则我们知道AppWindowToken的mChildren存储的是Activity的所有窗口(非子窗口)WindowState,并且已经根据自定义排序规则,Z轴从低到高排列好了

WindowState findMainWindow() {
        return findMainWindow(true);
    }
 WindowState findMainWindow(boolean includeStartingApp) {
        WindowState candidate = null;
        //遍历mChildren,从层级最高的窗口开始,寻找类型为TYPE_BASE_APPLICATION
        //或者TYPE_APPLICATION_STARTING的窗口
        for (int j = mChildren.size() - 1; j >= 0; --j) {
            final WindowState win = mChildren.get(j);
            final int type = win.mAttrs.type;
            
            if (type == TYPE_BASE_APPLICATION
                    || (includeStartingApp && type == TYPE_APPLICATION_STARTING)) {
               	//正在执行退出动画
                if (win.mAnimatingExit) {
                    candidate = win;
                } else {
                    return win;
                }
            }
        }
        return candidate;
    }

总结下findMainWindow,此方法优先寻找当前AppWindowToken下的所有(非子窗口)类型为TYPE_BASE_APPLICATION或者TYPE_APPLICATION_STARTING的窗口中,没有在执行退出动画(mAnimatingExit为false)的窗口,如果没有mAnimatingExit为false的窗口,则就可以返回mAnimatingExit为true的

我们再看下getStartingWindowType这个方法实现

private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
            ActivityManager.TaskSnapshot snapshot) {
            //当前正在进行最近任务栏相关的过渡动画
        if (getDisplayContent().mAppTransition.getAppTransition()
                == TRANSIT_DOCK_TASK_FROM_RECENTS) {
            
            return STARTING_WINDOW_TYPE_NONE;
            //如果在一个新task启动,或者此Activity所在的进程还未运行,或者
            //(此Activity的task和启动它的Activity不在同一个task并且此
            //Activity还为创建),满足其中一个条件说明需要启动窗口
        } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {
            return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
            //此此Activity的task和启动它的Activity不在同一个task 
            //并且允许创建任务快照窗口
        } else if (taskSwitch && allowTaskSnapshot) {
        
            if (mWmService.mLowRamTaskSnapshotsAndRecents) {
                //在低RAM设备上使用启动窗口代替
                //任务快照窗口。 
                return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
            }
            //这里是使用任务快照窗口还是启动窗口的一些条件判断
            return snapshot == null ? STARTING_WINDOW_TYPE_NONE
                    : snapshotOrientationSameAsTask(snapshot) || fromRecents
                            ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN;
        } else {
        	//不需要启动窗口
            return STARTING_WINDOW_TYPE_NONE;
        }
    }

总结下getStartingWindowType方法,有三种情况会为启动的Activity添加启动窗口:
1.在一个新task中启动此Activity
2.此Activity所在的进程还未运行
3.此Activity的task和启动它的Activity不在同一个task并且此Activity还为创建

最常见的添加启动窗口的情况就是在Launcher上点击一个应用启动

继续分析代码

scheduleAddStartingWindow

post了一个Runnable,使用postAtFrontOfQueue的方式,插入队列头部以加快启动窗口添加速度

void scheduleAddStartingWindow() {
        if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Enqueueing ADD_STARTING");
            mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
        }
    }

mAddStartingWindow

private final Runnable mAddStartingWindow = new Runnable() {

        @Override
        public void run() {
        
            final StartingData startingData;
            synchronized (mWmService.mGlobalLock) {
                ......

            WindowManagerPolicy.StartingSurface surface = null;
            try {
                surface = startingData.createStartingSurface(AppWindowToken.this);
            } catch (Exception e) {
               
            }
           	......
        }
    };

StartingData.createStartingSurface

调用的StartingData实现类SplashScreenStartingData的方法

@Override
    StartingSurface createStartingSurface(AppWindowToken atoken) {
        return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
                mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
                mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
    }

直接调到WindowManagerPolicy的实现类PhoneWindowManager的addSplashScreen方法,此方法中会窗口启动窗口并添加到WMS中,启动窗口的创建和我们之前分析的Dialog的创建流程差不多,Android的窗口创建添加流程其实都差不多的,认真分析几个类型窗口的创建差不多就能明白窗口机制了

@Override
    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
        if (!SHOW_SPLASH_SCREENS) {
            return null;
        }
        if (packageName == null) {
            return null;
        }

        WindowManager wm = null;
        View view = null;

        try {
            Context context = mContext;
            
            final Context displayContext = getDisplayContext(context, displayId);
            ......
			//创建PhoneWindow
            final PhoneWindow win = new PhoneWindow(context);
            //告诉PhoneWindow它是一个启动窗口类型
            win.setIsStartingWindow(true);

            CharSequence label = context.getResources().getText(labelRes, null);
            // Only change the accessibility title if the label is localized
            if (label != null) {
                win.setTitle(label, true);
            } else {
                win.setTitle(nonLocalizedLabel, false);
            }
			//设置窗口类型为TYPE_APPLICATION_STARTING
            win.setType(
                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);

            synchronized (mWindowManagerFuncs.getWindowManagerLock()) {

                if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) {
                    windowFlags |= FLAG_SHOW_WHEN_LOCKED;
                }
            }

			//设置各种flag,不能获取焦点,不能接收事件等
            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);
			//设置宽高为MATCH_PARENT
            win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                    WindowManager.LayoutParams.MATCH_PARENT);
			
            final WindowManager.LayoutParams params = win.getAttributes();
            //重点注意,启动窗口用的是当前这个Activity的tAppWindowToken
            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;
            }
			//设置title为Splash Screen+包名,经常看log的应该对这句log很熟悉
			//当我们从launcher启动Activity时log中很容易发现这句话,这就是启动窗口
			//的标志
            params.setTitle("Splash Screen " + packageName);
            //此方法中会给启动窗口设置资源文件,并调用setContentView设置到
            //PhoneWindow的DecorView中
            addSplashscreenContent(win, context);

            wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
            view = win.getDecorView();
            //添加到WMS中
            wm.addView(view, params);

            // Only return the view if it was successfully added to the
            // window manager... which we can tell by it having a parent.
            return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
        } catch (WindowManager.BadTokenException e) {
            .....
        }
        return null;
    }

我前面几篇文章对Android窗口机制,Token机制,以及窗口Z-order分析的比较多了,我们就直接来到WMS看如果窗口类型为启动窗口,WMS会做些什么

wm.addView

经过几篇窗口机制文章分析,已经对此方法非常熟悉了,我们直接找启动窗口相关的代码

public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
            //Activity在启动时就创建了AppWindowToken,并将AppWindowToken
            //添加到了displayContent,前面说了启动窗口用的当前Activity的
            //AppWindowToken,所以此处token能够获取到
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
			if (token == null) {
			
						......
						
			}else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
				//当前Activity的AppWindowToken
                atoken = token.asAppWindowToken();
                if (atoken == null) {
                    	.....
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                  		.....
                    return WindowManagerGlobal.ADD_APP_EXITING;
                    //如果是启动窗口类型,并且当前Activity的AppWindowToken
                    //的成员变量startingWindow不为空则说明启动窗口是重复添加
                } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
                   			.....
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
				//创建启动窗口的WindowState,在WindowState构造函数中会
				//获取启动窗口的mBaseLayer用作Z-order排序
				final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
					//添加到mWindowMap
					mWindowMap.put(client.asBinder(), win);
					......
 			final AppWindowToken aToken = token.asAppWindowToken();
 			//这里才给当前Activity的AppWindowToken的startingWindow赋值,
 			//所以当aToken.startingWindow,不为空则一定是添加过了启动窗口
            if (type == TYPE_APPLICATION_STARTING && aToken != null) {
                aToken.startingWindow = win;
            }
			//将启动窗口添加到当前AppWindowToken中,并且会按照自定义规则排序
			win.mToken.addWindow(win);
			......
}

首先给启动窗口创建WindowState时会根据窗口类型获取一个mBaseLayer,因为启动窗口属于应用窗口类型,根据前面文章分析,应用类型窗口mBaseLayer统一为21000,
我们再简单看下窗口排序规则win.mToken.addWindow(win),添加启动窗口时我们分析了启动窗口的token是当前启动的Activity的AppWindowToken,AppWindowToken的addWindow方法直接调用的是父类WindowToken方法

void addWindow(final WindowState win) {
		//如果是子窗口,不会添加到WindowToken中,子窗口是添加到父窗口
		//的WindowState中
        if (win.isChildWindow()) {
            
            return;
        }
        //如果已经添加过了则不再添加
        if (!mChildren.contains(win)) {
            
            addChild(win, mWindowComparator);
            mWmService.mWindowsChanged = true;
           
        }
    }

这里的addChild会调用到父类WindowContainer的addChild方法,有两个参数,一个窗口WindowState,一个自定义排序器,我们看下自定义的排序规则

 	/**
     比较当前token的两个子窗口,如果新添加的窗口的Z-order小于已存在的窗口,
     则返回-1,否则返回1
     */
 private final Comparator<WindowState> mWindowComparator =
            (WindowState newWindow, WindowState existingWindow) -> {
        final WindowToken token = WindowToken.this;
        if (newWindow.mToken != token) {
            throw new IllegalArgumentException("newWindow=" + newWindow
                    + " is not a child of token=" + token);
        }

        if (existingWindow.mToken != token) {
            throw new IllegalArgumentException("existingWindow=" + existingWindow
                    + " is not a child of token=" + token);
        }

        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
    };

isFirstChildWindowGreaterThanSecond这个方法在AppWindowToken中实现了

@Override
    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
            WindowState existingWindow) {
        final int type1 = newWindow.mAttrs.type;
        final int type2 = existingWindow.mAttrs.type;
		//TYPE_BASE_APPLICATION类型窗口的Z-order应该在AppWindowToken
		//所有窗口之下
        if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {
            return false;
        } else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {
            return true;
        }

        //启动类型窗口排在AppWindowToken所有窗口之上
        if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {
            return true;
        } else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {
            return false;
        }
        return true;
    }

其实AppWindowToken对自己内部窗口的排序规则定义很简单,TYPE_BASE_APPLICATION类型窗口在所有窗口之下,TYPE_APPLICATION_STARTING类型窗口在所有窗口之上,其他窗口就按顺序添加,因为AppWindowToken下的窗口都是应用类型,他们的mBaseLayer都是21000,所以需定义这个规则来排序,好了我们到现在已经清楚了Android启动窗口的创建与添加流程,最后在WindowContainer的addChild方法中将启动窗口成功按照Z-order的顺序添加到了WMS中,之后此方法中最终会调用setLayer方法到SurfaceAnimator的setLayer方法,通过Transaction的setLayer方法将Z-order设置到SurfaceFlinger中去,Transaction这个类专门用来与SurfaceFliner进行沟通,它提供了很多对窗口进行控制的方法,比如控制窗口的显示与隐藏,窗口位置,窗口矩阵,窗口颜色,窗口裁剪等等

到此为止一个比较完整的启动窗口创建添加流程就分析完了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值