Android 单 Activity 架构下的 Splash Screen 与主题规范指南

张开发
2026/4/20 5:44:09 15 分钟阅读

分享文章

Android 单 Activity 架构下的 Splash Screen 与主题规范指南
在传统的 Android 多 Activity 架构中开发者通常会创建一个独立的SplashActivity来展示品牌 Logo 或启动动画然后再跳转到MainActivity并将自身finish()掉。然而在单 Activity 架构尤其是结合 Jetpack Compose Navigation 时中这种做法不再适用。如果新增一个SplashActivity就打破了“单 Activity”的初衷如果直接把 Splash 逻辑塞进MainActivity的布局中又无法解决应用冷启动时系统渲染第一帧之前的白屏/黑屏问题。为了解决这一问题Android 12 引入了官方的Splash Screen API并通过androidx.core:core-splashscreen库向后兼容。在单 Activity 架构中它的核心思想是“利用系统级的主题切换机制让同一个 Activity 在冷启动瞬间使用 Splash 主题在绘制第一帧内容前自动切换回普通主题。”下面将详细讲解这一机制的标准做法。1. 核心原理postSplashScreenTheme机制在引入了core-splashscreen库后我们可以定义一个专门继承自Theme.SplashScreen的主题。这个主题不仅可以定义启动图标、背景色最关键的是它必须包含一个名为postSplashScreenTheme的属性。它的工作流如下进程启动前系统读取AndroidManifest.xml中 Activity 配置的SplashTheme。冷启动瞬间系统利用该主题的属性如windowSplashScreenBackground快速绘制一个启动画面避免白屏。Activity 创建在Activity.onCreate()中我们在super.onCreate()之前调用installSplashScreen()。主题动态切换installSplashScreen()会读取postSplashScreenTheme属性中指定的真实业务主题并将当前 Activity 的 Context Theme动态替换为该业务主题。UI 渲染接下来执行setContent { ... }或setContentView()此时底层的原生 Context 已经恢复为正常的业务主题Compose 可以正常读取所需的各种 Window 属性如无标题栏、状态栏透明等。2. 标准落地步骤与代码示例第一步引入依赖确保在build.gradle中引入了官方的兼容库implementation(androidx.core:core-splashscreen:1.0.1) // 或最新版本第二步定义主题配置 (themes.xml/styles.xml)在你的资源文件中你需要同时定义业务主题和启动主题。1. 正常的业务主题单 Activity 推荐 NoActionBar!-- 这是你原本正常使用的业务主题负责沉浸式、颜色等 --stylenameAppThemeparentTheme.Material3.DayNight.NoActionBar!-- 这里可以配置状态栏透明以便 Compose 处理 Edge-To-Edge -- item nameandroid:statusBarColorandroid:color/transparent/item item nameandroid:navigationBarColorandroid:color/transparent/item !-- 其他业务相关的属性配置 --/style2. 专用的 Splash 主题!-- 必须继承自 Theme.SplashScreen --stylenameAppSplashThemeparentTheme.SplashScreen!-- 启动页的背景颜色 -- item namewindowSplashScreenBackgroundcolor/white/item !-- 启动页中心展示的 Icon可以放应用的 Logo -- item namewindowSplashScreenAnimatedIconmipmap/ic_launcher_round/item !-- 【核心属性】指定启动结束后Activity 要恢复成哪个真实主题 -- item namepostSplashScreenThemestyle/AppTheme/item !-- 可选如果 Logo 有动画可以设置动画时长上限 -- item namewindowSplashScreenAnimationDuration1000/item/style第三步在 Manifest 中应用 Splash 主题在AndroidManifest.xml中将这个单 Activity 的主题指向刚刚定义的AppSplashTheme而不是AppTheme。activityandroid:name.MainActivityandroid:themestyle/AppSplashThemeandroid:exportedtrueintent-filteractionandroid:nameandroid.intent.action.MAIN/categoryandroid:nameandroid.intent.category.LAUNCHER//intent-filter/activity第四步在 Activity 中调用installSplashScreen()这是最关键的一步必须在super.onCreate()之前调用。importandroid.os.Bundleimportandroidx.activity.ComponentActivityimportandroidx.activity.compose.setContentimportandroidx.core.splashscreen.SplashScreen.Companion.installSplashScreenclassMainActivity:ComponentActivity(){overridefunonCreate(savedInstanceState:Bundle?){// 1. 必须在 super.onCreate() 之前调用// 它会负责执行主题的切换将 AppSplashTheme 替换为 postSplashScreenTheme 指定的 AppThemeinstallSplashScreen()super.onCreate(savedInstanceState)// 2. 这里已经处于 AppTheme 环境下了setContent{YourComposeAppTheme{// 你的单 Activity Compose 导航容器AppNavHost()}}}}3. 常见误区与避坑指南直接给 Activity 永久套用Theme.SplashScreen如果只在 Manifest 中写了AppSplashTheme却忘了在代码里调用installSplashScreen()或者没有配置postSplashScreenTheme那么 Activity 就会一直运行在 Splash 环境下。这会导致很多意想不到的 UI 属性丢失因为 Splash 主题并没有提供常规业务所需的那些底层配置。试图用 Compose 解决冷启动白屏有很多开发者尝试在 Compose 中画一个满屏的 Logo 来做 Splash。但请注意从用户点击桌面图标到 Android 进程启动再到 Compose 引擎初始化并渲染出第一帧中间是有一段明显的时间差的。如果不用原生SplashTheme的windowSplashScreenBackground兜底这段时间必定会闪白屏或黑屏。单 Activity 中的“业务等待页”与原生 Splash 的结合如果你的 App 启动时需要请求网络、校验 Token、初始化重型 SDK 等原生 Splash API 的停留时间是不够的它默认只停留在绘制出第一帧前。标准解法是无缝衔接阶段 A进程初始化系统展示原生AppSplashThemeLogo 纯色背景避免白屏。阶段 B业务等待即 Compose 的 SplashScreenCompose 渲染出第一帧此时进入你的 ComposeSplashScreen路由节点。UI 设计需要你自己写这个 Compose 页面并不是系统自动生成的你需要自己用 Compose 把它写得和原生 Splash 一模一样一样的背景色、一样的 Logo 居中大小。这样一来在原生 Splash 消失、Compose 页面展现的瞬间用户在视觉上感觉不到画面发生了切换认为是同一个 Splash 页面在继续展示。如何保证一模一样背景色与暗黑模式兼容在 Compose 页面顶层的Box或Surface中绝对不能硬编码使用Color.White等固定色值否则在暗黑模式下会导致严重的闪屏。你需要确保背景色与原生themes.xml中windowSplashScreenBackground保持动态一致。方案 A推荐使用 Compose 主题系统确保你的 Compose Theme 能够跟随系统深色模式切换并使用MaterialTheme.colorScheme.background作为背景色。方案 B直接引用原生颜色使用colorResource(id R.color.splash_bg)前提是你在res/values/colors.xml和res/values-night/colors.xml中分别定义了白天和黑夜的splash_bg并将其同时用于themes.xml的windowSplashScreenBackground配置中。Logo 资源使用相同的图标资源。Logo 大小官方的 SplashScreen API 对中间 Icon 的限制是288dp并且如果在有圆形的遮罩内通常被裁切到192dp或者更小。在 Compose 的Image修饰符中给定具体的宽度和高度通常在96dp到192dp之间需要基于你的原始矢量图或切图具体调参将其完全放置在屏幕正中央contentAlignment Alignment.Center。状态栏适配因为是全屏展示ComposeSplashScreen需要调用Modifier.fillMaxSize()如果原本的 Activity 做到了 Edge-To-Edge 沉浸式那 Logo 的居中自然就能对齐。如果仍有微小偏差需要你真机对比微调通常在 1~2 个像素以内用户是无感知的。业务职责在这个 Compose 页面里你可以使用 ViewModel 结合LaunchedEffect来做网络请求、读取本地 Token、拉取配置等耗时操作。这意味着可以适当进行较长时间的等待。关于 Application 初始化强依赖组件如 Crash 收集、日志、DI 框架如 Koin仍必须留在Application.onCreate()。但弱依赖组件、隐私合规授权后的 SDK 初始化、首页需要的预加载数据非常推荐搬到这个 Compose SplashScreen 阶段来做以此缩短 Application 的冷启动耗时。阶段 C动态路由跳转当上述业务就绪后根据结果决定跳去哪里。如果有网且已登录navController.navigate(home)。如果没网或 Token 失效navController.navigate(login)或错误引导页。关键动作跳转时必须把 Splash 路由移出返回栈popUpTo(splash) { inclusive true }防止用户按返回键退回启动页。

更多文章