React Native启动图片完全指南:从原理到配置实战
第一天学 react native从创建项目到模拟器里出现启动图片这一连串动作其实没你想的那么“一键完成”。我当初把npx react-native init跑完点运行按钮屏幕上蹦出一张默认启动图时心里想的还不是“哦成功了”而是“这张图到底是谁渲染出来的为什么是它先出现React Native 的代码不是还没加载吗”这些问题如果第一天就搞清楚后面做启动屏、白屏优化、首屏耗时都会顺手很多。这篇文章就围绕“启动图片”这一个点展开。适合刚开始学 react native、正准备边学边写自己的第一个 APP 的人也适合已经跑通 hello world、但不知道启动图该往哪儿改的初学者。我会先讲清楚启动图在 APP 启动流程里的位置再列出选型方案接着手把手把 Android 和 iOS 的配置步骤写出来最后把常见坑和排查方法整理成表。你不需要有原生开发经验只要按步骤走一样能看懂。1. 第一天学 React Native先把“启动图片”当标尺1.1 为什么第一天就会撞上启动图片很多人以为 react native 的项目启动后第一个看到的是 JS 代码里的View或Text实际上不是这样。手机操作系统在拉起 APP 时得先创建一个原生窗口这个窗口里要有一点内容展示给用户然后等 JavaScript bundle 被加载、解析、执行完成RN 的页面才能真正渲染出来。这个中间空档如果完全不处理屏幕上显示的通常是一块白板。启动图片的作用就是在这个空档里替 RN 页面站岗。它不是 React Native 提供的功能而是系统原生层面的启动画面。Android 叫 launch theme 或 launch screeniOS 叫 LaunchScreen。第一天的项目模板里自带一张默认的启动图所以你点开 APP 能看到图片不是引擎自动“画”出来的而是原生系统先把资源里的图显示出来了。这件事值得单独拎出来讲是因为它涉及一个关键边界RN 负责 JS 层渲染原生负责应用层窗口和启动阶段。不理解这个边界后面处理“启动白屏”“图片一闪而过”“release 包启动图消失”这类问题会完全没有头绪。1.2 首日路线不要一上来就装第三方库很多教程会直接让你装react-native-splash-screen一行命令装完调用一下就完事。我在第一天不建议这么干。原因很简单你还没见过原生工程长什么样直接装库等于把“理解”这件事外包了以后出了问题只能瞎猜。我的建议路线是把 react native 项目跑起来观察默认启动图存在。打开 Android 和 iOS 的原生工程目录找到启动图相关的配置。手工替换启动图片理解资源命名和尺寸。再看第三方库知道库帮你做了什么你才敢放心用。这条路线是一条“先拆后装”的路线。它不慢反而能帮你避免后面返工。因为启动图涉及原生资源项目一旦脱离 debug 模式打正式包资源缺失或尺寸不对的问题会在用户手机上直接暴露。2. 启动图片方案怎么选三种主流路子不搞玄学2.1 先想明白你要的启动图到底是什么不同人对启动图理解不一样。有人只想让它别白屏有人想把品牌信息展示给用户还有人想让启动图和首页无缝衔接。你需求不同选型就不同。如果只是“别白屏”直接用原生默认配置就够。如果是“展示品牌”需要准备一套适配多机型尺寸的图片资源这就要动原生工程。如果是“让启动图到首页过渡顺滑”可能还要控制隐藏时机让启动图在 JS 首帧渲染完成后再消失。第一天的项目模板里系统默认的启动图是白底加中间图标。你把图片替换成自己的基本就完成了第一版启动图需求。但如果只是替换图片不做隐藏时机控制可能会出现启动图“闪一下”就消失或者反过来启动图停留时间太长体验很怪。2.2 三种实现方式横向对比我整理了一张表方便你对号入座。方案实现成本可控程度适合场景需要原生知识原生配置Android Theme / iOS LaunchScreen低纯改资源文件只能控制显示内容隐藏靠系统自动构建基础启动图、签名包少量react-native-splash-screen低API 简单高能在 JS 里手动隐藏启动图需要精确控制显隐少量react-native-bootsplash中需生成资源高图片适配能力好需要适配刘海屏、深色模式中量你注意看原生配置的隐藏按钮其实不在你手里。iOS 的 LaunchScreen 会在首帧渲染完成后自动退出Android 的 windowBackground 则会一直显示到页面内容绘制完成。这意味着你用原生方式做启动图不需要写“延迟隐藏”逻辑但也不能精确决定隐藏的那一刻系统的裁量权更大。2.3 我的选型判断第一天先手工改攒下经验再上库我个人的建议是第一天别在这上面犹豫太久。先用纯原生方式把图片替换成自己的跑通一次“冷启动看到启动图”的完整流程。这一步花不了二十分钟但你会知道图片资源放在哪儿、尺寸为什么有那么多套、深色模式下为什么还要单独准备。等到你开始做真实业务的时候再考虑react-native-bootsplash。这个库强在生成资源它可以根据你给的一张 logo自动切出 Android 的mipmap多套尺寸和 iOS 的1x/2x/3x资源还会处理刘海屏安全区省掉一堆重复劳动。但它的底层原理仍然是修改原生文件你先理解原生再装它排查问题时就能直接去翻它生成的目录而不是到处发帖求助。3. 核心细节尺寸、命名、生命周期一个都不能少3.1 图片尺寸与命名规则Android 的启动图需要放在android/app/src/main/res下不同像素密度要用不同目录。标准密度对应关系大概是目录名像素密度对应常见分辨率启动图建议逻辑尺寸mdpi1x约 320x480320pxhdpi1.5x约 480x800480pxxhdpi2x约 720x1280640pxxxhdpi3x约 1080x1920960pxxxxhdpi4x约 1440x25601280px上面的“启动图大小”针对的是居中 logo 或插画。如果你想做全屏背景图就要按手机分辨率最大尺寸来做别只放一张小图否则高像素手机会拉伸到没法看。iOS 的资源目录在ios/项目名/Images.xcassets命名要带1x、2x、3x后缀。这里最容易踩的坑是很多新手从网上下载了一张图随手存成launch.png就拖进 Xcode结果 iPhone 15 上显示得模模糊糊。正确做法是按设计稿切三张分别命名launch1x.png、launch2x.png、launch3x.png。实际开发中还要考虑深色模式。iOS 12 之后支持dark模式Assets.xcassets 里可以为同一张图配置Any Appearance和Dark两个版本。Android 也有values-night目录如果你不处理深色模式下的启动页可能是一片黑或者背景与 logo 颜色不协调。3.2 启动图的显示与隐藏逻辑理解启动图的显示与隐藏是解决白天启动问题的钥匙。操作系统展示启动图的时间窗口是从 APP 进程拉起开始到页面首帧渲染完成结束。RN 在这个窗口里做的事是初始化运行时、加载 JS bundle、执行业务逻辑、构建组件树这几步耗时越长启动图停留越久。如果你用纯原生配置Android 的windowBackground会在首帧绘制时自动让位iOS 的 LaunchScreen 会在首帧渲染完成前退出。它们都属于“系统自动控制”不需要你手动 dismiss。如果你用react-native-splash-screen或react-native-bootsplash你就接管了隐藏时机。常见做法是在根组件的useEffect里等首屏数据或关键图片加载完成后再调用hide。很多小白习惯放在页面组件加载完就调结果首页还有一半没渲染启动图就没了照样会看到短暂白屏。正确逻辑应该是“让启动图多留一会把白屏时间盖住”而不是“越快消失越好”。3.3 不同 react native 版本目录有变化RN 从 0.70 之后模板变化不大但一些老教程里的路径可能失效。你现在用最新版创建项目Android 的包名和MainActivity.kt位置都在android/app/src/main/java/com/你的包名下iOS 的工程在ios/LaunchScreen 相关文件默认是LaunchScreen.storyboard。我在写这篇文章时参考的最新稳定版初始化命令已经变成npx react-native-community/cli init。老版本文档里的npx react-native init还能用但会提示被迁移。学习阶段建议直接用官方模板推荐的新命令避免复制老配置时怎么都对不上。4. 边学边写完整实操过程与核心环节实现4.1 初始化一个能跑的项目我先说环境前提电脑装了 Node 16 以上、Android Studio 或 Xcode模拟器已启动。如果你是用 Windows就只跑 Android 端macOS 可以双端都跑。打开终端执行npx react-native-community/cli init FirstApp cd FirstApp这条命令会生成一个FirstApp文件夹。首次装依赖比较久消息提示Metro waiting on port 8081的时候说明开发服务已经起来了。接下来有两个选择我习惯用两个终端窗口终端 A 用来跑 Metronpm start终端 B 用来编译安装npx react-native run-android第一次编译 Android 可能要下载 Gradle 依赖耗时十分钟以上都很正常。跑通之后模拟器里会显示默认启动图。你手动杀进程再从桌面图标点开就能真实感受到“冷启动出现启动图片”的过程。4.2 Android 纯原生方式从主题到启动图不装任何库只用原生配置你把默认启动图换成自己的图。第一步准备一张启动图命名为launch_image.png分别放进android/app/src/main/res/mipmap-mdpi/launch_image.png android/app/src/main/res/mipmap-hdpi/launch_image.png android/app/src/main/res/mipmap-xhdpi/launch_image.png android/app/src/main/res/mipmap-xxhdpi/launch_image.png android/app/src/main/res/mipmap-xxxhdpi/launch_image.png如果只是复制一张大图到所有目录低分辨率手机会加载没必要的体积高分辨率手机会稍微压缩实际影响不大但练手时建议按尺寸表切图。第二步在android/app/src/main/res/drawable下新建launch_screen.xml。我常用 layer-list 做背景加居中 logo这样背景颜色和图标分离后续改色不需要重做图片?xml version1.0 encodingutf-8? layer-list xmlns:androidhttp://schemas.android.com/apk/res/android item android:drawablecolor/launch_background / item bitmap android:gravitycenter android:srcmipmap/launch_image / /item /layer-list建议你先把背景色定义在colors.xml里别直接写十六进制方便深色模式覆盖。新建android/app/src/main/res/values/colors.xmlresources color namelaunch_background#FFFFFF/color /resources第三步改styles.xml。新版模板的styles.xml默认在android/app/src/main/res/values/styles.xml你找到AppTheme加上一行windowBackgroundstyle nameAppTheme parentTheme.AppCompat.Light.NoActionBar item nameandroid:windowBackgrounddrawable/launch_screen/item /style这一步是纯原生启动图的关键。Android 系统在 window 创建阶段就需要知道背景长什么样它读取的就是windowBackground这个 drawable 会在 RN 渲染完成前一直显示。第四步在MainActivity.kt里不让 activity 直接启动 RN 的默认 content可以先加载一个原生布局等 bundle 加载完成后再切回。模板默认是setContentView(R.layout.launch_screen)还是直接super.onCreate逻辑不同版本有所不同。最简单可靠的纯原生做法就是上面只改主题和 drawable不写任何 Java/Kotlin 代码系统首帧完成后会自动切换。你只需要验证图片出现即可等到想精细控制时再上库。4.3 iOS 纯原生方式从 Assets 到 LaunchScreeniOS 的默认启动文件是 Xcode 里的LaunchScreen.storyboard。它支持直接用约束摆放图片也支持纯色背景。打开ios/FirstApp.xcworkspace左侧找到Images.xcassets先新建一个图片集名字我习惯叫LaunchImage。把准备好的1x/2x/3x三张图拖进对应的 Appearances 空格里。接着打开LaunchScreen.storyboard在中部画布选中 Image View把图片源设置为LaunchImage设置居中约束再勾选Content Mode为Aspect Fit避免拉伸。如果你的启动图是一整张全屏背景记得把 imageView 的约束设成上、下、左、右都贴合父视图并且把 Storyboard 里自带的 Label 等元素删掉。这里有个容易被忽略的点LaunchScreen.storyboard不能使用自定义代码逻辑它只能做静态布局。如果你后续想让启动图展示服务器下发的活动图iOS 这套原生方案就实现不了只能考虑用 RN 层“先显示启动图再加载动态图”的方案。改完后重新编译运行杀掉 APP 再从桌面图标点开你就会看到自己的启动图片替换了默认的一张。4.4 用 BootSplash 接管启动图的生成与隐藏如果你不想手工切图想用最省心的方案我用下来觉得react-native-bootsplash比较成熟。它适合已经了解原生流程、想提高效率的人。安装并生成资源的流程npm install react-native-bootsplash npx react-native-bootsplash generate \ --platformsandroid,ios \ --background#FFFFFF \ --logo./assets/splash-logo.png这个命令会把splash-logo.png生成各个尺寸的原生资源并写进原生工程。注意assets/splash-logo.png建议用带透明背景的 logo不要直接用整张背景图。生成后还要在原生目录做一次链接。Android 需要在MainActivity的onCreate里加一行Override protected void onCreate(Bundle savedInstanceState) { BootSplash.init(savedInstanceState); super.onCreate(savedInstanceState); }iOS 需要手动把BootSplash.storyboard设置成 Launch Screen。这两个步骤generate命令会在终端提示你具体路径跟着提示对照模板走即可。JS 侧的隐藏在根组件里import BootSplash from react-native-bootsplash; import { useEffect } from react; useEffect(() { BootSplash.hide({ fade: true }); }, []);注意这里的 hide 时机应该在首页的数据和组件都准备好之后。我实际操作时会先挂一个onReady状态等关键请求完成再调用hide。如果一进useEffect就 hide逻辑等同于“开个闪光灯马上关掉”启动图失去掩盖白屏的意义。4.5 冷启动验证别只看开发模式无论你用的是哪种方案验证时必须做“冷启动”而不是从开发菜单热重载。热重载的时候APP 进程没被杀掉启动图可能已经被缓存你看到的不是真实效果。Android 验证方式把 APP 划掉或执行adb shell am force-stop com.firstapp然后点击桌面图标启动观察启动图。iOS 验证方式在模拟器菜单栏选择Device→Restart重新解锁后点击 APP 图标。开发模式下 Metro 起着bundle 是从电脑本地拉的启动速度比正式包快你看到的启动图停留时间远小于正式包。真正要确认效果可以执行一次 Android release 包构建npx react-native run-android --mode releaseiOS 在 Xcode 里把 Scheme 改成 Release 再跑。会稍麻烦一些但能真实反映启动图体验。5. 常见问题与排查技巧实录5.1 启动白屏启动图一闪而过这是频率最高的一个问题。现象是点击图标后先有一瞬间白屏然后才出现启动图或者启动图刚出来就闪没了。如果你用原生配置白屏可能是因为windowBackground没有生效。常见原因是styles.xml里的主题和AndroidManifest.xml里 activity 引用的主题不一致。打开android/app/src/main/AndroidManifest.xml看android:theme是否指向AppTheme如果指向了别的主题你改了 AppTheme 也没用。如果你用第三方库并且调用 hide 太早也会出现启动图一闪而过。排查方法就是给hide加延迟看问题是否消失。但我不建议用固定延迟应该用“首屏组件挂载完成 必要数据 ready”这两个信号触发隐藏。5.2 图片拉伸、模糊、被刘海屏遮挡Android 端启动图发虚通常是因为只放了一张低分辨率图高分辨率手机会用缩放模拟放大的效果。另一个原因是 layer-list 里的 bitmap 默认不会居中你需要在 bitmap 标签里指定android:gravitycenter否则图片会被拉伸填满整个区域。iOS 端被刘海屏遮挡常见是因为在 Storyboard 里没有使用安全区约束。你把 imageView 的约束和父视图对齐顶部就会延伸到刘海区域内容可能被挖孔。这种情况下建议让背景图全屏logo 或文字用安全区内的约束定位。深色模式也是一个坑。Android 如果没建values-night目录夜间主题下系统会用深色底色替换你默认的白色背景导致启动图配色诡异。iOS 则检查 Assets 里是否只有Any外观没有Dark如果有在Dark空格里放一张适配图或确保深色环境下原图同样可读。5.3 开发模式与 release 包表现不一致常有朋友反馈“开发模式启动图完全正常打包后白屏了。”这不是玄学根源在于正式包里 bundle 文件是离线打包到资源里的加载时间比开发模式长启动图停留时间也相应变长但资源路径可能与开发模式不同。Android release 包若找不到启动图要先检查mipmap目录里的资源是否被混淆或缩减。打开android/app/build.gradle看 release 构建配置里有没有开启shrinkResources。如果开了很可能会把未被代码引用的 drawable 资源删掉启动图挂掉。解决办法是给启动图资源加tools:keep或关闭该清单中的资源裁剪。iOS release 包则留意LaunchScreen.storyboard是否被替换。有的团队为了做动态启动图改过 Info.plist 里的 LaunchStoryboardName如果你改了又没正确编译冷启动时会回到系统默认状态。5.4 排查速查表我把这阶段可能用到的排查工具和场景整理成一个表。工具或命令用途典型场景终端里的 Metro 日志查看 JS bundle 是否成功加载首屏一直停留启动图说明 bundle 没加载完adb logcatgrep ReactNative查看 Android 端原生报错Xcode Console查看 iOS 端打印的加载错误iOS 真机启动图消失但页面空白adb shell am force-stop 包名模拟冷启动验证启动图全流程npx react-native info检查环境版本匹配依赖版本异常导致原生配置不生效关闭 Metro 后直接跑 release 包模拟用户无开发服务状态避免误把开发模式的缓存行为当成正式效果6. 第一天之后可以怎么继续6.1 我实际操作后的几个体会启动图这件事看起来只是放一张图片实际牵扯到原生资源、主题配置、生命周期、渲染时机四层问题。我第一天做完后最大的感受是不要试图在启动图上堆花活。放一张干净的品牌 logo、一个正确的背景色、一套适配尺寸就比 90% 的粗糙实现强。具体谈几个体会背景色和 logo 分离。做 layer-list 或 storyboard 时背景单独一层logo 单独一层以后换色不用重新裁图。切图宁多勿少。安卓的 mipmap 目录我每次都会补齐五个iOS 的2x/3x也一定要准备真机里的清晰度差别很直观。隐藏时机宁可偏晚不可偏早。晚一点顶多是启动图多停留一瞬早一点就露出白屏。首日学习阶段把这句话写进代码注释里后面所有启动图问题都能少踩一半坑。6.2 后续可以往哪些方向扩展启动图只是第一步跑通之后你可以顺手把“启动流程”完整学一遍。比如试着在启动图上叠加“版本号”和“用户协议”或者做一套“启动图到首页的无缝过渡动画”。这些玩法需要你开始接触 JS 与原生通信正好进阶到 RN 的 Bridging 概念。我个人后续的做法是把启动图、首屏数据预加载、路由初始化三件事统一放在一个AppPrepare组件里用状态机控制每一步。第一天先别学这么复杂等你能不查文档说出“启动图在 Android 的哪个 drawable 里、iOS 的哪一文件里”时再往前冲也不迟。动手写吧代码不会骗人。