一、启动窗口的类型
private static final int STARTING_WINDOW_TYPE_NONE = 0; //不使用启动窗口
private static final int STARTING_WINDOW_TYPE_SNAPSHOT = 1; //应用热启动,启动窗口是activity的一张截图
private static final int STARTING_WINDOW_TYPE_SPLASH_SCREEN = 2; //应用冷启动,启动窗口是应用配置的一张图片
二、启动窗口的意义
应用有三种启动状态:冷启动、温启动、热启动
冷启动:应用自设备开机后首次启动;系统终止应用后,应用首次启动。时间开销最大。
热启动:应用的所有Activity仍驻留在内存中,系统的全部工作只是将Activity重新带到前台。时间开销最小。
温启动:应用进程已经存在,但应用必须通过调用onCreate()从头重新创建Activity。时间开销中间。
应用冷启动时,自点击home launcher的应用Icon起,到应用完成第一次绘制,系统进程、应用进程要完成大量的初始化工作,非常费时。假如没有starting window,这一过程给用户的感受就是:这应用好卡,点击之后3秒钟过去了,还没反应;或者,我是不是没点到应用图标上。starting window的意义就在于,用户点击应用Icon之后,启动窗口会立即打开,等应用完成第一次绘制后,启动窗口被系统销毁,纵享丝滑。
三、启动窗口的流程
3.1 ActivityStack.SHOW_APP_STARTING_PREVIEW
// Set to false to disable the preview that is shown while a new activity
// is being started.
private static final boolean SHOW_APP_STARTING_PREVIEW = true; // 1. starting window的总开关
...
void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
Task rTask = r.getTask();
final boolean allowMoveToFront = options == null || !options.getAvoidMoveToFront();
final boolean isOrhasTask = rTask == this || hasChild(rTask);
...
} else 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.
Task prevTask = r.getTask();
ActivityRecord prev = prevTask.topActivityWithStartingWindow();
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.getTask() != prevTask) {
prev = null;
}
// (2) The current activity is already displayed.
else if (prev.nowVisible) {
prev = null;
}
}
r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity)); // 2. 调用ActivityRecord默认方法,两个类在同一个package,无需import
}
} else {
// If this is the first activity, don't do any fancy animations,
// because there is nothing for it to animate on top of.
ActivityOptions.abort(options);
}
}
注释1:该boolean值是ActivityStack类的全局变量,正如英文注释,此布尔值是startingwindow的总开关。
注释2:r是startActivityLocked方法的一个形参,是ActivityRecord类型的变量,调用ActivityRecord.showStartingWindow()方法。
3.1 ActivityRecord.showStartingWindow()
static final int STARTING_WINDOW_NOT_SHOWN = 0;
static final int STARTING_WINDOW_SHOWN = 1;
static final int STARTING_WINDOW_REMOVED = 2;
int mStartingWindowState = STARTING_WINDOW_NOT_SHOWN; // 1. 启动窗口的状态
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
}
if (pendingOptions != null
&& pendingOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
// Don't show starting window when using shared element transition.
return;
}
final CompatibilityInfo compatInfo =
mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
final boolean shown = addStartingWindow(packageName, theme,
prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
allowTaskSnapshot(),
mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal()); // 2. 调用本类方法
if (shown) {
mStartingWindowState = STARTING_WINDOW_SHOWN; // 3. 置启动窗口的状态
}
}
注释1:全局变量,访问权限为default。描述启动窗口的状态,一共有3种状态:默认的not_shown;添加成功后的shown;移除后的removed。
注释2:调用本类的addStartingWindow方法,返回值类型为boolean值,代表启动窗口是否添加成功。
注释3:启动窗口添加成功后,其状态设置为shown。
3.3 ActivityRecord.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) {
...
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, snapshot);
if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
if (isActivityTypeHome()) {
// The snapshot of home is only used once because it won't be updated while screen
// is on (see {@link TaskSnapshotController#screenTurningOff}).
mWmService.mTaskSnapshotController.removeSnapshotCache(task.mTaskId);
if ((mDisplayContent.mAppTransition.getTransitFlags()
& WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION) == 0) {
// Only use snapshot of home as starting window when unlocking directly.
return false;
}
}
return createSnapshot(snapshot); // 1. 应用热启动
}
// If this is a translucent 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.
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
if (theme != 0) {
AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
com.android.internal.R.styleable.Window,
mWmService.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); // 2. 查看启动Activity主题的windowIsTranslucent属性
final boolean windowIsFloating = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false); // 3. 查看windowIsFloating属性
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); // 4.查看windowDisablePreview属性
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
windowIsTranslucent, windowIsFloating, windowShowWallpaper);
if (windowIsTranslucent) {
return false;
}
if (windowIsFloating || windowDisableStarting) {
return false;
}
if (windowShowWallpaper) {
if (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 (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) { // 5. 代码走到这里,说明启动窗口的类型不是snapshot。如果也不是splash,那就只能是none.
return false;
}
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
mStartingData = new SplashScreenStartingData(mWmService, pkg, // 6. mStartingData赋值
theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
// getMergedOverrideConfiguration());
getMergedOverrideConfiguration(), overrideTheme);
scheduleAddStartingWindow(); // 7. 发送消息
return true;
}
注释1:return createSnapshot(snapshot);启动窗口类型为snapshot,给mStartingData赋值snapshotdata,然后发送消息。
private boolean createSnapshot(ActivityManager.TaskSnapshot snapshot) {
if (snapshot == null) {
return false;
}
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData");
mStartingData = new SnapshotStartingData(mWmService, snapshot); // 给mStartingData赋值snapshotdata,类比注释6
scheduleAddStartingWindow(); // 对应注释7
return true;
}
注释2、3、4:代码走到这,说明是应用冷启动。查看应用launcher activity的主题,如果其主题将这3个属性中的任何一个置为true,冷启动的时候,不会创建splash screen。
注释5:代码走到这,说明启动窗口的type既不是snapshot,也不是splashscreen,只能是none,直接return false即可,即根本没有启动窗口。
注释6 、7类比注释1。
3.4 ActivityRecord.scheduleAddStartingWindow()
private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
...
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); // 1. Handler机制,跨线程通信。
}
}
private class AddStartingWindow implements Runnable {
@Override
public void run() {
// Can be accessed without holding the global lock
final StartingData startingData;
synchronized (mWmService.mGlobalLock) {
// There can only be one adding request, silly caller!
mWmService.mAnimationHandler.removeCallbacks(this);
if (mStartingData == null) {
// Animation has been canceled... do nothing.
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"startingData was nulled out before handling"
+ " mAddStartingWindow: %s", ActivityRecord.this);
return;
}
///M: Check starting surface aleady add or not.
if (startingSurface != null) {
Slog.v(TAG_WM, "already has a starting surface!!!");
return;
}
startingData = mStartingData; // 2. 可能有两种类型。
}
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
this, startingData);
WindowManagerPolicy.StartingSurface surface = null;
try {
surface = startingData.createStartingSurface(ActivityRecord.this); // 3. 可能两种类型
} catch (Exception e) {
Slog.w(TAG, "Exception when adding starting window", e);
}
if (surface != null) {
boolean abort = false;
synchronized (mWmService.mGlobalLock) {
// If the window was successfully added, then we need to remove it.
if (mStartingData == null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Aborted starting %s: startingData=%s",
ActivityRecord.this, mStartingData);
startingWindow = null;
mStartingData = null;
abort = true;
} else {
startingSurface = surface;
}
if (!abort) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Added starting %s: startingWindow=%s startingView=%s",
ActivityRecord.this, startingWindow, startingSurface);
}
}
if (abort) {
surface.remove();
}
} else {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",
ActivityRecord.this);
}
}
}
注释1: mAddStartingWindow是AddStartingWindow类型的变量,AddStartingWindow实现了Runnable接口,执行mAddStartingWindow的run()方法。
注释2、3:可能有snapshot、splash两种类型,下面主要介绍splash的情况。
3.5 SplashScreenStartingData.createStartingSurface()
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mPolicy.addSplashScreen(activity.token, mPkg, mTheme, mCompatInfo,
mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
// mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId());
mMergedOverrideConfiguration, activity.getDisplayContent().getDisplayId(),
mOverrideTheme);
}
3.6 PhoneWindowManager.addSplashScreen()
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;
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen " + packageName
+ ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
+ Integer.toHexString(theme));
// Obtain proper context to launch on the right display.
final Context displayContext = getDisplayContext(context, displayId);
if (displayContext == null) {
// Can't show splash screen on requested display, so skip showing at all.
return null;
}
context = displayContext;
if (theme != context.getThemeResId() || labelRes != 0) {
try {
context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
context.setTheme(theme);
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
}
if (overrideConfig != null && !overrideConfig.equals(EMPTY)) {
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen: creating context based"
+ " on overrideConfig" + overrideConfig + " for splash screen");
final Context overrideContext = context.createConfigurationContext(overrideConfig);
overrideContext.setTheme(theme);
final TypedArray typedArray = overrideContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
if (resId != 0 && overrideContext.getDrawable(resId) != null) {
// We want to use the windowBackground for the override context if it is
// available, otherwise we use the default one to make sure a themed starting
// window is displayed for the app.
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen: apply overrideConfig"
+ overrideConfig + " to starting window resId=" + resId);
context = overrideContext;
}
typedArray.recycle();
}
final PhoneWindow win = new PhoneWindow(context);
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);
}
win.setType(
WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
// Assumes it's safe to show starting windows of launched apps while
// the keyguard is being hidden. This is okay because starting windows never show
// secret information.
// TODO(b/113840485): Occluded may not only happen on default display
if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) {
windowFlags |= FLAG_SHOW_WHEN_LOCKED;
}
}
// Force the window flags: this is a fake window, so it is not really
// touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
// flag because we do know that the next window will take input
// focus, so we want to get the IME window up on top of us right away.
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);
win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
final WindowManager.LayoutParams params = win.getAttributes();
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.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
if (!compatInfo.supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
}
params.setTitle("Splash Screen " + packageName);
// addSplashscreenContent(win, context);
boolean contentAdded = addSplashscreenContent(win, context);
wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
view = win.getDecorView();
if ((context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
!= Configuration.UI_MODE_NIGHT_YES) {
PhoneWindowManagerInjector.addStartingWindow(context, view, win, label != null ? label : nonLocalizedLabel);
}
if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
+ packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));
wm.addView(view, params); // 1. 添加view
// 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) {
// ignore
Log.w(TAG, appToken + " already running, starting window not displayed. " +
e.getMessage());
} catch (RuntimeException e) {
// don't crash if something else bad happens, for example a
// failure loading resources because we are loading from an app
// on external storage that has been unmounted.
Log.w(TAG, appToken + " failed creating starting window", e);
} finally {
if (view != null && view.getParent() == null) {
Log.w(TAG, "view not successfully added to wm, removing view");
wm.removeViewImmediate(view);
}
}
return null;
}
注释1:wm.addview。wm是WindowManager类型的,addview()方法的真正实现在WindowManagerImpl类中。
3.7 WindowManagerImpl.addview()
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
3.8 WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId); // 1. 调用
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
index = findViewLocked(view, false);
if (index >= 0) {
Log.e(TAG, "BadTokenException or InvalidDisplayException, clean up.");
removeViewLocked(index, true);
}
throw e;
}
}
}
注释1:调用ViewRootImpl的setView()方法
3.9 ViewRootImpl.setView()
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls); // 1. 跨进程通信
setFrame(mTmpFrame);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
inputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
3.10 Session.addToDisplayAsUser()
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, userId);
}