StartWindow
介绍
在 Android 中,StartWindow(启动窗口) 是系统在启动一个新的 Activity 时,为了避免界面出现明显的“白屏”或者“黑屏”闪烁,而临时添加的一个窗口。它的主要作用是在真正的 Activity 界面初始化、布局、绘制完成之前,给用户展示一个过渡界面,提升启动体验。
核心概念
名称 | 作用 |
---|---|
StartWindow | 应用启动时,系统自动为新的 Activity 创建的临时窗口,用于掩盖应用真正 UI 初始化过程。 |
StartingWindow | 与 StartWindow 同义,源码中通常使用 StartingWindow 来描述。 |
SplashScreen(Android 12+) | 是 StartWindow 的现代化替代方案,系统提供标准的样式和动画过渡能力。 |
创建时机与源码位置
当你启动一个新的 Activity 时(包括应用首次启动或从其他 Activity 跳转),系统会尝试创建一个 StartWindow:
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean createStartingWindow(...) {
...
mWmService.mStartingSurfaceController.addStartingWindow(this);
}
具体流程如下:
1. 启动 Activity(AMS 层 startActivity)
2. ActivityRecord#createStartingWindow() 判断是否需要创建 StartWindow
3. StartingSurfaceController#addStartingWindow() 创建 StartingWindow(通常是 SplashScreen 或空白背景)
🧾 StartWindow 内容由谁决定?
1. 主题样式决定背景颜色或图片:
Manifest 中声明的 android:theme 控制了启动时窗口的视觉效果。
<activity
android:name=".MainActivity"
android:theme="@style/Theme.App.Starting">
• 若该主题设置了 windowBackground,系统将使用该背景作为 StartWindow 内容;
• 若设置了 windowSplashScreenAnimatedIcon(Android 12+),则展示动画图标。
2. 是否展示 StartWindow 由系统判断:
系统会根据是否是首次启动、是否冷启动、是否设置窗口背景等条件决定是否展示。
移除时机
StartWindow 在 目标 Activity 的窗口真正准备好展示之后 被移除。即:
• 当目标 Activity 执行完 onCreate() → setContentView() → View 被绘制 → reportDrawFinished();
• 或系统通过 ActivityRecord#handleLaunchActivity() 设置 DecorView 完成绘制;
系统会通过如下代码关闭 StartWindow:
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void removeOrphanedStartingWindow(boolean activityRemoved) {
mWmService.mStartingSurfaceController.removeStartingWindow(this, activityRemoved);
}
💡 Android 12 后的变化:SplashScreen API
从 Android 12 开始,StartWindow 被 SplashScreen API 替代,支持:
• Animated Icon
• Exit Animation
• 支持 setKeepOnScreenCondition 控制移除时机
实战建议
• 使用 SplashScreen API(Android 12+):在 onCreate() 中调用 installSplashScreen();
• 老版本兼容方案:使用 windowBackground 设置启动背景,或者搭配透明主题展示自己的 SplashActivity;
• 注意 windowDisablePreview:设置 android:windowDisablePreview=”true” 可禁用 StartWindow,但会导致短暂白屏或黑屏。
和最终展示的window的关系
StartWindow 是 Android 启动过程中用于“过渡”显示的临时窗口,通常用来避免应用启动时的“白屏”或“黑屏”。它在 Activity 启动初期由系统自动添加,等应用自身的 UI(也就是最终的 Window)准备好之后再将其移除。
下面我从它的生命周期、作用、实现和最终 Window 的关系等方面详细说明:
一、StartWindow 的作用
1. 避免白屏
在 Activity 还未完成布局绘制之前,先用一个占位窗口给用户一个视觉反馈。
2. 快速响应
系统可以立即展示 StartWindow,因为它不需要等待应用进程完成创建和布局。
3. 承接启动主题
StartWindow 通常使用 <activity android:theme=”…”> 中定义的 windowBackground 作为展示内容。
二、StartWindow 和最终 Window 的关系
特性 | StartWindow | 最终的 Window(DecorView) |
---|---|---|
所属进程 | 系统进程(SystemServer) | 应用进程 |
添加时机 | Activity 启动 early stage | Activity onCreate 后,执行 setContentView() |
目的 | 临时 UI 过渡 | 应用的真实 UI |
移除时机 | 应用绘制第一帧并完成 reportDraw() 后 | 持久展示 |
视觉内容 | 启动主题背景、应用图标(可配置) | 应用布局 |
三、实现原理(流程)
1. 添加 StartWindow
在 ActivityRecord#createStartingWindow() 中添加:
mWmService.mStartingSurfaceController.addStartingWindow(this);
这个 StartingWindow 是通过 PhoneWindow 构建一个 DecorView(但不在 App 进程内),主要由如下类负责:
• StartingWindowController
• StartingSurfaceDrawer
• WindowManagerService
2. 应用 Window 加载完成
应用进程通过如下方式创建并显示最终的 Window:
Activity.onCreate() {
setTheme(R.style.AppTheme); // 恢复正式主题
setContentView(R.layout.xxx); // 最终 UI 显示
}
这时会触发 WindowManager.addView() 添加 DecorView。
3. 移除 StartWindow
在首次绘制完成之后,会走到:
WindowManagerGlobal.reportDrawFinished()
→ WMS.handleAppShown()
→ removeStartingWindow()
最终将 StartWindow 从系统窗口中移除。
四、开发中相关注意点
• Theme 的继承:StartWindow 用的是 android:theme 中的内容,最好使用 @style/Theme.App.Start,避免复杂布局。
• 透明背景会导致黑屏:如果 windowBackground 是透明或没有设置,StartWindow 就失效。
• 不要做耗时操作:onCreate() 里的耗时会延迟最终 Window 绘制,也会拖慢 StartWindow 的移除。
五、图示结构关系(简化)
[System Process] [App Process]
┌──────────────────┐ ┌────────────────────┐
│ StartWindow │ ← addWindow() ← │ ActivityThread │
│ (Window) │ │ onCreate() │
└──────────────────┘ │ setContentView() →│
│ attach DecorView →│
└────────────────────┘
↓ 等待首帧绘制完成
removeStartWindow()
不是同一个PhoneWindow
StartWindow和展示DecorView的窗口是一个窗口?对应一个PhoneWindow?
不是一个窗口,也不对应同一个 PhoneWindow。
一、简短结论
对象 | StartWindow | DecorView 所在窗口 |
---|---|---|
所属进程 | SystemServer(系统进程) | App 进程 |
所属 Window 类 | BaseWindow(内部类) | PhoneWindow |
Window 类型 | 临时窗口 | 主窗口(正式) |
添加位置 | WindowManagerService 添加系统窗口 | WindowManagerGlobal 添加应用窗口 |
View 根节点 | 系统构造的简易 View(一般含背景、图标) | 应用自己构建的 View(setContentView) |
二、源码级说明
1. StartWindow 是系统添加的独立窗口
在 ActivityTaskManagerService 的 realStartActivityLocked() → addStartingWindow():
// frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
boolean createStartingWindow(...) {
mWmService.mStartingSurfaceController.addStartingWindow(this);
}
这个 addStartingWindow() 内部会创建一个 系统进程里的临时 View 层级,通过 WindowManagerService.addWindow() 添加到系统的窗口列表中。
系统会使用 app 的启动主题(例如背景色、logo)来构造一个 View 并显示出来,这个 View 不属于你的应用进程。
2. DecorView 是 App 自己构造并添加的主窗口
在 ActivityThread.handleResumeActivity() 中:
r.window = r.activity.getWindow(); // 是 PhoneWindow
View decor = r.window.getDecorView();
wm.addView(decor, l); // 应用自己添加窗口,WindowManagerGlobal → ViewRootImpl
此时应用在自己进程中创建 DecorView,通过 ViewRootImpl 连接到系统服务,最终出现在屏幕上。
这才是真正属于 App 的窗口,也是 setContentView() 设置内容之后真正展示的窗口。
三、两者不是同一个窗口的证据
行为 | 说明 |
---|---|
StartWindow 添加 | WindowManagerService 通过 addWindow() 添加系统级窗口,token 是 Activity 的 |
DecorView 添加 | ViewRootImpl.setView() 最终调用 WMS 的 addWindow(),但是一个新的 Surface 和 View 树 |
StartWindow 移除 | 系统在收到应用“首帧绘制完成”信号后移除这个独立的 View |
DecorView 出现 | 应用第一次 doTraversal() 完成绘制 → SurfaceFlinger 显示 |
四、可视化对比
[System Process]
┌─────────────────────────────┐
│ StartWindow │ ← 通过 WMS.addWindow 添加
│ View: 背景+图标 │
│ Token: Activity 的 token │
└─────────────────────────────┘
↓(等待 App 首帧完成)
移除窗口
[App Process]
┌─────────────────────────────┐
│ PhoneWindow │ ← Activity.getWindow()
│ ┌─────────────────────────┐ │
│ │ DecorView │ │ ← setContentView() 设置 View 树
│ └─────────────────────────┘ │
└─────────────────────────────┘
↓(首次 Vsync 后绘制)
ViewRootImpl.draw → 展示主 UI
五、补充:它们之间的连接点是 token
系统之所以能把 StartWindow 和应用主窗口关联起来,是因为:
• 两者都绑定了同一个 WindowToken;
• App 的 Activity 还未真正 attach DecorView 之前,系统先用这个 token 显示了 StartWindow;
• App 在 resume 时,通过相同的 token 注册正式窗口,系统完成替换。
六、结论
StartWindow 是一个独立的、系统进程中创建的临时窗口;
DecorView 所在窗口才是应用真实的主窗口;
二者不是同一个 PhoneWindow,也不是同一个 ViewRootImpl,但使用同一个 token 实现关联。
实现原理
在 Android 系统中,StartWindow(启动窗口)是为了提升应用启动体验而设计的临时窗口。它在应用的主界面(最终的 Window)尚未准备好时提供视觉反馈,避免用户看到空白或黑屏。以下是 StartWindow 的详细实现过程,包括其添加与移除的关键代码路径:
一、StartWindow 的添加流程
当应用启动时,系统会在应用的主界面加载完成前,添加一个启动窗口。这个过程主要涉及以下关键类和方法:
1. ActivityTaskManagerService: 负责管理应用的任务和活动。
2. StartingWindowController: 协调启动窗口的添加和移除。
3. StartingSurfaceDrawer: 实际负责绘制启动窗口的内容。
在启动过程中,系统会调用 StartingWindowController 的 addStartingWindow() 方法,该方法进一步调用 StartingSurfaceDrawer 的 addSplashScreenStartingWindow() 方法来创建和显示启动窗口。
二、StartWindow 的移除流程
一旦应用的主界面准备就绪,系统会移除启动窗口。这个过程主要涉及以下关键步骤:
1. 应用绘制完成通知: 应用在完成首帧绘制后,会通过 reportDrawFinished() 方法通知系统。
2. 系统移除启动窗口: 系统接收到绘制完成的通知后,会调用 StartingWindowController 的 removeStartingWindow() 方法来移除启动窗口。
在 removeStartingWindow() 方法中,系统会检查当前窗口的绘制状态,如果满足条件(如窗口已绘制完成),则会将启动窗口从窗口管理器中移除。
三、关键代码路径示意图
以下是启动窗口添加与移除的关键代码路径示意图:
[ActivityTaskManagerService]
|
v
[StartingWindowController.addStartingWindow()]
|
v
[StartingSurfaceDrawer.addSplashScreenStartingWindow()]
|
v
[WindowManagerService.addWindow()]
|
v
[显示启动窗口]
当应用主界面绘制完成后:
[应用进程]
|
v
[reportDrawFinished()]
|
v
[系统接收通知]
|
v
[StartingWindowController.removeStartingWindow()]
|
v
[WindowManagerService.removeWindow()]
|
v
[移除启动窗口]
四、开发注意事项
• 启动主题配置: 确保在 AndroidManifest.xml 中为启动的 Activity 配置合适的主题,包括 windowBackground 属性,以便启动窗口能够正确显示。
• 避免透明背景: 使用透明背景可能导致启动窗口无法显示,建议使用不透明的背景色或图片。
• 优化首帧绘制时间: 在应用的 onCreate() 方法中避免执行耗时操作,以加快主界面的加载速度,从而尽快移除启动窗口。