根据 Android V app 冷启动 (1) Activity生命周期 的分析,在第一阶段启动中,当构建完窗口层级后,添加了启动窗口,本文就来分析这个课题。
WMCore 添加启动窗口
java
// Task.java
// r 是要启动的 activity
// topTask 指的是 launcher root task
// newTask 为 true
// isTaskSwitch 为 true
// options 启动 activity 参数,不为 null
// sourceRecord 指的是 launcher
void startActivityLocked(ActivityRecord r, @Nullable Task topTask, boolean newTask,
boolean isTaskSwitch, ActivityOptions options, @Nullable ActivityRecord sourceRecord) {
// ...
final Task activityTask = r.getTask();
// ...
task = activityTask;
// Slot the activity into the history root task and proceed
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
+ "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
// ...
// 检测是否需要显示启动窗口
boolean doShow = true;
if (newTask) {
// 从 start u0 的 log 看,是包含这个 flag 的
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
// reset task 指的是把相关的 activity 移动到当前 task,或者把不相关 actvity 移出 task
// 本文分析的案例不涉及
resetTaskIfNeeded(r, r);
// reset Task 后,如果 Task 的 top-non-delayed-non-finishing activity 仍然是要启动的 activity
// 那么,需要显示启动窗口
doShow = topRunningNonDelayedActivityLocked(null) == r;
}
} else if (options != null && options.getAnimationType()
== ActivityOptions.ANIM_SCENE_TRANSITION) {
// ...
}
// 启动 activity 的参数中也可以指定不需要显示启动窗口
if (options != null && options.getDisableStartingWindow()) {
doShow = false;
}
if (r.mLaunchTaskBehind) {
} 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 baseTask = r.getTask();
// Task 中前一个有启动窗口数据的 ActivityRecord
final ActivityRecord prev = baseTask.getActivity(
a -> a.mStartingData != null && a.showToCurrentUser());
mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
isTaskSwitch, sourceRecord);
}
}
Task 检测了是否需要为启动的 activity 显示启动窗口,如果是,交给启动窗口控制器 StartingSurfaceController 来显示
java
// StartingSurfaceController.java
void showStartingWindow(ActivityRecord target, ActivityRecord prev,
boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
if (mDeferringAddStartingWindow) {
// ...
} else {
// 直接交给 ActivityRecord show start window
target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */,
source);
}
}
java
// ActivityRecord.java
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean startActivity, ActivityRecord sourceRecord) {
showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
sourceRecord, null /* candidateOptions */);
}
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
ActivityOptions candidateOptions) {
if (mTaskOverlay) {
// We don't show starting window for overlay activities.
return;
}
final ActivityOptions startOptions = candidateOptions != null
? candidateOptions : mPendingOptions;
if (startOptions != null
&& startOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
// Don't show starting window when using shared element transition.
return;
}
// 解析 splash screen theme
final int splashScreenTheme = startActivity ? getSplashscreenTheme(startOptions) : 0;
final int resolvedTheme = evaluateStartingWindowTheme(prev, packageName, theme,
splashScreenTheme);
// 启动窗口是显示图标,还是纯色
// false
mSplashScreenStyleSolidColor = shouldUseSolidColorSplashScreen(sourceRecord, startActivity,
startOptions, resolvedTheme);
// 这个表示 app 端 activity 是否已经创建
// false
final boolean activityCreated =
mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal();
// If this activity is just created and all activities below are finish, treat this
// scenario as warm launch.
final boolean newSingleActivity = !newTask && !activityCreated
&& task.getActivity((r) -> !r.finishing && r != this) == null;
// 执行下一步添加启动窗口
final boolean scheduled = addStartingWindow(packageName, resolvedTheme,
prev, newTask || newSingleActivity, taskSwitch, processRunning,
allowTaskSnapshot(), activityCreated, mSplashScreenStyleSolidColor, allDrawn);
if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) {
Slog.d(TAG, "Scheduled starting window for " + this);
}
}
boolean addStartingWindow(String pkg, int resolvedTheme, ActivityRecord from, boolean newTask,
boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot,
boolean activityCreated, boolean isSimple,
boolean activityAllDrawn) {
if (!okToDisplay()) {
return false;
}
if (hasStartingWindow()) {
return false;
}
// 目前 ActivityRecord 下还没有窗口 WindowState
final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
if (mainWin != null && mainWin.isDrawn()) {
// App already has a visible window...why would you want a starting window?
return false;
}
// app 冷启动,系统还没有 Task 的快照
// null
final TaskSnapshot snapshot =
mWmService.mTaskSnapshotController.getSnapshot(task.mTaskId, task.mUserId,
false /* restoreFromDisk */, false /* isLowResolution */);
// 获取启动窗口类型
// app 冷启动,启动窗口类型为 STARTING_WINDOW_TYPE_SPLASH_SCREEN
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, activityAllDrawn, snapshot);
// false
final boolean useLegacy = type == STARTING_WINDOW_TYPE_SPLASH_SCREEN
&& mWmService.mStartingSurfaceController.isExceptionApp(packageName, mTargetSdk,
() -> {
ActivityInfo activityInfo = intent.resolveActivityInfo(
mAtmService.mContext.getPackageManager(),
PackageManager.GET_META_DATA);
return activityInfo != null ? activityInfo.applicationInfo : null;
});
// 把所有数据转化为一个 int
final int typeParameter = StartingSurfaceController
.makeStartingWindowTypeParameter(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, isSimple, useLegacy, activityAllDrawn,
type, isIconStylePreferred(resolvedTheme), packageName, mUserId);
// ...
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SplashScreenStartingData");
// 创建启动窗口数据
mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
// 执行下一步添加启动窗口
scheduleAddStartingWindow();
return true;
}
ActivityRecord 解析数据,并把所有数据包装到 mStartingData,然后执行下一步添加启动窗口。
java
// ActivityRecord.java
private void scheduleAddStartingWindow() {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
this, mStartingData);
// 通过 StartingData 来创建启动窗口 surface
// 但是得到的并不是一个真正的 surface,而是一个象征 surface 的数据包装类而已
// mStartingData 的类型为 SplashScreenStartingData
mStartingSurface = mStartingData.createStartingSurface(ActivityRecord.this);
if (mStartingSurface != null) {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
"Added starting %s: startingWindow=%s startingView=%s",
ActivityRecord.this, mStartingWindow, mStartingSurface);
} else {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Surface returned was null: %s",
ActivityRecord.this);
}
}
java
// SplashScreenStartingData.java
class SplashScreenStartingData extends StartingData {
@Override
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
activity, mTheme);
}
}
java
// StartingSurfaceController.java
StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {
final Task task = activity.getTask();
final TaskOrganizerController controller =
mService.mAtmService.mTaskOrganizerController;
// 最终由 TaskOrganizerController 执行添加启动窗口
if (task != null && controller.addStartingWindow(task, activity, theme,
null /* taskSnapshot */)) {
// StartingSurface 只是一个数据包装类,并不是真正的 surface
return new StartingSurface(task, controller.getTaskOrganizer());
}
return null;
}
java
// TaskOrganizerController.java
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
TaskSnapshot taskSnapshot) {
final Task rootTask = task.getRootTask();
if (rootTask == null || activity.mStartingData == null) {
return false;
}
final ITaskOrganizer lastOrganizer = getTaskOrganizer();
if (lastOrganizer == null) {
return false;
}
// 把所有启动窗口的相关数据,都保存到 StartingWindowInfo
final StartingWindowInfo info = task.getStartingWindowInfo(activity);
if (launchTheme != 0) {
info.splashScreenThemeResId = launchTheme;
}
info.taskSnapshot = taskSnapshot;
info.appToken = activity.token;
try {
// 通知 WMShell 添加启动窗口
lastOrganizer.addStartingWindow(info);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskStart callback", e);
return false;
}
return true;
}
兜兜转转,最终是把启动窗口数据包装到 StartingWindowInfo,然后发送给 WMShell,让其完成添加启动窗口的任务。
WMShell 创建启动窗口
java
// ShellTaskOrganizer.java
public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
mStartingWindow.addStartingWindow(info);
}
}
java
// StartingWindowController.java
public void addStartingWindow(StartingWindowInfo windowInfo) {
// 注意,这是在 splash screen thread 中执行的
mSplashScreenExecutor.execute(() -> {
// WMShell 添加启动窗口的 trace
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
// 1. 计算启动窗口类型
// 此时返回的是 STARTING_WINDOW_TYPE_SPLASH_SCREEN
final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
windowInfo);
final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
// ...
} else if (isSplashScreenType(suggestionType)) {
// 2. 添加启动窗口
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
// ...
}
if (suggestionType != STARTING_WINDOW_TYPE_NONE
&& suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
int taskId = runningTaskInfo.taskId;
int color = mStartingSurfaceDrawer
.getStartingWindowBackgroundColorForTask(taskId);
if (color != Color.TRANSPARENT) {
mTaskBackgroundColors.append(taskId, color);
}
if (mTaskLaunchingCallback != null && isSplashScreenType(suggestionType)) {
mTaskLaunchingCallback.accept(taskId, suggestionType, color);
}
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
});
}
java
// StartingSurfaceDrawer.java
/**
* A class which able to draw splash screen or snapshot as the starting window for a task.
*/
public class StartingSurfaceDrawer {
void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowType int suggestType) {
mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
}
java
// SplashScreenWindowCreator.java
void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowInfo.StartingWindowType int suggestType) {
final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
? windowInfo.targetActivityInfo
: taskInfo.topActivityInfo;
if (activityInfo == null || activityInfo.packageName == null) {
return;
}
// replace with the default theme if the application didn't set
final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
suggestType, mDisplayManager);
if (context == null) {
return;
}
// 构建布局参数,为了通过 WindowManager 添加启动窗口 View
final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
context, windowInfo, suggestType, activityInfo.packageName,
suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);
final int displayId = taskInfo.displayId;
final int taskId = taskInfo.taskId;
final Display display = getDisplay(displayId);
// 注意下面一段注释,解释了添加启动窗口 View 的流程
// TODO(b/173975965) tracking performance
// Prepare the splash screen content view on splash screen worker thread in parallel, so the
// content view won't be blocked by binder call like addWindow and relayout.
// 1. Trigger splash screen worker thread to create SplashScreenView before/while
// Session#addWindow.
// 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
// traversal, which will call Session#relayout on splash screen thread.
// 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
// the same time the splash screen thread should be executing Session#relayout. Blocking the
// traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
// Record whether create splash screen view success, notify to current thread after
// create splash screen view finished.
// SplashScreenViewSupplier 是为了缓存 worker thread 中创建的启动窗口 View
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
// 创建启动窗口的根 View
final FrameLayout rootLayout = new FrameLayout(
mSplashscreenContentDrawer.createViewContextWrapper(context));
rootLayout.setPadding(0, 0, 0, 0);
rootLayout.setFitsSystemWindows(false);
// 这个 Runnable 同步地获取 worker thread 中创建的启动窗口 View,并添加到根 View 中
final Runnable setViewSynchronized = () -> {
// trace 记录了同步并添加 view 的过程
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
// waiting for setContentView before relayoutWindow
// 阻塞式的同步 worker thread 创建的启动窗口 View
SplashScreenView contentView = viewSupplier.get();
final StartingSurfaceDrawer.StartingWindowRecord sRecord =
mStartingWindowRecordManager.getRecord(taskId);
final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
? (SplashWindowRecord) sRecord : null;
if (record != null && windowInfo.appToken == record.mAppToken) {
if (contentView != null) {
try {
// 把启动窗口 View 保存到根 View 中
rootLayout.addView(contentView);
} catch (RuntimeException e) {
// ...
}
}
record.setSplashScreenView(contentView);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
};
requestTopUi(true);
// 1. SplashScreenContentDrawer 在 worker thread 中创建启动窗口 View
// 创建完成之后,会通过第四个参数,把 view 缓存到 ViewSupplier 中
mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
viewSupplier::setView, viewSupplier::setUiThreadInitTask);
try {
// 2. 通过 WindowManager 添加启动窗口的根 View
if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
// We use the splash screen worker thread to create SplashScreenView while adding
// the window, as otherwise Choreographer#doFrame might be delayed on this thread.
// And since Choreographer#doFrame won't happen immediately after adding the window,
// if the view is not added to the PhoneWindow on the first #doFrame, the view will
// not be rendered on the first frame. So here we need to synchronize the view on
// the window before first round relayoutWindow, which will happen after insets
// animation.
// 3. 请求 Vsync 信号,在下一帧的回调中,把 worker thread 中创建的启动窗口 View,
// 同步到当前线程,并添加到根 View 中
mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
// ...
} else {
// ...
}
} catch (RuntimeException e) {
// ...
}
}
简单来说,这里就是为启动窗口创建一个 View,然后通过 WindowManager add 进去。但是,从 AOSP 可以看出,为了性能,采用多线程来,具体流程如下
- SplashscreenContentDrawer 在 worker thread 中启动窗口 View。
- splash screen thread(当前线程)先通过 WindowManager 添加启动窗口根 View。注意,ViewRootImpl 会向 Choreographer 注册一个类型为 CALLBACK_TRAVERSAL 的回调,用于执行 traversal。 relayout window 就是在 traversal 中执行的。
- splash screen thread(当前线程)向 Choreographer 注册一个 CALLBACK_INSETS_ANIMATION 的回调,用于同步 worker thread 中的启动窗口 View,并添加到根 View 中。
当 vsyn 信号到来,会先执行 CALLBACK_INSETS_ANIMATION 类型的回调,再执行 CALLBACK_TRAVERSAL 的回调。因此,先执行第三步,同步启动窗口 View,并添加到根 View 中,然后执行第二步,在 traversal 中,会 relayout window 获取 surface,之后绘制启动窗口。