先交代一下背景我最近在学 React Native目标很简单——不搞那种“先看两周文档再动手”的思路而是第一天就开项目、边查边写争取尽早跑通一个真实 APP 的基础链路。结果第一关就撞上了启动页Splash Screen的问题APP 能启动但要么白屏一会要么干脆没有启动图模拟器和真机表现还不一样。这篇文章就把“React Native 学习第一天”的完整过程记录下来重点拆解启动图片Splash Screen的配置逻辑、踩坑点和我实际验证过的解决方案。不管你是刚接触 RN 的新手还是被启动白屏折腾过的老同志这篇内容应该都能给你省下一点排查时间。1. 第一天的学习思路与目标拆解1.1 为什么选择“边学边写”而不是先啃完文档再动手我知道很多人接触 React Native 的时候第一反应是去买一本大部头或者把官方文档从头到尾读一遍。我个人的经验是文档该看但不要放在第一步尤其当你的目标是“做出东西”的时候。我给自己定的第一天目标非常窄能创建一个 RN 项目能在 Android 和 iOS 模拟器上跑起来能在启动 APP 的时候看到一张自定义的启动图片。三个目标按优先级排序第三个最难也是我今天要重点讲的。这个思路的核心在于“反馈驱动学习”。你每写一段代码、每改一个配置就能立刻在模拟器上看到结果这种即时反馈带来的记忆效果远好于单纯阅读。遇到不懂的术语比如 MainActivity、LaunchScreen、windowBackground就边查边记而不是停下来系统学习。第一天的正确姿势不是“学会”而是“跑通”。1.2 环境准备这些工具一次配到位我是在 macOS 上操作的不过下面这套流程对 Windows 同样适用只是 SDK 路径和一些环境变量的写法略有差异。如果你还没装环境按这个清单来Node.js 16 以上版本建议用 nvm 管理避免以后不同项目版本冲突JDK 17React Native 0.73 之后官方推荐装 17 别装 11Android Studio用来跑 Android 模拟器同时管理 SDKXcode仅 macOS 需要跑 iOS 模拟器用的一个趁手的编辑器我用的是 VS Code装一个 React Native 扩展插件就行检查环境的命令是npx react-native doctor这命令会告诉你哪个环节缺什么比手动一个个查省事得多。我第一次跑的时候它提示 Android SDK 的 platform-tools 没装按提示补上就过了。1.3 创建第一个项目命令就三条环境就绪之后创建项目的命令非常简单npx react-native init SplashDemo cd SplashDemo npx react-native run-android这里有两个坑需要提醒你第一个是初始化时不要用react-native init后面直接跟项目名就完事默认模板其实分成两个分支——一个是旧的社区 CLI 风格一个是新版的react-native-community/cli初始化方式。npx react-native init现在默认创建的是新版模板底层用的就是react-native-community/cli这是个正常现象别看到命令行输出一堆react-native-community开头的包名就慌。第二个是run-android第一次会下载 Gradle 依赖特别慢这一步不是卡死是 Gradle 在拉包。国内网络如果特别慢建议在android/build.gradle里把仓库优先指向阿里云镜像能省下不少时间。改完记得 sync 一下 Gradle。2. 启动图片Splash Screen的原理拆解2.1 为什么 APP 需要启动图片它到底在解决什么问题启动图片业界一般叫 Splash Screen 或者 Launch Screen。很多人以为这只是“加个品牌露出图”好看而已其实它的核心作用远不止于此。React Native 的 JavaScript 引擎启动是有延迟的。APP 从用户点击图标到 JS 代码真正执行完首帧渲染中间要经历系统拉起进程、初始化原生运行时、加载 bundle 包、执行 JS 逻辑、注册组件、渲染首屏。这个过程哪怕很快在低端 Android 设备上也可能需要 2-3 秒。如果这段时间什么都不显示用户看到的就是一个白屏或黑屏。这个体验非常糟糕用户会本能的认为 APP 崩了。启动图片的目的就是在这段“不可见”的时间内给用户一个视觉反馈告诉他们“程序正在启动请稍候”在体验层面掩盖掉技术上的加载等待。一句话总结启动图片是优化启动体验的缓冲层不是装饰。2.2 实现启动图片的三条技术路线对比在 React Native 项目里实现启动图片常规路线有三条我第一天就把它们都试了一遍简单对比如下方案原理优点缺点原生方案手动改原生工程在 Android 的styles.xml和 iOS 的LaunchScreen.storyboard中配置图片不依赖额外库性能最好双端分别配置代码量大维护成本高react-native-splash-screen 库在原生层显示一个原生 View并在 JS 层控制隐藏时机最常用社区成熟文档全需要手动关联原生代码遇到 RN 版本升级可能兼容性出问题react-native-bootsplash 库自动生成各尺寸图片配置相对简单支持动态生成资源适配 Android 12 新特性模板生成逻辑偏定制新手理解起来有门槛我最终选择的是react-native-splash-screen原因很简单它是我搜到最多人用的方案遇到问题能搜到大量解决方案对第一天的我来说可排查性比先进性重要得多。不过要提醒你一点这条路线在 React Native 0.76 之后的一个版本里出现过过一次原生模块自动链接失效的 issue。如果你用的是 0.77 及以上版本建议优先看react-native-bootsplash或者原生配置方案。2.3 启动图片的加载顺序原生先启动JS 后接管理解启动图片的实现机制最关键的是搞清楚先后顺序用户点击 APP 图标系统启动原生进程原生层读取启动配置Android 读取主题中的windowBackgroundiOS 读取 LaunchScreen立即显示启动图片原生层初始化 React Native 环境加载 JS BundleJS 引擎执行完首屏渲染逻辑调用SplashScreen.hide()方法启动图片隐藏展示 APP 真实首屏这个顺序决定了配置的核心思路你必须在原生层设置启动图单纯在 JS 层写Image是没用的因为 JS 层渲染出来之前用户已经看到画面了。3. 实操记录从零到一完成启动页配置3.1 准备启动图资源尺寸和格式这么选才不踩坑我先去网上找了一张 1024x1024 的 PNG 图片作为演示资源。但在实际操作中你最好根据启动图的分辨率和显示区域准备多张不同尺寸的图否则会踩到拉伸变形或边缘被裁切的坑。Android 端启动图建议至少准备以下密度对应的dpi资源密度分辨率参考mdpi320x480hdpi480x800xhdpi720x1280xxhdpi1080x1920xxxhdpi1440x2560如果你不想做这么多张最低限度也要放一张xxhdpi的然后在 XML 里设置scaleTypecenter这样可以避免小图被拉伸变模糊。格式方面强烈建议用 PNG 而非 JPEG。JPEG 虽然有损压缩体积更小但在带透明背景的场景下完全不能用。另外 iOS 的 LaunchScreen 对 PNG 的 alpha 通道支持不太稳定如果你在真机上看到图片边缘有黑边或白边大概率是 alpha 透明通道的问题建议把背景做成不透明的。3.2 Android 端配置四个文件改了就能看到效果第一步安装依赖npm install react-native-splash-screen --save第二步修改 MainActivity打开android/app/src/main/java/com/splashdemo/MainActivity.java改成这样import android.os.Bundle; import org.devio.rn.splashscreen.SplashScreen; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { Override protected void onCreate(Bundle savedInstanceState) { SplashScreen.show(this); super.onCreate(savedInstanceState); } Override protected String getMainComponentName() { return SplashDemo; } }注意第二行 import 别写错这个库包名是org.devio.rn.splashscreen很多人会顺手写成com.reactnative.splashscreen然后编译直接报错找不到符号。第三步新建启动图布局文件在android/app/src/main/res/layout/目录下新建launch_screen.xml?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:backgrounddrawable/launch_screen_bg android:orientationvertical ImageView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter android:contentDescriptionSplash android:srcdrawable/launch_screen_image / /LinearLayout第四步修改 styles.xml在android/app/src/main/res/values/styles.xml里添加下面这段style nameSplashTheme parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowBackgrounddrawable/launch_screen/item /style注意这里的windowBackground引用的是一个drawable资源这也是很多教程没讲清楚的地方。如果你直接引用一张图片放大到不同屏幕比例时会有问题正确做法是先准备一个launch_screen.xml放在drawable目录下作为背景的根布局。最后在AndroidManifest.xml里把启动 Activity 的主题切换为SplashThemeactivity android:name.MainActivity android:themestyle/SplashTheme android:exportedtrue /activity做完这四个文件的修改Android 端就配置好了。注意整个过程里你只是“显示”了启动图真正“隐藏”的逻辑在 JS 层我们下一节讲。3.3 iOS 端配置LaunchScreen.storyboard 是核心入口iOS 端的配置相对简单一些但也更容易被配置细节卡住。用 Xcode 打开项目的ios目录找到LaunchScreen.storyboard这是 iOS 的启动图定义文件。默认模板里只有一个空白 View你要做的就是拖一个UIImageView上去然后设置图片。不过我更推荐直接在Images.xcassets里添加LaunchImage资源因为 storyboard 方式在部分机型上会存在缓存问题真机调试时非常烦人——你改了图但系统还显示旧的要重启设备才生效。用 xcassets 方式的操作路径是打开Images.xcassets在空白处右键选择 “New App Icon / Launch Image”把不同尺寸的启动图拖入对应的2x、3x占位框在Build Settings中确认Launch Screen File为空在Assets.xcassets的AppIcon设置里选好LaunchScreen资源iOS 的启动图尺寸很繁琐一个典型的 iPhone 15 Pro Max 需要 1290x2796 的3x尺寸图。如果你只是学习测试直接放一张 828x1792 的2x图也能在绝大多数模拟器上跑通。3.4 JS 层控制显示之后要在正确时机隐藏配置完成之后在 JS 层做两件事。第一件在根组件挂载时导入并调用在index.js或App.js的组件生命周期里import SplashScreen from react-native-splash-screen; // 在根组件的 componentDidMount 里 useEffect(() { SplashScreen.hide(); }, []);第二件确保时机正确这里有个非常重要的经验hide()的调用时机直接决定用户看到的体验。如果你在useEffect里立刻调用hide()启动图片可能就一闪而过因为首屏内容还没渲染完隐藏后依然是白屏。正确做法是等首屏数据准备好或者至少在第一个页面完成基本布局之后再调用。我当时验证了一个很实用的模式const [ready, setReady] useState(false); useEffect(() { // 模拟异步加载数据的过程 setTimeout(() { setReady(true); SplashScreen.hide(); }, 2000); }, []);这样启动图至少展示 2 秒覆盖掉 JS 加载和首屏渲染时间。生产环境里你应该根据真实的数据加载耗时来决定何时隐藏而不是写死一个时间。4. 踩坑实录启动白屏、图片适配与双端差异4.1 启动白屏最典型的问题原因只有一个我在模拟器上第一次跑通项目后发现 APP 启动时出现 1 到 2 秒的纯白屏然后才看到启动图片加载出来。这个现象在热词里叫 “react native 启动白屏”非常常见。原因其实很简单启动图片的显示依赖于原生资源的加载但原生工程初始化时主题背景会先显示出来。如果你在styles.xml里设置了windowBackground那个阶段的背景就是它但如果你没设置系统默认就是白色。排查方式很直接把windowBackground改成一个深色比如#000000。如果启动瞬间闪过的是黑色说明你的启动图还没覆盖到最早期阶段如果依然是白色那就是你的windowBackground配置根本没有生效检查styles.xml中 parent 主题是否正确。我当时的问题出在Theme.AppCompat.Light.NoActionBar这一行上。React Native 默认模板用的是Theme.AppCompat.Light.NoActionBar的变体我这里改成SplashTheme之后Activity 的标题栏被隐藏了但背景色没变加了一行item nameandroid:windowDisablePreviewtrue/item反而更糟。最后直接删掉这个 item才恢复正常。4.2 Android 12 的 SplashScreen API系统级行为与旧方案冲突如果你用的是 Android 12 及以上设备你会发现启动图的表现变得非常怪异有时候你能看到自定义的启动图有时候系统先显示一个默认的应用图标和背景色然后才跳到你的启动图。这是因为 Android 12 原生引入了一套新的 SplashScreen API它控制了系统级的启动动画。当系统级 SplashScreen 存在时你的应用内启动图windowBackground / react-native-splash-screen会在其后执行叠加出“两层启动”的效果。解决方案是适配 Android 12 的 SplashScreen API。在values-v31目录下新建styles.xmlstyle nameSplashTheme parentandroid:style/Theme.SplashScreen item namewindowSplashScreenBackgroundcolor/launch_bg_color/item item namewindowSplashScreenAnimatedIcondrawable/launch_screen_image/item item namepostSplashScreenThemestyle/AppTheme/item /style用这套配置Android 12 设备会走新的系统级 SplashScreen老版本设备走旧逻辑双端兼容。我是折腾到这一步才明白为什么有的教程说“Android 12 上启动图失效了”——不是失效是系统升级了玩法你的适配方案也得跟着升级。4.3 iOS 端启动图为什么改了不生效缓存在作祟iOS 是一个让人又爱又恨的平台。爱的是开箱即用的体验恨的是启动图缓存机制特别顽固。我在改了一张图之后发现模拟器上显示的还是旧图。试过清理构建缓存试过删除 App 重装都没用。最后搜了一下说是模拟器端要把 “Content and Privacy” 之类的设置重置更直接的方案是xcrun simctl clear_cache命令重置模拟器缓存。实战命令如下xcrun simctl shutdown all xcrun simctl erase all这会把整个模拟器恢复到出厂状态速度很快但会把所有安装的 APP 都清掉。在你尝试任何启动图修改之前建议先执行一次避免“改了半天以为是代码问题结果是缓存”的尴尬。另一个 iOS 端的坑是如果你用的是LaunchScreen.storyboard系统实际上是在首次启动时生成一张快照图。快照一旦生成就不会更新除非你改变 storyboard 的结构比如新加一个 View 或改约束。所以我要么改 storyboard 里控件的尺寸或位置要么就直接切到 xcassets 方式。4.4 图片加载不出来或变形先查资源目录再查 scaleType启动图最常见的视觉问题有两种图片不显示或者显示出来变形拉伸。图片不显示时先别急着查代码。Android 的资源匹配机制非常严格文件放在res/drawable-xhdpi和放在res/drawable有区别文件名里不能含大写字母——这点尤其坑SplashScreen.png和splashscreen.png在资源查找时完全不是一回事。尽量用小写字母命名。图片变形时检查布局文件的scaleType。用fitXY一定会拉伸用center可以保持比例但在长屏机型上两侧可能留白。折中方案是fitCenter配合一个和启动图同色的背景这样留白区域看起来就像是设计的一部分。5. 边学边写的节奏控制与后续学习建议5.1 第一天结束时我理解了哪些核心概念现在回头看第一天最大的收获不是“配置出了启动图”而是把 React Native 双端启动流程跑通了一遍。我梳理了三个第二天还要用到的概念第一原生层和 JS 层是两套独立的环境。很多新手写 RN 代码时会觉得“反正 Java 代码不用管”但启动图这类功能恰恰必须穿透两层才能完整实现。RN 不是写一份代码跑双端那么简单双端差异会出现在任何涉及原生的环节。第二配置类的改动通常需要重新编译原生工程不是刷新一下就生效。我第 N 次改完styles.xml之后只重载了 JS结果启动图纹丝不动白白浪费时间。记住改了android/或ios/目录下的文件必须重新run-android或run-ios甚至要先clean一次。第三模拟器场景和真机场景有差异。Android 12 的新 SplashScreen API 在模拟器上反应慢半拍iOS 的缓存问题在真机上更顽固。如果只在模拟器上调通就以为万事大吉拿到真机上大概率还是会被打脸。5.2 第一天的代码管理习惯别忽略 Git 提交学习阶段的代码也别乱放从第一天就提交 Git 是个好习惯。我当时的划分很简单git init git add . git commit -m feat: 初始化项目并配置双端启动图第二天开始写业务逻辑后每完成一个小功能就提交一次这样万一改出问题你能精准的回滚到某个版本而不是靠 CtrlZ 倒退。这个习惯在整条 RN 学习路上都会救你很多次。我还建议第一次配置成功之后立刻复制一份代码备份因为后续配置别的组件时很可能不小心改坏现有的原生配置。备份路径放在项目外名字写成SplashDemo_working_backup_日期这种。5.3 后续两天学什么从启动页走向真实页面第一天的目标已经达成第二天我计划做两个事一是搭一个底部导航框架让 APP 真正有“多页面”的样子二是封装一个简单的网络请求模块把 JS 层和后端联通的链路跑通。第三天再回头把启动图片的交互细节优化一遍比如根据启动状态显示动态 loading 效果。这条路线是我个人觉得比教程合理的地方按照“先能跑再变复杂”的顺序推进每一步都处在上一大步搭好的、可运行可验证的代码基础上。你不需要一步到位先跑通再把功能加厚。6. 写在最后第一天的一个小经验最后分享一个我自己的体会React Native 学习的第一天不要贪多也不要在配置环境上死磕超过两小时。如果某个依赖加载超过 30 分钟还没进展,切镜像也好、求助社区也好尽快过去别让自己卡在“看不见成果”的环节。启动图片这件事本质上是让你快速建立“我改代码APP 有反应”的信心循环。这个循环一旦建立起来后面学什么都会顺利很多。一个小技巧送给你如果你今天也打算边学边写建议你从npx react-native init这种工程模板开始不要用 Expo 的托管工作流。虽然 Expo 几乎零配置很多学习教程也用 Expo 演示但它的启动图和原生配置都是高度封装的你学不到原生层的东西。做学习项目宁可麻烦一点也要亲手碰一遍原生配置。我今天的启动图配置完看到模拟器上那张自定义图片稳稳当当地展示了两秒然后平滑过渡到主界面说实话挺有成就感的。下一步就是接着写业务页面了希望这篇文章能帮你少踩几个我踩过的坑。