什么是Activity的启动窗口?为什么存在这样的一个窗口?什么是TaskSnapshot?啥也不说了,直接带着这些个问题开始本篇的闲聊。
我们先来一个动图直观的了解一下。
首先我们点击启动我们的测试demo,看见蓝色的界面没有,那就是Activity的启动窗口。Android在Activity启动的时候,当应用界面还未显示出来时,会先给Activity添加的一个启动窗口。这个启动窗口显示的效果受应用theme设置的相关内容影响,比如本例中的效果可以使用两种不同的方式达到,
方法一:
- @color/blue
方法二:
- @color/blue
推荐使用前者,至于原因,实际开发中您用一下就知道了,这里就不赘述了。现在我们应该清除什么是Activity的启动窗口了吧。至于Android添加这个启动窗口的原因,我们也不言而喻了。我们仔细想想,如果我们将这个启动窗口的内容设置成我们应用首界面第一帧图片,那么我们感官上应用启动的速度是不是飞快?体现效果是不是倍棒?Android上的OTaskSnapshot便是Google对这一功能的升级,直接优化了Android多任务切换时的体验效果,直接体现了google对于Android系统的目标“run fast, smooth, and responsively”。
FrameWork层入口:frameworks/base/services/core/java/com/android/server/am/ActivityRecord.showStartingWindow
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
}
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean fromRecents) {
if (mWindowContainerController == null) {
return;
}
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
}
final CompatibilityInfo compatInfo =
service.compatibilityInfoForPackageLocked(info.applicationInfo);
final boolean shown = mWindowContainerController.addStartingWindow(packageName, theme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal(),
fromRecents);
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN;
}
}
这里逻辑不难,主要就是通过AppWindowContainerController的addStartingWindow函数添加启动窗口。关于AppWindowContainerController这个类的作用,后续分析WMS和AMS联系的时候会详细介绍。我们来看下addStartingWindow方法:
public 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) {
synchronized(mWindowMap) {
//进行一系列的条件检查,符合基本条件才能创建starting window
if (mContainer == null) {
Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
return false;
}
// 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 (!mContainer.okToDisplay()) {
return false;
}
if (mContainer.startingData != null) {
return false;
}
final WindowState mainWin = mContainer.findMainWindow();
if (mainWin != null && mainWin.mWinAnimator.getShown()) {
// App already has a visible window...why would you want a starting window?
return false;
}
//任务快照,我们可以简单理解为我们应用程序退到后台的时候,Android为我们的应用截取的一张图片。
//我们通过Recents看到的便是这个TaskSnapshot截取的图片内容。
final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
false /* restoreFromDisk */, false /* reducedResolution */);
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, fromRecents, snapshot);
//当获取的starting window类型为STARTING_WINDOW_TYPE_SNAPSHOT时,使用snapshot内容作为启动窗口内容
//一般我们从Recents界面进入应用时,或者应用被压入后台再次点击桌面图标进入应用时走该流程。
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
return createSnapshot(snapshot);
}
//当我们应用theme中设置窗口背景透明,窗口类型float类型(一般dialog的窗口设置为float),或者需要显示壁纸,
//又或者直接禁用启动窗口时,我们都不会进行启动窗口的显示
if (theme != 0) {
AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
com.android.internal.R.styleable.Window, mService.mCurrentUserId);
if (ent == null) {
// Whoops! App doesn't exist. Um. Okay. We'll just pretend like we didn't
// see that.
return false;
}
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 (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Translucent=" + windowIsTranslucent
+ " Floating=" + windowIsFloating
+ " ShowWallpaper=" + windowShowWallpaper);
if (windowIsTranslucent) {
return false;
}
if (windowIsFloating || windowDisableStarting) {
return false;
}
if (windowShowWallpaper) {
if (mContainer.getDisplayContent().mWallpaperController.getWallpaperTarget()
== null) {
// If this theme is requesting a wallpaper, and the wallpaper
// is not currently visible, then this effectively serves as
// an opaque window and our starting window transition animation
// can still work. We just need to make sure the starting window
// is also showing the wallpaper.
windowFlags |= FLAG_SHOW_WALLPAPER;
} else {
return false;
}
}
}
if (mContainer.transferStartingWindow(transferFrom)) {
return true;
}
// There is no existing starting window, and we don't want to create a splash screen, so
// that's it!
if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
return false;
}
if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme,
compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
mContainer.getMergedOverrideConfiguration());
scheduleAddStartingWindow();
}
return true;
}
系统给定三种类型的启动窗口:
一般情况下如果我们可以通过设置theme的windowDisablePreview的属性为true禁止显示starting window,如果我们设置了应用窗口背景色透明或者使用了float属性为true类型的窗口时,这个时候启动应用也是不会添加starting window的。对于启动窗口的内容何时使用snapshot内容何时受应用的theme影响。我们可以以应用的启动方式不同来做一判断,一般应用冷启动和温启动的时候starting window的显示内容受theme影响,应用通过Recent启动或者热启动时,starting window会显示snapshot的内容。
Android的启动窗口整体理解起来不算很难,我们也可以使用这一原理优化我们的应用启动体验。我们可以通过设置应用启动Activity的背景为应用启动之后的界面,这样系统在展示启动窗口的时候,我们从视觉效果上就已经认为它已经成功启动了,当然这不是优化我们应用启动的首选方案,最终我们还是要优化我们的相关界面的代码,至于如何优化,您可以从ActivityManagerService解读之Activity启动时间闲聊--优雅的优化我们应用的启动时间一文中寻找一点思路。本篇只简单介绍了Android的starting window,对于其中涉及的TaskSnapshop内容并未过多介绍,由于该部分内容更多和Android View绘制显示相关,后续整理到该部分内容时再做详细介绍。