功能都写完了,准备打个包给产品看看,结果桌面上蹲着一个 React Native 默认的灰色图标,点开还先闪一屏白,然后才是首页。这时候你会发现,改图标和加启动页这件事,在 RN 里根本不是「换张图」那么简单:iOS 要一整套 AppIcon.appiconset,Android 要六套 mipmap,启动页两端的机制还完全不一样,Android 压根没有原生启动图这个概念。
这篇把我配一次图标和启动屏踩到的东西整理成了一条流水线,从批量生成资源开始,到 Android 和 iOS 各自往哪个目录放、要改哪几个文件,再到那个恼人的启动白屏怎么消掉。每一步都有截图,做完应该长什么样也写在图后面。
在本篇文章中,我们将从浅入深,和大家一起学习以下知识:
- iOS 和 Android 各自需要哪些尺寸的图标和启动图,为什么两端差别这么大
- 怎么用一张 1024×1024 的原图批量生成全套资源,不用手动裁
- Android 端替换
mipmap图标、改应用名称的具体位置 react-native-splash-screen的接入方式,以及原生侧那两行代码分别在做什么- iOS 端替换
AppIcon.appiconset、配置LaunchImage的完整路径 - 启动瞬间那一屏白色是哪来的,怎么用
windowIsTranslucent处理掉
# 一、先搞清楚两端各自要什么
动手之前先把资源需求理一遍,不然生成完一堆图不知道往哪塞。
iOS 这边,应用图标放在 ios/项目名/Images.xcassets/AppIcon.appiconset 这个目录里。它是一个「资源集」,里面有一份 Contents.json 声明每个槽位对应多大的图,Xcode 按这份清单去找文件。少一个尺寸,编译时 Xcode 会在 Images.xcassets 那一屏给你标黄警告,上传 App Store Connect 的时候则会直接被校验打回来。所以 iOS 的图标不能只放一张。
Android 这边简单一些,图标放在 android/app/src/main/res/ 下面的一组 mipmap-* 目录里,按屏幕密度分成 mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi 几档,系统会自己挑最合适那一档来用。
启动图的差别就大了。iOS 系统层面就支持启动图,App 冷启动时先渲染 LaunchScreen,等 RN 的 JS 环境跑起来再切到首页,这个过程是系统托管的。Android 没有这个东西,你必须自己拿一个 Activity 的主题背景去顶住,或者装 react-native-splash-screen 这类库,手动控制显示和隐藏。
这就是为什么同样一句「加个启动页」,Android 那边要多改三个文件。
# 二、用图标工厂批量生成全套资源
手动裁十几个尺寸是纯粹的体力活,交给工具就行。
方式一
如果你的项目里正好也有 Ionic 那一套,可以直接复用它的资源生成能力,参考 Ionic 项目里生成不同尺寸的启动图和图标。Ionic CLI 的做法是让你只准备 resources/icon.png 和 resources/splash.png 两张原图,命令跑完自动铺满两端所有尺寸。
方式二
纯 RN 项目用在线的图标工厂更直接。
各种尺寸 Icon 图标生成
工具地址是 https://icon.wuruihong.com/ ,上传一张 1024×1024 的 PNG,勾选要生成的平台,几秒钟就能拿到一个压缩包。原图这里有个要求,必须是不带圆角、不带透明通道的正方形 PNG,圆角是系统自己裁的,你自己先裁好反而会出现二次圆角;带 alpha 通道的图上传 App Store 会被直接拒绝。
拿到压缩包之后分两端放:
- 安卓下替换
andriod/app/src/main/res/下的 mipmap 文件即可 - iOS 下替换如下
下面这张图是 iOS 端图标资源该放的位置,也就是 Xcode 工程里 Images.xcassets 下的 AppIcon 资源集。

替换完之后回 Xcode 点开 Images.xcassets > AppIcon,每个槽位都应该有缩略图,没有空槽就说明尺寸齐了。如果某个格子是空的,多半是文件名和 Contents.json 对不上,把工厂生成的整个文件夹连同 Contents.json 一起覆盖过去最省事。
各种尺寸启动图图标生成
启动图也可以使用图标工厂生产了 https://icon.wuruihong.com/splash ,入口和图标是分开的,别在图标那个页面上传启动图原图。
- 安卓下拷贝生成的文件到
andriod/app/src/main/res/目录下
生成结果按密度分好了目录,下面这张图是 Android 启动图生成后的目录结构。

对照着往 res/ 下面拷就行,同名目录直接合并。这张是拷贝完成后 res/ 目录里应该呈现的样子。

- iOS 下拷贝生成的该文件夹替换即可
iOS 的启动图会生成成一个 LaunchImage.launchimage 资源集,同样是整个文件夹替换。

替换后在 Xcode 的 Images.xcassets 里能看到多出来一个 LaunchImage 条目,里面按设备型号分好了槽位。

这里有个坑要注意,图标工厂是第三方工具,它对新机型尺寸的支持是滞后的。我当时生成出来的槽位就少了几个新设备的,Xcode 里那几格是空的。空槽不会导致编译失败,但那些机型上启动图会被拉伸或者留黑边,介意的话得手动补。
# 三、Android 端替换图标和名称
资源到位了,接下来是两端各自的接线工作,先说 Android。
修改图标和名称
找到根目录 /android/app/src/main/res,把生成好的 mipmap-* 目录整个覆盖进去。

覆盖完成后,res/ 下每个 mipmap- 开头的目录里都应该有一个 ic_launcher.png(有些模板还会带 ic_launcher_round.png,圆形图标,Android 7.1 之后的启动器会用它)。文件名不要改,AndroidManifest.xml 里 android:icon="@mipmap/ic_launcher" 是按这个名字找的。
应用名称改的是另一个地方,在 android/app/src/main/res/values/strings.xml 里的 app_name 字段,改完重新编译就能在桌面上看到新名字。
启动页
Android 的启动页要靠库来做,原文这几条说明我原样保留:
- 在
react-native的android中的启动图和IOS不相同点在于,android没有默认的启动图,在IOS里面有- 使用插件
import SplashScreen from 'react-native-splash-screen';- https://github.com/crazycodeboy/react-native-splash-screen
把生成好的启动页按下图这个格式处理即可,也就是按密度放进 drawable-* 系列目录。

放完之后目录里应该出现 launch_image.png 这类文件,名字要和后面 XML 里引用的 @drawable/launch_image 保持一致。这一步做错的表现很典型:编译报 resource drawable/launch_image not found,一眼就能定位。
# 四、iOS 端改 App 名称和图标
修改 app 名称
编辑 ios/test/Info.plist 文件(test 换成你自己的工程名),改 CFBundleDisplayName 这一项。
<key>CFBundleDisplayName</key>
- <string>$(PRODUCT_NAME)</string>
+ <string>测试程序</string>
这段改的是「显示在桌面图标下面的名字」。默认值 $(PRODUCT_NAME) 是个变量,会取工程的 Product Name,通常是英文工程名。要注意 CFBundleDisplayName 和 CFBundleName 是两个字段,前者是给用户看的显示名,后者是包内部的短名,长度限制更严;桌面显示不对的时候优先改前者。中文名直接写中文即可,Info.plist 是 UTF-8 的。
修改应用图标
应用图标对尺寸有要求,比较简单的方式是准备一张 1024*1024 的图片,然后使用图标工厂在线生成。
这里直接从 Sketch iOS 图标设计模板中选取了一张图片,生成后的结果如下。

生成结果是一个完整的 AppIcon.appiconset 文件夹,里面每个 PNG 的文件名都带着尺寸标识。

我们可以直接用生成好的内容替换默认的图标即可,也就是替换 ios/test/Images.xcassets/AppIcon.appiconset 中的内容。如果不需要全部尺寸,可以用 Xcode 打开项目,点击 Images.xcassets > AppIcon 拖入相应尺寸的图标。
替换完记得在 Xcode 里 Command + Shift + K 清一次编译缓存再跑,不然模拟器上大概率还是老图标。这个我踩过,以为是资源没替对,翻了半天目录,其实只是缓存没清。
# 五、接入 react-native-splash-screen
添加启动页可以使用 react-native-splash-screen 库,通过它可以控制启动页的显示和隐藏。
它解决的问题是什么呢?RN 应用冷启动时,原生容器起来得很快,但 JS bundle 加载和首屏渲染需要时间,这中间那段空档如果不遮住,用户看到的就是一片白。这个库做的事就是在原生侧先把一张全屏图盖上去,等你在 JS 里确认首屏数据准备好了,再手动调用 SplashScreen.hide() 把它撤掉。
$ yarn add react-native-splash-screen
$ react-native link react-native-splash-screen
这两条命令,第一条装包,第二条把原生依赖自动接进 iOS 和 Android 工程。补充一句时效性的说明:react-native link 是当年(RN 0.60 之前)的做法,0.60 之后 RN 引入了 autolinking,装完包直接 cd ios && pod install 就行,不需要也不应该再跑 link,跑了反而可能导致重复链接。老项目沿用上面这套写法没问题,新项目按 autolinking 走。
Android
编辑 MainActivity.java,添加显示启动页的代码。
import android.os.Bundle; // here
import com.facebook.react.ReactActivity;
// react-native-splash-screen >= 0.3.1
import org.devio.rn.splashscreen.SplashScreen; // here
// react-native-splash-screen < 0.3.1
import com.cboy.rn.splashscreen.SplashScreen; // here
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this); // here
super.onCreate(savedInstanceState);
}
// ...other code
}