应用的启动到显示到屏幕是需要一定的时间的,为了提升用户的体验,google加入了启动窗口,也就是SplashScreen
SplashScreen显示流程
在应用的启动过程中,会调用到ActivityStarter的startActivityInner方法,具体可参考:Android11 应用启动流程
ActivityStarter.startActivityInner
//frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java
int startActivityInner(/*省略*/){//省略mTargetStack.startActivityLocked(mStartActivity,topStack != null ? topStack.getTopNonFinishingActivity() : null, newTask,mKeepCurTransition, mOptions);//省略
}
ActivityStack.startActivityLocked
//frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java
void startActivityLocked(/*省略*/) {//省略if (r.mLaunchTaskBehind) {//省略} else if (SHOW_APP_STARTING_PREVIEW && doShow) {//省略r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));}//省略
}
ActivityRecord.showStartingWindow
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {//省略final CompatibilityInfo compatInfo =mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);final boolean shown = addStartingWindow(packageName, theme,compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),allowTaskSnapshot(),mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal());if (shown) {mStartingWindowState = STARTING_WINDOW_SHOWN;}}
addStartingWindow
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean addStartingWindow(/*省略*/) {if (theme != 0) {AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,com.android.internal.R.styleable.Window,mWmService.mCurrentUserId);//开始获取配置的属性final boolean windowIsTranslucent = ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent, false);final boolean windowIsFloating = ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);final boolean windowShowWallpaper = ent.array.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false);final boolean windowDisableStarting = ent.array.getBoolean(com.android.internal.R.styleable.Window_windowDisablePreview, false);if (windowIsTranslucent) {//配置了windowIsTranslucent,直接返回return false;}if (windowIsFloating || windowDisableStarting) {//配置了windowDisablePreview或者windowIsFloating也返回return false;}//省略mStartingData = new SplashScreenStartingData(mWmService, pkg,theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,getMergedOverrideConfiguration());scheduleAddStartingWindow();return true;}
如果不想要这个启动窗口,就可以参考配置对应的属性。
创建SplashScreenStartingData,然后调用scheduleAddStartingWindow继续处理
scheduleAddStartingWindow
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void scheduleAddStartingWindow() {// Note: we really want to do sendMessageAtFrontOfQueue() because we// want to process the message ASAP, before any other queued// messages.if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Enqueueing ADD_STARTING");mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);}}private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
执行AddStartingWindow的run方法
private class AddStartingWindow implements Runnable {@Overridepublic void run() {synchronized (mWmService.mGlobalLock) {//省略startingData = mStartingData;//对startingData 进行了赋值}WindowManagerPolicy.StartingSurface surface = null;try {surface = startingData.createStartingSurface(ActivityRecord.this);} catch (Exception e) {Slog.w(TAG, "Exception when adding starting window", e);}}if (surface != null) {startingSurface = surface;//对startingSurface 赋值}//省略
}
SplashScreenStartingData.createStartingSurface
@OverrideStartingSurface createStartingSurface(ActivityRecord activity) {return mService.mPolicy.addSplashScreen(activity.token, activity.mUserId, mPkg, mTheme,mCompatInfo, mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId());}
mService.mPolicy是PhoneWindowManager对象
PhoneWindowManager.addSplashScreen
//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java@Overridepublic StartingSurface addSplashScreen(IBinder appToken, int userId, 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 (theme != context.getThemeResId() || labelRes != 0) {try {context = context.createPackageContextAsUser(packageName, CONTEXT_RESTRICTED,UserHandle.of(userId));//获取要启动应用的contextcontext.setTheme(theme);//设置主题} catch (PackageManager.NameNotFoundException e) {}}//省略final PhoneWindow win = new PhoneWindow(context);//创建PhoneWindowwin.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);//设置type为TYPE_APPLICATION_STARTINGfinal WindowManager.LayoutParams params = win.getAttributes();params.token = appToken;//设置tokenparams.packageName = packageName;//设置包名addSplashscreenContent(win, context);//可以配置启动窗口要显示的内容wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);view = win.getDecorView();wm.addView(view, params);//添加viewreturn view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;}
可以看出,SplashScreen的添加和系统窗口的添加是一样,都是调用addView去添加一个窗口。需要注意
- 窗口类型为TYPE_APPLICATION_STARTING
- token为ActivityRecord的token,在WMS端决定该窗口是挂在ActivityRecord下
- 返回的是一个SplashScreenSurface,也就是说前面startingSurface 是一个SplashScreenSurface对象
最后来看一下addSplashscreenContent方法
//frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
private void addSplashscreenContent(PhoneWindow win, Context ctx) {final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);final int resId = a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);a.recycle();if (resId == 0) {return;}final Drawable drawable = ctx.getDrawable(resId);if (drawable == null) {return;}// We wrap this into a view so the system insets get applied to the drawable.final View v = new View(ctx);v.setBackground(drawable);win.setContentView(v);}
通过配置windowSplashscreenContent来设置启动窗口需要显示的内容
SplashScreen退出流程
待启动的应用绘制完成之后,需要退出SplashScreen,其调用流程如下
WindowManager: at com.android.server.wm.ActivityRecord.removeStartingWindow(ActivityRecord.java:1970)
WindowManager: at com.android.server.wm.ActivityRecord.onFirstWindowDrawn(ActivityRecord.java:5346)
WindowManager: at com.android.server.wm.WindowState.performShowLocked(WindowState.java:4438)
WindowManager: at com.android.server.wm.WindowStateAnimator.commitFinishDrawingLocked(WindowStateAnimator.java:375)
从performShowLocked开始分析
//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean performShowLocked() {//省略final int drawState = mWinAnimator.mDrawState;if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {if (mAttrs.type != TYPE_APPLICATION_STARTING) {mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);//现在要显示的不是启动窗口} else {mActivityRecord.onStartingWindowDrawn();}}//省略
ActivityRecord.onFirstWindowDrawn
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {//省略removeStartingWindow();//省略}
removeStartingWindow
//frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void removeStartingWindow() {//省略final WindowManagerPolicy.StartingSurface surface;if (mStartingData != null) {surface = startingSurface;mStartingData = null;startingSurface = null;startingWindow = null;startingDisplayed = false;//省略mWmService.mAnimationHandler.post(() -> {ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Removing startingView=%s", surface);try {surface.remove();} catch (Exception e) {Slog.w(TAG_WM, "Exception when removing starting window", e);}});
}
首先对surface进行赋值并清空一些变量,startingSurface是前面通过createStartingSurface得到的SplashScreenSurface对象,然后调用SplashScreenSurface的remove方法
SplashScreenSurface.remove
@Overridepublic void remove() {final WindowManager wm = mView.getContext().getSystemService(WindowManager.class);wm.removeView(mView);}
调用removeView去移除之前显示的启动窗口。
总结
启动窗口的启动和退出也是通过addView/removeView来实现的(本文忽略了WMS端的处理)
启动
退出