React Native 实现无白屏启动页

change-rn-splash-screen

一个 React Native 应用在启动的时候往往不如原生快,这主要是因为 RN 比原生应用多了一层 JS Bridge,需要的初始化的时间也就更长一些。所以如果不做任何改动的话,我们打开应用的瞬间就会观察到短暂的白屏现象。为了更好的用户体验,我们需要想办法去掉这个讨厌的白屏,使得应用在打开时就显示启动页。

本文主要讨论如何使用 react-native-splash-screen 在 Android 和 iOS 上实现无白屏启动页效果。

Android 上启动页的实现

现有方案的对比

通常,在 Android 上,我们会为启动页设置以下 Theme:

<style name="AppTheme.SplashTheme" parent="AppTheme">
    <item name="android:windowBackground">@drawable/start_page_background</item>
    <-- or even more -->
    <item name="colorPrimaryDark">@android:color/white</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowContentOverlay">@null</item>
</style>

通过添加 android:windowBackground 来设置启动页背景图,然后再在 SplashScreenActivity 中做路由跳转。但是这在 RN 中显然行不通,因为 RN 里只有一个 MainActivity

网上的文章中,有一种方案是通过自己配置一个启动页 activity,在其中对 ReactRootView 以及 ReactInstanceManager 进行预加载,加载完毕后再跳转到 MainActivity。这种做法会有一些副作用,而且也并非完美的解决方案,需要对 ReactActivityDelegateReactActivityDelegate 进行改造。

react-native-splash-screen 的作者提供了一种思路,只要在根视图上添加一个 dialog 遮盖掉白屏,然后等到合适的时机(JS Bundle 加载并渲染完毕后)再隐藏掉这个 dialog。

这种方法还是非常巧妙的,因为我们不需要对源码做修改,所以不用担心随着 RN 版本的迭代更新而失效,而且也不会对 RN 组件的生命周期造成影响。

Android 实现细节

安装 react-native-splash-screen 之后,我们需要添加一个开屏页的布局 launch_screen.xml,理论上来说可以自由添加任何元素,但是不推荐添加过于复杂的布局,一般来说添加一张图片就足够了:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/bg_splash_screen" />
</RelativeLayout>

之后我们还可以定制化开屏页的主题,如下:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- use this to delay app launch from a cold start to eliminate blank space -->
        <!-- which is not a best practice in splash screen, not recommended -->
        <!--<item name="android:windowIsTranslucent">true</item>-->

        <!-- use this to specify the status bar color -->
        <item name="colorPrimaryDark">@color/primary_dark</item>
        <!-- use this to replace the blank from a cold start -->
        <item name="android:windowBackground">@drawable/splash</item>
    </style>

</resources>

这里有两种选项,一种是直接跳过冷启动的白屏,另一种是替换默认背景图,推荐使用第二种方式。

然后将主题应用到开屏页:

public class MainActivity extends ReactActivity {

    // other code...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // you have to add it before super.onCreate
        SplashScreen.show(this, R.style.AppTheme);
        super.onCreate(savedInstanceState);
    }
}

这里我将开屏页的主题和 App 主题设置为同一个,这样状态栏颜色就不会发生变化。因为我发现如果开屏页和 App 主题的状态栏颜色不一致的话,会造成启动时状态栏从 App 主题颜色过渡到启动页状态栏颜色,进入主页面后再过渡到 App 主题,效果不是很好。

看下效果(图片太大,简书不支持缩放,为了不影响阅读体验,请使用链接查看):splash_screen_android.gif

由于设置了背景图,冷启动时会观察到从背景图切换到启动页图的效果。

iOS 上启动页的实现

iOS 实现原理与 Android 类似,通过添加一个 Launch Image 或者修改 LaunchScreen.xib 来控制启动屏的显示内容,然后等到 JS Bundle 加载完再显示主页面。

这里我同样通过添加启动页图片来实现,xCode 中选择 Images.xcassets,然后 + → App Icons & Launch Images → New iOS Launch Image,选中 LaunchImage 并在右侧的 Attribute Inspector 中选择你需要支持的屏幕。最后将准备好的启动页图片拖到对应的型号下就可以了。附上启动页图片大小参照:Static Launch Screen Images

添加完启动页图片后,还需要修改以我们新添加的图片作为启动页,在 xCode 中选择 General → App Icons and Launch Images → Launch Images Source → Use Asset Catalog,再选择我们新添加的 LaunchImage 就可以了。注意可能要将 app 卸载后重新安装后,修改的启动页图片才可能生效。

效果图:splash_screen_ios.gif

可以看到 iOS 上有一点和 Android 不同,由于 iOS 没有冷启动机制,所以不会出现背景图切换的问题。

代码见:https://github.com/aJIEw/HeadFirstRN/blob/master/app/redux/router/AppRouter.js

推荐阅读更多精彩内容