在第一阶段启动中,当窗口层级树构建完成后,会通知 root task ,其下有一个正在启动的 Activity,Task 会决定是否为这个正在启动 Activity 添加启动窗口。 本文就来研究添加启动窗口的流程。
WM-Core 添加启动窗口
// 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) {
// ...
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "Adding activity %s to task %s "
+ "callers: %s", r, task, new RuntimeException("here").fillInStackTrace());
// ...
// doShow 决定是否要显示启动窗口
boolean doShow = true;
if (newTask) {
// 从 start u0 的 log 看,是包含这个 flag 的。但是,这不重要
// 最终 doShow 还是 true
if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
resetTaskIfNeeded(r, r);
doShow = topRunningNonDelayedActivityLocked(null) == r;
}
} else if (options != null && options.getAnimationType()
== ActivityOptions.ANIM_SCENE_TRANSITION) {
// ...
}
// ...
if (r.mLaunchTaskBehind) {
} else if (SHOW_APP_STARTING_PREVIEW && doShow) {
Task baseTask = r.getTask();
// 当前 Task 下,已经拥有启动窗口的 ActivityRecord
// null
final ActivityRecord prev = baseTask.getActivity(
a -> a.mStartingData != null && a.showToCurrentUser());
mWmService.mStartingSurfaceController.showStartingWindow(r, prev, newTask,
isTaskSwitch, sourceRecord);
}
}
// StartingSurfaceController.java
void showStartingWindow(ActivityRecord target, ActivityRecord prev,
boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
if (mDeferringAddStartingWindow) {
// ...
} else {
target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */,
source);
}
}
// ActivityRecord.java
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean startActivity, ActivityRecord sourceRecord) {
showStartingWindow(prev, newTask, taskSwitch, isProcessRunning(), startActivity,
sourceRecord, null /* candidateOptions */);
}
// prev 是 Task 下前一个拥有启动窗口的 ActivityRecord,此时为 null
// newTask 为 true
// TaskSwitch 为 true
// processRunning 代表 app 进程是否正在运行,此时为 false
// startActivity 为 true
// sourceRecord 指 Launcher
// candidateOptions 为 null
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
boolean processRunning, boolean startActivity, ActivityRecord sourceRecord,
ActivityOptions candidateOptions) {
// ...
// candidateOptions 此时为 null,
// mPendingOptions 是启动 Activity 时,传入的参数
final ActivityOptions startOptions = candidateOptions != null
? candidateOptions : mPendingOptions;
// ...
// 解析 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);
// 此时 ActivityRecord 的状态还是 INITIALING,app 端并没有创建 Activity
// false
final boolean activityCreated =
mState.ordinal() >= STARTED.ordinal() && mState.ordinal() <= STOPPED.ordinal();
// false
// 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) {
// ...
// 目前 ActivityRecord 下还没有任何窗口
// null
final WindowState mainWin = findMainWindow(false /* includeStartingApp */);
// ...
// app 冷启动,此时还没有 TaskSnapshot
// 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");
// ActivityRecord.mStartingData 代表启动窗口数据
// SplashScreenStartingData 就是一个简单的数据包装类
mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
scheduleAddStartingWindow();
return true;
}
private void scheduleAddStartingWindow() {
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Add starting %s: startingData=%s",
this, mStartingData);
// ActivityRecord.StartingSurface 象征性代表启动窗口 surface,并非实际的 surface
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);
}
}
// SplashScreenStartingData.java
class SplashScreenStartingData extends StartingData {
@Override
StartingSurface createStartingSurface(ActivityRecord activity) {
return mService.mStartingSurfaceController.createSplashScreenStartingSurface(
activity, mTheme);
}
}
// 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;
}
// 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;// null
info.appToken = activity.token;
try {
// 通知 WMShell 添加启动窗口
lastOrganizer.addStartingWindow(info);
} catch (RemoteException e) {
Slog.e(TAG, "Exception sending onTaskStart callback", e);
return false;
}
return true;
}
WM-Shell 添加启动窗口
// ShellTaskOrganizer.java
public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
mStartingWindow.addStartingWindow(info);
}
}
// StartingWindowController.java
public void addStartingWindow(StartingWindowInfo windowInfo) {
mSplashScreenExecutor.execute(() -> {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
// 值为 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)) {
mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
} else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
// ...
}
// ..
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
});
}
// StartingSurfaceDrawer.java
void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
@StartingWindowType int suggestType) {
mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
}
// 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;
}
// 构建 LayoutParams
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);
// 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. splash screen thread 中异步创建 splash screen view
mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
viewSupplier::setView, viewSupplier::setUiThreadInitTask);
try {
// 2. 通过 WindowManagerGlobal 添加启动窗口的根 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) {
// ...
}
}
- 在 splash screen worker thread 中创建启动窗口 View。
- 在 splash screen thread 中,通过 WindowManagerGlobal 添加启动窗口的根 View。ViewRootImpl 会向 Choreographer 注册一个类型为 CALLBACK_TRAVERSAL 的回调,用于执行 traversal,traversal 中会 relayoutwindow 并完成绘制。
- 在 splash screen thread 中,向 Choreographer 注册一个 CALLBACK_INSETS_ANIMATION 的回调,用于同步 worker thread 中创建启动窗口 View,并添加到根 View 中。
当 Vsyn 信号到来,会先执行 CALLBACK_INSETS_ANIMATION 类型的回调,再执行 CALLBACK_TRAVERSAL 的回调。 因此,先执行第三步,把启动窗口 View 添加到根 View 中,然后执行第二步中的 traversal,完成启动窗口的绘制。