重重学app冷启动流程桌面点击到SplashWindow显示与移除流程剖析-aosp16

重重学app冷启动流程桌面点击到SplashWindow显示与移除流程剖析-aosp16
背景在统计了学员们系统fw面试题后发现桌面点击到app展示第一帧的源码流程这类题目是被考的最多的基本上属于amswms必问的面试题。但是问这类题目绝不是死记硬背可以搞定的哈面试官们都会对流程中的一些点进行灵活考察等当然你只要掌握整个流程无论别人怎么问其实都可以应答如流。本文我们来基于aosp16 r4的源码给大家复习一下原来amswms课程中给大家讲解的SplashWindow的显示与删除流程。1. 冷启动SplashStartingWindow流程概览冷启动Cold Start是指目标应用进程尚不存在需要 Zygote 重新 fork 的场景。SplashStartingWindow核心设计精髓SplashStartingWindow 的创建完全与目标应用进程的生命周期解耦。它在 Zygote fork 之前就已经由 ShellSystemUI进程创建并显示到屏幕上——所以用户点击图标后能秒开看到视觉反馈从而提升app冷启动的用户体验。下面是详细的整体的流程时序图情况主要涉及了5个进程部分2. 第一阶段Launcher 点击 → AMS2.1 点击事件处理文件packages/apps/Launcher3/src/com/android/launcher3/touch/ItemClickHandler.java用户点击桌面图标时ItemClickHandler.onClick()被触发。从 View 的 tag 中取出ItemInfo根据类型分发publicstaticvoidonClick(Viewv){if(itemInfoinstanceofWorkspaceItemInfo||itemInfoinstanceofAppInfo){onClickAppShortcut(v,(WorkspaceItemInfo)itemInfo,source);}}最终通过startAppShortcutOrInfoActivity()构造 Intent调用Launcher.startActivitySafely()文件packages/apps/Launcher3/src/com/android/launcher3/Launcher.java// ~line 2106publicbooleanstartActivitySafely(Viewv,Intentintent,ItemInfoitem){intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);super.startActivity(view,intent,optsBundle);}第一次 IPCLauncher 进程 → AMSIActivityManager.startActivity()。3. 第二阶段AMS 处理 —— 在进程 fork 前创建 Splash3.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 1181voidstartSpecificActivity(ActivityRecordr,...){finalWindowProcessControllerwpcmService.getProcessController(r.processName,r.info.applicationInfo.uid);if(wpc!nullwpc.hasThread()){// 冷启动时 wpc null跳过此分支realStartActivityLocked(r,wpc,...);}else{// ★ 注意调用顺序r.showStartingWindow(true/* taskSwitch */);// 先创建 Starting WindowmService.startProcessAsync(r.processName,...);// 再 fork 进程}}关键showStartingWindow()在startProcessAsync()之前调用。在 Zygote fork 新进程之前Splash Window 已经开始创建。3.2 getStartingWindowType() 决策文件services/core/java/com/android/server/wm/ActivityRecord.java~line 2411privateintgetStartingWindowType(booleannewTask,booleantaskSwitch,booleanprocessRunning,booleanallowTaskSnapshot,booleanactivityCreated,booleanactivityAllDrawn,TaskSnapshotsnapshot){// 冷启动时newTasktrue 或 processRunningfalse → 直接进入此分支if((newTask||!processRunning||(taskSwitch!activityCreated))!isActivityHome){returnSTARTING_WINDOW_TYPE_SPLASH_SCREEN;}if(taskSwitch){if(allowTaskSnapshotisSnapshotCompatible(snapshot)){returnSTARTING_WINDOW_TYPE_SNAPSHOT;}if(!isActivityHome){returnSTARTING_WINDOW_TYPE_SPLASH_SCREEN;}}returnSTARTING_WINDOW_TYPE_NONE;}冷启动中processRunningfalse直接返回STARTING_WINDOW_TYPE_SPLASH_SCREEN。3.3 addStartingWindow → SplashScreenStartingData文件services/core/java/com/android/server/wm/ActivityRecord.java~line 2285booleanaddStartingWindow(Stringpkg,intresolvedTheme,...){// 守门员如果已有绘制完成的窗口不需要 starting windowfinalWindowStatemainWinfindMainWindow(false);if(mainWin!nullmainWin.isDrawn()){returnfalse;}// 获取 Task 截图冷启动时通常为 nullfinalTaskSnapshotsnapshotmWmService.mTaskSnapshotController.getSnapshot(...);// 决定类型finalinttypegetStartingWindowType(newTask,taskSwitch,processRunning,allowTaskSnapshot,activityCreated,activityAllDrawn,snapshot);// ★ 冷启动 → SPLASH_SCREENif(typeSTARTING_WINDOW_TYPE_SPLASH_SCREEN){mStartingDatanewSplashScreenStartingData(mWmService,resolvedTheme,typeParameter);scheduleAddStartingWindow();// ★ 触发创建}}3.4 IPC 到 Shell 进程文件services/core/java/com/android/server/wm/StartingSurfaceController.javaStartingSurfacecreateSplashScreenStartingSurface(ActivityRecordactivity,inttheme){returnmTaskOrganizerController.addStartingWindow(activity.getTask(),activity,theme);}文件services/core/java/com/android/server/wm/TaskOrganizerController.java~line 652StartingSurfaceaddStartingWindow(Tasktask,ActivityRecordactivity,inttheme,...){finalStartingWindowInfoinfonewStartingWindowInfo();info.taskInfotask.getTaskInfo();info.targetActivityInfoactivity.info;info.splashScreenThemeResIdtheme;// ★ IPC 到 ShellmTaskOrganizer.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 123voidaddSplashScreenStartingWindow(StartingWindowInfowindowInfo,intsuggestType){// 线程同步的供应商对象内部使用 wait/notifyfinalSplashScreenViewSupplierviewSuppliernewSplashScreenViewSupplier();finalFrameLayoutrootLayoutnewFrameLayout(context);// 主线程回调在 Choreographer 回调中等待 Worker 的结果并设置内容finalRunnablesetViewSynchronized()-{SplashScreenViewcontentViewviewSupplier.get();// 阻塞等待 WorkerrootLayout.addView(contentView);};// 启动两条并行路径 // 路径AWorker 线程预渲染图标 BitmapmSplashscreenContentDrawer.createContentView(context,suggestType,windowInfo,viewSupplier::setView,viewSupplier::setUiThreadInitTask);// 路径B主线程添加窗口到 WindowManagerif(addWindow(taskId,windowInfo.appToken,rootLayout,display,params,suggestType)){mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION,setViewSynchronized,null);}}4.2 线程同步SplashScreenViewSupplier内部使用synchronizedwait()/notify()实现阻塞等待// ~line 387privatestaticclassSplashScreenViewSupplierimplementsSupplierSplashScreenView{privateSplashScreenViewmView;privatebooleanmIsViewSet;// Worker 线程调用voidsetView(SplashScreenViewview){synchronized(this){mViewview;mIsViewSettrue;notify();// 唤醒主线程}}// 主线程调用 —— 阻塞直到 Worker 完成publicSplashScreenViewget(){synchronized(this){while(!mIsViewSet){wait();// 等待 Worker}returnmView;}}}4.3 添加窗口protectedbooleanaddWindow(inttaskId,IBinderappToken,Viewview,Displaydisplay,WindowManager.LayoutParamsparams,intsuggestType){// ★ 与普通 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()第三次 IPCSystemServer → Zygotesocket 通信。新进程入口为ActivityThread.main()文件core/java/android/app/ActivityThread.java~line 9238publicstaticvoidmain(String[]args){Looper.prepareMainLooper();ActivityThreadthreadnewActivityThread();thread.attach(false);// 通知 AMS 进程就绪Looper.loop();}5.2 attachApplication → bindApplication → realStartActivityprivatevoidattach(booleansystem){finalIActivityManagermgrActivityManager.getService();mgr.attachApplication(mAppThread);// ★ IPC 到 AMS}第四次 IPC新进程 → SystemServer。文件services/core/java/com/android/server/am/ActivityManagerService.java~line 4583privatebooleanattachApplicationLocked(IApplicationThreadthread,...){thread.bindApplication(...);// IPC 发送应用初始化数据mAtmInternal.attachApplication(app);}RootWindowContainer.attachApplication()遍历属于该进程的顶层 Activity调用realStartActivityLocked()文件services/core/java/com/android/server/wm/ActivityTaskSupervisor.java~line 822booleanrealStartActivityLocked(ActivityRecordr,...){clientTransaction.addCallback(LaunchActivityItem.obtain(...));mService.getLifecycleManager().scheduleTransaction(clientTransaction);}6. 第五阶段Activity 启动与窗口首帧绘制6.1 客户端处理 LaunchActivity文件core/java/android/app/ActivityThread.java~line 4335privateActivityperformLaunchActivity(ActivityClientRecordr,IntentcustomIntent){activitymInstrumentation.newActivity(cl,component.getClassName(),r.intent);activity.attach(appContext,this,...);// 创建 PhoneWindowmInstrumentation.callActivityOnCreate(activity,r.state);returnactivity;}然后是handleResumeActivity()~line 5905publicvoidhandleResumeActivity(ActivityClientRecordr,...){performResumeActivity(r,...);// onResume()if(r.windownull!a.mFinishedwillBeVisible){Viewdecorr.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 5454privatevoidreportDrawFinished(NullableTransactiont,intseqId){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 5818booleanfinishDrawing(SurfaceControl.TransactionpostDrawTransaction,intsyncSeqId){// Starting window 绘制完成 → 记录指标if(mAttrs.typeTYPE_APPLICATION_STARTING){mWmService.mAtmService.mTaskSupervisor.getActivityMetricsLogger().notifyStartingWindowDrawn(mActivityRecord);}// ...}绘制状态机转换DRAW_PENDING → COMMIT_DRAW_PENDING → READY_TO_SHOW7.2 performShowLocked() —— 两种窗口的分岔文件services/core/java/com/android/server/wm/WindowState.java~line 4434booleanperformShowLocked(){finalintdrawStatemWinAnimator.mDrawState;if((drawStateHAS_DRAWN||drawStateREADY_TO_SHOW)mActivityRecord!null){// ★ 关键根据窗口类型分发if(mAttrs.type!TYPE_APPLICATION_STARTING){mActivityRecord.onFirstWindowDrawn(this);// 应用窗口 → 移除 Splash}else{mActivityRecord.onStartingWindowDrawn();// Splash 窗口 → 记录已展示}}mWinAnimator.mDrawStateHAS_DRAWN;}设计要点同一个方法处理两种窗口——应用窗口绘制完成时触发onFirstWindowDrawn()移除 SplashSplash 窗口自身绘制完成时触发onStartingWindowDrawn()记录指标。7.3 onFirstWindowDrawn → removeStartingWindow文件services/core/java/com/android/server/wm/ActivityRecord.java// ~line 6508voidonFirstWindowDrawn(WindowStatewin){firstWindowDrawntrue;if(mStartingWindow!null){win.cancelAnimation();}removeStartingWindow();// ★ 移除 Splash}// ~line 2729voidremoveStartingWindow(){if(transferSplashScreenIfNeeded()){// 如果 App 设置了自定义退出动画return;}removeStartingWindowAnimation(true);// 否则走默认动画移除}// ~line 2766voidremoveStartingWindowAnimation(booleanprepareAnimation){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 的创建完全与应用进程的生命周期解耦。冷启动中processRunningfalse系统直接创建SplashScreenStartingData通过 Shell 的双线程并行渲染在 ~1 帧内将 Splash 显示到屏幕。应用进程则需要走完 fork → main → attachApplication → bindApplication → LaunchActivity → onCreate → onResume → addView → 首帧 draw 的完整路径。Splash 就是用来填补这段时间的视觉空白。原文地址https://mp.weixin.qq.com/s/DH0zQZEk2qD46946LdqNNw