背景
在统计了学员们系统fw面试题后,发现桌面点击到app展示第一帧的源码流程这类题目是被考的最多的,基本上属于ams,wms必问的面试题。但是问这类题目绝不是死记硬背可以搞定的哈,面试官们都会对流程中的一些点进行灵活考察等,当然你只要掌握整个流程,无论别人怎么问其实都可以应答如流。
本文我们来基于aosp16 r4的源码给大家复习一下原来ams,wms课程中给大家讲解的SplashWindow的显示与删除流程。
1. 冷启动SplashStartingWindow流程概览
冷启动(Cold Start)是指目标应用进程尚不存在,需要 Zygote 重新 fork 的场景。
SplashStartingWindow核心设计精髓:SplashStartingWindow 的创建完全与目标应用进程的生命周期解耦。它在 Zygote fork 之前就已经由 Shell(SystemUI)进程创建并显示到屏幕上——所以用户点击图标后能"秒开"看到视觉反馈,从而提升app冷启动的用户体验。
下面是详细的整体的流程时序图情况,主要涉及了5个进程部分:

2. 第一阶段:Launcher 点击 → AMS
2.1 点击事件处理
文件:packages/apps/Launcher3/src/com/android/launcher3/touch/ItemClickHandler.java
用户点击桌面图标时,ItemClickHandler.onClick() 被触发。从 View 的 tag 中取出 ItemInfo,根据类型分发:
public static void onClick(View v) {
if (itemInfo instanceof WorkspaceItemInfo || itemInfo instanceof AppInfo) {
onClickAppShortcut(v, (WorkspaceItemInfo) itemInfo, source);
}
}
最终通过 startAppShortcutOrInfoActivity() 构造 Intent,调用 Launcher.startActivitySafely():
文件:packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
// ~line 2106
public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
super.startActivity(view, intent, optsBundle);
}
第一次 IPC:Launcher 进程 → AMS(IActivityManager.startActivity())。
3. 第二阶段:AMS 处理 —— 在进程 fork 前创建 Splash
3.1 ActivityStarter → startSpecificActivity
文件:services/core/java/com/android/server/wm/ActivityStarter.java
// execute() → executeRequest() → startActivityInner()
mRootWindowContainer.resumeFocusedTasksTopActivities();
文件:services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
// ~line 1181
void startSpecificActivity(ActivityRecord r, ...) {
final WindowProcessController wpc =
mService.getProcessController(r.processName, r.info.applicationInfo.uid);
if (wpc != null && wpc.hasThread()) {
// 冷启动时 wpc == null,跳过此分支
realStartActivityLocked(r, wpc, ...);
} else {
// ★ 注意调用顺序:
r.showStartingWindow(true /* taskSwitch */); // 先创建 Starting Window
mService.startProcessAsync(r.processName, ...); // 再 fork 进程
}
}
关键:showStartingWindow() 在 startProcessAsync() 之前调用。在 Zygote fork 新进程之前,Splash Window 已经开始创建。
3.2 getStartingWindowType() 决策
文件:services/core/java/com/android/server/wm/ActivityRecord.java ~line 2411
private int getStartingWindowType(boolean newTask, boolean taskSwitch,
boolean processRunning, boolean allowTaskSnapshot,
boolean activityCreated, boolean activityAllDrawn, TaskSnapshot snapshot) {
// 冷启动时:newTask=true 或 processRunning=false → 直接进入此分支
if ((newTask || !processRunning || (taskSwitch && !activityCreated)) && !isActivityHome) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
if (taskSwitch) {
if (allowTaskSnapshot && isSnapshotCompatible(snapshot)) {
return STARTING_WINDOW_TYPE_SNAPSHOT;
}
if (!isActivityHome) {
return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
}
}
return STARTING_WINDOW_TYPE_NONE;
}
冷启动中 processRunning=false,直接返回 STARTING_WINDOW_TYPE_SPLASH_SCREEN。
3.3 addStartingWindow → SplashScreenStartingData
文件:services/core/java/com/android/server/wm/ActivityRecord.java ~line 2285
boolean addStartingWindow(String pkg, int resolvedTheme, ...) {
// 守门员:如果已有绘制完成的窗口,不需要 starting window
final WindowState mainWin = findMainWindow(false);
if (mainWin != null && mainWin.isDrawn()) {
return false;
}
// 获取 Task 截图(冷启动时通常为 null)
final TaskSnapshot snapshot = mWmService.mTaskSnapshotController.getSnapshot(...);
// 决定类型
final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
allowTaskSnapshot, activityCreated, activityAllDrawn, snapshot);
// ★ 冷启动 → SPLASH_SCREEN
if (type == STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
mStartingData = new SplashScreenStartingData(mWmService, resolvedTheme, typeParameter);
scheduleAddStartingWindow(); // ★ 触发创建
}
}
3.4 IPC 到 Shell 进程
文件:services/core/java/com/android/server/wm/StartingSurfaceController.java
StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {
return mTaskOrganizerController.addStartingWindow(activity.getTask(), activity, theme);
}
文件:services/core/java/com/android/server/wm/TaskOrganizerController.java ~line 652
StartingSurface addStartingWindow(Task task, ActivityRecord activity, int theme, ...) {
final StartingWindowInfo info = new StartingWindowInfo();
info.taskInfo = task.getTaskInfo();
info.targetActivityInfo = activity.info;
info.splashScreenThemeResId = theme;
// ★ IPC 到 Shell
mTaskOrganizer.addStartingWindow(taskInfo, info);
}
4. 第三阶段:Shell 进程双线程并行渲染 Splash
这是 Android 系统精心设计的性能优化点。
4.1 核心思路
文件:libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
将图标渲染(CPU/GPU 密集操作)和窗口添加(Binder IPC 阻塞操作)放到两个线程并行执行。
// ~line 123
void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, int suggestType) {
// 线程同步的"供应商"对象(内部使用 wait/notify)
final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
final FrameLayout rootLayout = new FrameLayout(context);
// 主线程回调:在 Choreographer 回调中等待 Worker 的结果并设置内容
final Runnable setViewSynchronized = () -> {
SplashScreenView contentView = viewSupplier.get(); // 阻塞等待 Worker
rootLayout.addView(contentView);
};
// ===== 启动两条并行路径 =====
// 路径A(Worker 线程):预渲染图标 Bitmap
mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
viewSupplier::setView,
viewSupplier::setUiThreadInitTask);
// 路径B(主线程):添加窗口到 WindowManager
if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
}
}
4.2 线程同步:SplashScreenViewSupplier
内部使用 synchronized + wait()/notify() 实现阻塞等待:
// ~line 387
private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
private SplashScreenView mView;
private boolean mIsViewSet;
// Worker 线程调用
void setView(SplashScreenView view) {
synchronized (this) {
mView = view;
mIsViewSet = true;
notify(); // 唤醒主线程
}
}
// 主线程调用 —— 阻塞直到 Worker 完成
public SplashScreenView get() {
synchronized (this) {
while (!mIsViewSet) {
wait(); // 等待 Worker
}
return mView;
}
}
}
4.3 添加窗口
protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
WindowManager.LayoutParams params, int suggestType) {
// ★ 与普通 Activity 窗口完全相同的添加路径
mWindowManagerGlobal.addView(view, params, display, null, context.getUserId());
}
窗口类型为 TYPE_APPLICATION_STARTING,此时 Splash 首帧已显示在屏幕上,而应用进程可能还没开始 fork。
5. 第四阶段:Zygote fork 与应用进程初始化
5.1 Fork 新进程
startProcessAsync() 触发 Zygote socket 通信:
AMS.startProcessAsync() → ProcessList.startProcess() → ZygoteProcess.start()
第三次 IPC:SystemServer → Zygote(socket 通信)。
新进程入口为 ActivityThread.main():
文件:core/java/android/app/ActivityThread.java ~line 9238
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false); // 通知 AMS 进程就绪
Looper.loop();
}
5.2 attachApplication → bindApplication → realStartActivity
private void attach(boolean system) {
final IActivityManager mgr = ActivityManager.getService();
mgr.attachApplication(mAppThread); // ★ IPC 到 AMS
}
第四次 IPC:新进程 → SystemServer。
文件:services/core/java/com/android/server/am/ActivityManagerService.java ~line 4583
private boolean attachApplicationLocked(IApplicationThread thread, ...) {
thread.bindApplication(...); // IPC 发送应用初始化数据
mAtmInternal.attachApplication(app);
}
RootWindowContainer.attachApplication() 遍历属于该进程的顶层 Activity,调用 realStartActivityLocked():
文件:services/core/java/com/android/server/wm/ActivityTaskSupervisor.java ~line 822
boolean realStartActivityLocked(ActivityRecord r, ...) {
clientTransaction.addCallback(LaunchActivityItem.obtain(...));
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
}
6. 第五阶段:Activity 启动与窗口首帧绘制
6.1 客户端处理 LaunchActivity
文件:core/java/android/app/ActivityThread.java ~line 4335
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
activity.attach(appContext, this, ...); // 创建 PhoneWindow
mInstrumentation.callActivityOnCreate(activity, r.state);
return activity;
}
然后是 handleResumeActivity()(~line 5905):
public void handleResumeActivity(ActivityClientRecord r, ...) {
performResumeActivity(r, ...); // onResume()
if (r.window == null && !a.mFinished && willBeVisible) {
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
wm.addView(decor, l); // ★ 创建 ViewRootImpl,触发渲染
}
}
wm.addView() 内部创建 ViewRootImpl,驱动 performTraversals() 进行 measure/layout/draw。
6.2 首帧绘制完成 → reportDrawFinished
文件:core/java/android/view/ViewRootImpl.java ~line 5454
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
mWindowSession.finishDrawing(mWindow, t, seqId); // ★ IPC 到 WMS
}
第六次 IPC:应用进程 → WMS。
7. 第六阶段:onFirstWindowDrawn → Splash 移除
7.1 WMS 侧:WindowState.finishDrawing()
文件:services/core/java/com/android/server/wm/WindowState.java ~line 5818
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction, int syncSeqId) {
// Starting window 绘制完成 → 记录指标
if (mAttrs.type == TYPE_APPLICATION_STARTING) {
mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger()
.notifyStartingWindowDrawn(mActivityRecord);
}
// ...
}
绘制状态机转换:DRAW_PENDING → COMMIT_DRAW_PENDING → READY_TO_SHOW
7.2 performShowLocked() —— 两种窗口的分岔
文件:services/core/java/com/android/server/wm/WindowState.java ~line 4434
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); // 应用窗口 → 移除 Splash
} else {
mActivityRecord.onStartingWindowDrawn(); // Splash 窗口 → 记录已展示
}
}
mWinAnimator.mDrawState = HAS_DRAWN;
}
设计要点:同一个方法处理两种窗口——应用窗口绘制完成时触发 onFirstWindowDrawn() 移除 Splash;Splash 窗口自身绘制完成时触发 onStartingWindowDrawn() 记录指标。
7.3 onFirstWindowDrawn → removeStartingWindow
文件:services/core/java/com/android/server/wm/ActivityRecord.java
// ~line 6508
void onFirstWindowDrawn(WindowState win) {
firstWindowDrawn = true;
if (mStartingWindow != null) {
win.cancelAnimation();
}
removeStartingWindow(); // ★ 移除 Splash
}
// ~line 2729
void removeStartingWindow() {
if (transferSplashScreenIfNeeded()) { // 如果 App 设置了自定义退出动画
return;
}
removeStartingWindowAnimation(true); // 否则走默认动画移除
}
// ~line 2766
void removeStartingWindowAnimation(boolean prepareAnimation) {
if (mStartingData != null) {
mStartingData.removeStartingWindow(...); // IPC 到 Shell 移除窗口
}
}
通过 IPC 通知 Shell 移除 Splash Window。Shell 侧播放退出动画(默认 400~500ms 的渐隐+位移动画),随后移除窗口。Splash Window 消失,应用首帧接管屏幕。
总结
冷启动 SplashStartingWindow 的完整生命周期:
Splash 创建:startSpecificActivity() → showStartingWindow() → getStartingWindowType() 返回 SPLASH_SCREEN
→ SplashScreenStartingData → scheduleAddStartingWindow() → IPC → Shell 双线程并行渲染
→ Splash 首帧显示 ✓
Splash 移除:App 首帧绘制 → reportDrawFinished() IPC → WindowState.performShowLocked()
→ onFirstWindowDrawn() → removeStartingWindow() → IPC → Shell 播放退出动画 + 移除
→ App 窗口接管 ✓
核心设计精髓:Splash Window 的创建完全与应用进程的生命周期解耦。冷启动中 processRunning=false,系统直接创建 SplashScreenStartingData,通过 Shell 的双线程并行渲染在 ~1 帧内将 Splash 显示到屏幕。应用进程则需要走完 fork → main → attachApplication → bindApplication → LaunchActivity → onCreate → onResume → addView → 首帧 draw 的完整路径。Splash 就是用来填补这段时间的视觉空白。
原文地址:
362



被折叠的 条评论
为什么被折叠?



