十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android 12热启动闪屏排查:从冷热启动差异到官方SplashScreen避坑指南

Android 12热启动闪屏排查:从冷热启动差异到官方SplashScreen避坑指南 最近连续接到两个类似反馈都是同一个症状应用里按官方规范接了 SplashScreen 之后冷启动一切正常但只要 App 退到后台再进来或者从最近任务里切回来屏幕就“闪”那么一下——有时是白闪有时是黑闪有时是一张已经该消失的品牌图又跳出来。排查完才发现热启动闪屏这件事坑不在 SplashScreen 本身而在很多人把“冷启动逻辑”直接套到了“热启动场景”上。这篇文章就把我踩过的、以及帮别人踩过的坑系统梳理一遍。先说明一下这篇聊的技术方案基于 Android 12 及更高版本的官方 SplashScreen API兼容库用的是androidx.core:core-splashscreen:1.0.1及以上版本。如果你还在用第三方启动页库或者自己手写启动 Activity问题成因和排查思路会不太一样但最后一部分的自查清单依然适用。1. 先弄清楚热启动和冷启动的“帧交接”差异很多人一听到“闪屏”两个字第一反应就是去查windowBackground、查启动图资源结果改了半天还是闪。问题在于热启动闪屏和冷启动闪屏在系统底层的帧交接流程完全不同你连场景都没分清楚自然无从下手。1.1 冷启动从零到一画面顺序固定冷启动时系统会做这几件事进程不存在先创建进程加载 Application系统根据启动 Activity 的 Theme 渲染一个StartingWindow启动窗口进程初始化完成后创建 Activity走onCreate/onStart/onResumeActivity 绘制出第一帧后系统把启动窗口移除。Android 12 之后这个启动窗口变成了 SplashScreen API 控制的窗口你的应用主题如果设置了Theme.SplashScreen系统就会在启动窗口里显示你配置的图标和背景色。冷启动时用户看到的画面顺序是纯色/图片启动图 → 应用第一帧中间没有其他画面来源所以问题相对容易暴露也容易修复。1.2 热启动从快照到新帧存在“三段式切换”热启动指的是进程还在后台存活、Activity 被销毁或者只是 stopped 的状态下重新回到前台。此时系统做的事情比冷启动“多”了一步系统会先用上一次任务栈留存的任务快照TaskSnapshot快速显示一下然后再确定是否需要重建 Activity如果 Activity 还在就直接onRestart/onStart如果被回收了就重建。这意味着用户从最近任务里把 App 切回来时屏幕上实际经历的是一连串画面的快速替换任务快照旧画面 → 启动窗口Splash → 应用新画面只要这三段之间有任何两段的视觉属性不一致背景色、图标大小、透明通道、亮暗色肉眼就会捕捉到“闪”。这就是热启动闪屏的根本来源不是某一帧画错了而是帧与帧之间的交接不顺。1.3 为什么官方 API 在热启动下依然会显示启动窗口官方 SplashScreen API 的行为是只要 Activity 没有处于 RESUMED 状态并且需要重新显示启动窗口系统就会展示 SplashScreen当 Activity 绘制出第一帧后调用setKeepOnScreenCondition回调返回 false或者第一帧完成启动窗口退出。问题就出在“第一帧完成”这个判定条件上。如果你在onCreate里做了耗时操作、使用了postDelayed强制让 Splash 多停留几秒或者在 Activity 重建流程里有一段时间没有产生新帧系统就会认为应用还没准备好于是启动窗口迟迟不退出或者退出了但下一帧没跟上于是就“闪”了。理解了这个机制下面所有排查思路都是围绕同一句话热启动闪屏查的是“旧画面、启动窗口、新画面”三段转换的每一帧是否连续。2. 闪屏的四种典型表现帮你对号入座闪屏不是一个具体的 bug而是一类现象。我见过的热启动闪屏基本能分成下面四种你可以对照自己的现象先归类排查方向会清晰很多。2.1 白闪或黑闪背景色不连续最常见的一种。表现为从后台切回来屏幕白茫茫地亮一下或者黑一下然后才出现应用界面。这种闪屏的本质是启动窗口的背景色和任务快照里的背景色不一致。举个例子你的windowSplashScreenBackground设置的是品牌深蓝色但 Activity 的根布局背景是白色TaskSnapshot 里保存的也是白色。于是用户看到的是“白色快照 → 深蓝启动窗口 → 白色内容”两个切换点都会有颜色跳变白色窗口就“闪”出来了。2.2 品牌图重影或图标跳动第二种表现是切回来时启动图里的 Logo 像“残影”一样闪了一下或者图标从大变小、从小变大。这个基本是windowSplashScreenAnimatedIcon和你实际页面上的品牌图标尺寸、位置、缩放方式不一致导致的。比如启动窗口里图标占屏幕的比例是系统根据windowSplashScreenIconSize计算的默认是 240dp 直径内的区域但你在 Splash 页或者主页面里放的 Logo 却是大的系统启动窗口退出时如果你配置了setSplashScreenAnimation图标从中间放大然后消失动画和实际页面的图标切换不同步就会出现“跳一下”的视觉错位。2.3 偶发黑闪Surface 重建或动画被切断第三种比较隐蔽只在部分机型上复现而且时有时无。现象是快速切后台再切回来偶尔整个屏幕黑一下。这个跟 SplashScreen 的背景色关系不大更多是因为Activity 在热启动时 Surface 发生了重建而重建期间新帧没有及时提交系统只能先显示一个黑色背景。还有一种情况是开发者模式里“动画时长缩放”被关掉设为 0系统所有窗口动画被禁用SplashScreen 的退出动画被强行切断画面从启动窗口直接跳到新的 Surface中间缺了过渡帧表现在部分机型上就是黑闪或硬切。2.4 Splash 卡住或重复出现第四种不是闪但往往和闪一起出现从后台回来Splash 停留了比预期更长的时间甚至闪屏页重复出现了两次。这个大概率是setKeepOnScreenCondition用错了——回调的条件在热启动场景下没能立即返回 false导致系统一直认为“应用还没准备好”。常见写法是val splashScreen installSplashScreen() splashScreen.setKeepOnScreenCondition { viewModel.isLoading.value }如果 ViewModel 在重建之后、恢复状态的时机晚于返回 true 的时机或者 isLoading 初始值就是 true系统就会多等一帧甚至几百毫秒期间画面是无内容的启动窗口表现上就是“卡”或“重复出现”。3. 热启动闪屏最常见的五个技术根因与修复细节症状归类完之后下面进入实操阶段。我挑了五个实际案例中出现频率最高的根因每个都给到具体的修复方案和代码说明。3.1 windowBackground 与启动窗口背景色不一致这是白闪第一元凶。Android 12 的 SplashScreen API 虽然接管了启动窗口但它只接管了Theme.SplashScreen系列属性你的 Activity 在启动窗口移除后使用的还是普通的windowBackground。如果两个值不一致任何一次启动包括热启动都会出现颜色跳变。正确做法是把这两者放进同一套 color 资源并且在主题里统一引用。style nameTheme.App parentTheme.AppCompat.DayNight.NoActionBar item nameandroid:windowBackgroundcolor/background_main/item ... /style style nameTheme.App.Starting parentTheme.SplashScreen item namewindowSplashScreenBackgroundcolor/background_main/item item namewindowSplashScreenAnimatedIcondrawable/ic_splash_logo/item item namepostSplashScreenThemestyle/Theme.App/item /style关键点windowSplashScreenBackground和android:windowBackground必须指向同一个颜色资源或同名不同值但要相等的颜色。不要一边是品牌色一边是纯白切换的瞬间一定闪。3.2 installSplashScreen 调用位置错误导致双 Splash这个坑我见过好几人踩在onCreate里先写了业务代码然后才调installSplashScreen()。官方说明里写得很清楚这个方法必须放在super.onCreate()之前因为它的作用就是把系统原本的启动窗口和你的setKeepOnScreenCondition绑定起来。class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // 必须最先调用 val splashScreen installSplashScreen() super.onCreate(savedInstanceState) // 之后再设置 keep-on-screen 条件 splashScreen.setKeepOnScreenCondition { false } setContentView(R.layout.activity_main) } }如果你放错了位置系统启动窗口已经准备退出了你又调用了 installSplashScreen它会重新触发一次 Splash 逻辑。在冷启动时可能看不出来因为冷启动的启动窗口本来就在但在热启动时系统会认为“你要显示新的启动窗口”于是 TaskSnapshot 被启动窗口覆盖紧接着又切回内容视觉上就是闪屏。检查方法很简单在installSplashScreen()前后各打一行日志确认它在super.onCreate()之前执行。3.3 setKeepOnScreenCondition 在热启动下误判setKeepOnScreenCondition的正确用法是让启动窗口多停留一会儿直到你的异步数据准备好避免用户看到白屏。但很多人忽略了一点这个回调的返回值是“当前是否应该保持 Splash”。热启动时 Activity 会经过onDestroy→onCreate的完整重建流程。如果你的条件依赖于 ViewModel 的状态而 ViewModel 初始值设置成了 true比如MutableLiveDataBoolean(true)那么系统在热启动恢复第一帧时会判定“应该继续显示启动窗口”于是 Splash 卡顿或者重复出现。修复建议条件是“何时可以离开”不要反着写。用数据是否 ready 来判断而不是用 loading 状态来判断。初始值尽量保持 false让系统在 Activity 首次绘制时就能退出启动窗口。如果确实需要等待网络请求建议在onCreate里把条件设置好不要在onResume之后才改。splashScreen.setKeepOnScreenCondition { // 只有真正需要等数据时返回 true viewModel.isInitialDataReady.value false viewModel.isInitialLoading.value true }这样在热启动时如果数据已经在内存里 ready条件立刻返回 false启动窗口正常退出如果数据确实没准备好才让启动窗口停留。3.4 在 Splash 展示期间做了耗时操作或强制延时有些老代码还保留着启动页停留 1~2 秒的“习惯”在 SplashScreen 里使用 Thread.sleep、SystemClock.sleep或者postDelayed强制延迟。在 Android 12 之后这种做法会直接把热启动闪屏问题放大。原因在前文说过系统在等待第一帧提交。你 sleep 的每一毫秒系统都在显示启动窗口而这时用户看到的不是一张静态图片而是首帧缺失导致的“静止画面”。如果正好赶上了动画时长缩放被关闭、或者系统 CPU 抢占那一帧出来之后画面跳变感会非常明显。正确做法是删除所有人为延时让 Splash 的时间完全由系统控制。如果你确实需要让 Splash 停留到数据加载完用setKeepOnScreenCondition控制而不是手动 sleep。3.5 自定义启动页和官方 Splash 混用造成重复切换不少项目是从老版本迁移过来的老代码里有一个 SplashActivity 负责展示品牌图然后跳转到 MainActivity。迁移到官方 SplashScreen 之后只把主题换成了Theme.SplashScreen但 SplashActivity 还继续存在。于是一次启动变成系统启动窗口SplashScreen→ SplashActivity 页面 → MainActivity两个启动页重复出现。热启动时表现更明显因为 TaskSnapshot 显示的是上次离开时的 MainActivity 画面然后突然切到 SplashActivity再切回 MainActivity“闪”就来了。修复方式不复杂删掉 SplashActivity把 MainActivity 作为 Launcher Activity主题设为Theme.App.Starting。如果需要做“初始化逻辑”比如检查登录态、拉配置放在 MainActivity 的onCreate里配合setKeepOnScreenCondition处理。4. 厂商 ROM 与开发者模式的干扰iQOO 等机型上的实际表现这部分我单独拎出来说是因为我发现很多人在开发机上测得好好的一上真机尤其是部分国产 ROM就闪最后怀疑是自己的代码问题查了半天其实是厂商系统和开发者模式的锅。4.1 “不保留活动”是如何放大热启动闪屏的开发者选项里有个设置叫“不保留活动”Dont keep activities开启后用户一离开 Activity系统立刻销毁它。这个设置原本是开发调试用的模拟 Activity 被系统回收的场景但很多人开着它测 App就会把“热启动”强制变成“冷启动 热启动混合体”进程还在但 Activity 每次都要重建。在这种状态下App 从后台回来时系统会先显示 TaskSnapshot然后立刻重建 Activity再显示 SplashScreen。如果你在 SplashScreen 退出后马上用了recreate()或做了新的状态恢复画面会再切一次闪屏出现频率明显高于正常用户。这不是 App 的 bug但确实是闪屏容易被暴露的场景。所以遇到奇奇怪怪的闪屏先看看开发机的这个选项是不是开着的。日常测试建议关掉它用它测出的结果不能代表真实用户。4.2 动画时长缩放对 Splash 退出动画的影响部分 ROM 的逻辑和原生不完全一致。原生 Android 下把“动画时长缩放”设为 0 会禁用窗口动画和 Activity 转场动画但 SplashScreen 的退出逻辑并不完全依赖这个开关——第一帧提交即退出。但在某些厂商 ROM 上实测表现是动画时长缩放为 0 时启动窗口的淡出动画被直接砍掉画面从启动窗口“硬切”到内容页看起来就是非常生硬的闪跳。这其实不是代码问题是系统设置的干扰。但如果你在开发机上为了加快测试速度把动画关了然后反馈“这里有闪屏”很可能是误报。我的建议是定位闪屏问题时先把动画时长缩放改回 1x把“不保留活动”关掉再复现一次。如果恢复正常说明问题只在特殊设置下出现不属于用户常态路径如果依然闪再进入真代码排查。4.3 厂商省电策略导致的第一帧延迟这个 iQOO 相关热搜词下面很多人提到过一种现象App 在后台停留几分钟后再回来Splash 会明显卡顿一下才消失。这和一些机型的省电策略有关后台应用被冻结或 CPU 调度被降频Activity 恢复时拿不到足够的 CPU第一帧生成延迟启动窗口等待时间变长。代码层面没法完全规避但有一个小技巧可以缓解在onStop到onRestart的阶段避免持有大对象减少内存回收压力同时确保 Application 的onCreate里不要做高耗时初始化因为这直接影响 Activity 首帧提交的速度。你也可以在进程被冻结后唤醒时主动触发一个轻量级的验证帧让第一帧更快提交。5. 热启动闪屏的复现验证与上线前自查清单排查闪屏最怕的不是找不到原因而是“现象不稳定改完不知道好没好”。下面这套方法是我自己固定沉淀下来的复现和验证流程照着做基本能把“这次真的修好了”和“这次只是侥幸没闪”区分开。5.1 用 adb 模拟热启动场景最常用的热启动复现路径有两条做完之后能够稳定触发闪屏# 1. 从最近任务切换回 App最接近真实用户操作 adb shell am start -n com.your.package/.MainActivity # 或者直接按 Home 键再点击图标 # 2. 在开发者模式开启“不保留活动”后后台切换 adb shell settings put global always_finish_activities 1你可以先跑一遍正常的“Home 键 → 点击图标”流程再开启always_finish_activities跑一遍对比两次之间的闪屏差异。如果两次都闪基本确定是代码问题如果只有第二次闪则重点检查 Activity 重建时的状态恢复逻辑。5.2 用硬件加速渲染分析定位闪帧如果你需要更精确地定位“闪”发生在哪一帧可以用adb shell dumpsys gfxinfo或者开启开发者选项里的“GPU 呈现模式分析”。在分析柱状图里短而低垂的柱状图代表这一帧没有渲染内容如果 Splash 退出前后出现了明显的空缺说明启动窗口到应用第一帧之间存在空档。这个分析尤其适用于那些“偶发黑闪”的疑难问题。正常的使用路径下空档期越短用户越难察觉空档期大于 16ms就很容易闪。5.3 SplashScreen 热启动上线前自查清单检查项正确做法出现问题时的现象windowSplashScreenBackground 与 windowBackground使用同一颜色资源白/黑闪installSplashScreen 调用顺序必须在 super.onCreate 之前双 Splash、闪屏setKeepOnScreenCondition 初始值初始为 false数据 ready 才退出Splash 卡顿、重复出现是否有自定义 SplashActivity删除改用 MainActivity Theme.SplashScreen启动页重复、闪屏是否使用 Thread.sleep 延时完全移除交给系统控制硬切闪屏动画时长缩放开发者选项测试时保持 1x定位问题后再还原无动画硬切误报率高不保留活动开发者选项默认关闭只在专项测试时开启热启动变重建闪屏频率偏高上线前建议你拿着这份清单逐项过一遍再从冷启动、热启动、最近任务切换三个路径各测三轮确认无闪屏后再发布。5.4 我自己的一个习惯性判断我修这个问题已经修出肌肉记忆了。接到“热启动闪屏”的反馈第一件事不是看代码而是先问对方“什么机型、闪的是什么颜色的光、你开没开开发者模式”。这三个问题答案出来一半的根因已经露头了。剩下一半再拉 adb 日志、开 GPU 渲染分析基本一小时内能定位。如果你后续还要继续折腾这个项目我建议把Theme.App.Starting里的资源统一抽成一份splash_colors.xml和splash_icons.xml这样以后厂商适配、深浅色模式切换都会省很多事。处理完这个问题顺便把 theme 里那堆硬编码颜色值清一遍你后面会发现这类闪屏能少七八成都不过分。
返回列表