启动白屏
介绍
在 Android 中,**“白屏”**指的是应用启动过程中,在 Activity 的界面还未准备好时,系统为了避免黑屏而默认显示的一块白色背景,这会造成用户看到一个纯白界面,体验不佳。
白屏产生的根本原因
主要出现在 冷启动(Cold Start)过程中,应用还未绘制出第一个 Activity 的界面时,系统需要显示一个占位界面,这时就可能出现“白屏”:
具体产生流程如下:
1. 启动 Activity 时,系统先创建一个 Window。
2. 还未执行 setContentView(),还没有 UI 被绘制;
3. 为了不显示黑屏,系统会为这个窗口 填充默认背景;
4. 如果没有配置其他内容,这个默认背景就是 纯白色;
5. 当真正的 UI 渲染完成后,白屏才会被覆盖。
为什么默认是白色的?
系统在启动 Activity 时,会根据 theme 给窗口一个背景。比如:
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- 若没有指定 windowBackground,则默认是白色 -->
</style>
如果你没有明确指定 android:windowBackground,那么系统就使用了默认背景,而这个背景通常就是白色。
如何避免白屏?
1. 使用 SplashScreen API(Android 12+):
2. 设置启动背景-避免白屏-不使用SplashScreen 阅读: StartingWindow相关文章
3. 将首帧绘制尽量提前(减少 Application 和 Activity 的初始化逻辑耗时)-避免白屏-不使用SplashScreen
原因分析
从 Android 启动 Activity 的源码角度分析「启动白屏」问题,本质是在 ActivityThread 创建 Activity 到界面完成绘制之间,系统已经将窗口显示出来了,但还未绘制正式内容,这时窗口显示的就是默认的背景(通常是白色),我们称之为“启动白屏”。
当 setContentView() 被调用之后,DecorView 被创建并添加到 ViewRootImpl,此时窗口会添加到 WMS 并显示。但由于首次绘制未完成,此时用户看到的是 DecorView 的默认背景 —— 这就是“白屏”现象。
························必读·······················
白屏是发生在:调用 setContentView() 后,DecorView 被创建并添加进系统窗口,但真实 UI 内容尚未绘制出来的那个阶段。所以所有setContentView()之前的操作,以及setContentView()本身的绘制时间,都会影响白屏的持续时间。包括Application.onCreate()中的操作,所以优化白屏的思路,就是减少setContentView()之前的代码的执行时间,同时配合设置启动背景来进行优化.注意,设置的启动背景是在Activity的onAttach方法中生效的,如果在onAttach方法之前进行的耗时操作,比如Application.onCreate()中的操作,仍然会影响到白屏,所以Application.onCreate()中也不要进行耗时的操作.
························必读························
下面我从源码路径和执行时序的角度详细拆解整个过程,并指出白屏产生的关键时刻。
🧭 1. 启动流程简略图(重点时序)
→ AMS 进程(ActivityManagerService)
↓
→ Application 进程(ActivityThread)
↓
1. ActivityThread.handleLaunchActivity()
- 创建 Activity
- 调用 Activity.attach()
→ 创建 PhoneWindow
- 调用 Activity.setContentView()
- 调用 Activity.onResume()
2. ViewRootImpl.setView()
- 创建 ViewRootImpl,设置 DecorView
- 添加窗口(WindowManagerService)
- 发起绘制流程 performTraversals()
📌 2. 白屏产生的根本原因
白屏出现的关键是:
Window 提前添加到了 WMS 中,DecorView 的背景被绘制出来了,但 setContentView() 还没执行,界面没有真正内容。
也就是 Activity.attach() 阶段 Window 就已经被加入,且 DecorView 的默认背景(如白色)开始展示。
🔍 3. 关键源码路径追踪
3.1 ActivityThread.handleLaunchActivity()
public Activity handleLaunchActivity(ActivityClientRecord r, ...) {
Activity a = performLaunchActivity(r, customIntent); // 创建 Activity
...
}
3.2 performLaunchActivity()
Activity activity = mInstrumentation.newActivity(...);
Application app = r.packageInfo.makeApplication(...);
...
activity.attach(...); // ❗关键,创建 PhoneWindow 并绑定 DecorView
...
activity.onCreate(...); // ❗此时才开始 setContentView()
3.3 Activity.attach()
mWindow = new PhoneWindow(this);
mWindow.setWindowManager(...);
这里会设置 WindowManager,同时会加载Android Manifest中设置的背景,阅读:Manifest中设置的android_theme什么时候生效
在Activity的onCreate方法中调用的setContentView中会调用构建 DecorView的方法,阅读:DecorView的创建。
public void setContentView(int layoutResID) {
// 此时才开始设置你定义的布局
mWindow.setContentView(layoutResID);
}
📦 4. Window 添加流程
当你设置完布局后,系统会通过 WindowManager 添加 View:
ViewRootImpl.setView() {
mView = decor;
...
requestLayout(); // 发起首次绘制
}
在 requestLayout() 中,会触发 performTraversals(),开始测量、布局、绘制:
ViewRootImpl.performTraversals() {
// 调用 view.measure(), view.layout(), view.draw(canvas)
}
但 此过程是异步的,即 DecorView 添加到 WMS 后,不代表界面立即可见内容,绘制过程尚未完成。
正确流程梳理:
步骤 | 描述 |
---|---|
1. Activity.attach() | 创建 PhoneWindow,但未创建 DecorView |
2. Activity.setContentView() | 创建 DecorView 并添加用户布局(inflate) |
3. handleResumeActivity() | 获取 DecorView 并通过 WindowManager 添加进系统窗口 |
4. ViewRootImpl.setView() | 设置 DecorView,触发首次 measure/layout/draw |
5. DecorView 背景被绘制 | 若内容还未绘制完,只显示背景,造成“白屏” |
❗为什么出现白屏?
系统出于启动优化考虑,提前显示了窗口,但此时应用主界面还没设置上来,显示的是 DecorView 的默认背景。
在未设置主题前,它的默认背景就是:
<style name="Theme.DeviceDefault">
<item name="android:windowBackground">@android:color/white</item>
</style>
✅ 5. 避免白屏的优化方案
✅ 5.1 使用 windowSplashScreenBackground
Android 12+ 引入的 SplashScreen API,建议使用:
<!-- themes.xml -->
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:windowSplashScreenBackground">@color/launch_background</item>
</style>
✅ 5.2 自定义启动主题 + 替换背景
<!-- res/values/themes.xml -->
<style name="AppTheme.Launch" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">@drawable/splash_bg</item>
</style>
并在代码中设置:
override fun onCreate(savedInstanceState: Bundle?) {
// 设置为正式主题(恢复)
setTheme(R.style.AppTheme)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
⚠️ 注意:切换主题要在 super.onCreate() 之前。
📌 总结
阶段 | 描述 |
---|---|
Activity.attach() | 创建 PhoneWindow,DecorView 初始化,添加到 WMS |
Activity.onCreate() | setContentView 设置正式布局 |
ViewRootImpl.setView() | 设置 View,触发首次绘制 |
首次绘制完成前 | DecorView 的背景被绘制(即白屏) |
白屏的核心原因在于:DecorView 提前展示,但未设置实际内容布局。