React Native 启动白屏问题解决方法

发布于 2023-05-15 22:53:19 字数 10622 浏览 61 评论 0

问题描述

用 React Native 架构的无论是 Android APP 还是 iOS APP,在启动时都出现白屏现象,时间大概1~3s(根据手机或模拟器的性能不同而不同)。

问题分析

为什么会产生白屏?

React Native 应用在启动时会将 js bundle 读取到内存中,并完成渲染。这期间由于 js bundle 还没有完成装载并渲染,所以界面显示的是白屏。

白屏给人的感觉很不友好,那有没有办法不显示白屏呢?

上文解释了:为什么 React Native 应用会在启动的时候显示一会白屏。既然知道了出现问题的原因,那么离解决问题也不远了。市场上大部分APP在启动的时候都会有个启动屏,启动屏对于用户是比较友好的,一来展示欢迎信息,二来显示一些产品信息或一些广告,启动页对于程序来说,是为程序完成初始化加载数据,做一些初始化工作的所保留的时间,启动屏等待的时间可长可短,具体根据业务而定。

下面我就教大家如何给React Native 应用添加启动屏,并解决启动白屏的问题。

Android 启动白屏解决方案

我们可以通过为 React Native Android 应用添加启动屏的方式,来解决启动白屏的问题。我在《React Native Android启动屏,启动白屏,闪现白屏》一文中介绍过一种为 React Native Android 应用添加启动屏的方法, 不过那种方法虽好,但牵扯到对React Native 源码的修改,如果React Native 版本有更新还需要对源码做一些处理,所以以后维护起来不是很方便。

下面就向大家介绍另外一种为 React Native Android 应用添加启动屏的方案。

在React Native Android启动屏,启动白屏,闪现白屏一文中 我们使用的是在根视图容器上添加一个视图作为启动屏,当js bundle加载并渲染完成后,再将添加的视图从根视图上移除。在根视图上添加一个视图的方式其实就是为了遮挡白屏,既然是遮挡白屏,我们是不是可以弹出一个对话框呢?

小伙伴们肯定会说,对话框也不是全屏啊,主题也不一样啊,不过没关系,既然我们可以添加对话框,那么我们就可以修改对话框的样式来达到我们需要的效果。

要达到启动屏的效果,我们需要一个什么样效果的对话框呢?

  1. 在APP启动的时候显示;
  2. 在js bundle加载并渲染完成后消失;
  3. 全屏显示;
  4. 显示的内容可以通过 layout xml 进行修改;

上述是我们对这个对话框的基本需求,现在就让我们来实现这一需求:

第一步,创建一个对话框组件 SplashScreen

为满足上述需求,对话框组件需要提供下面两个方法:

1.显示对话框的方法:

/**
 * 打开启动屏
 */
public static void show(final Activity activity,final boolean fullScreen) {
    if (activity == null) return;
    mActivity = new WeakReference<Activity>(activity);
    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (!activity.isFinishing()) {

                mSplashDialog = new Dialog(activity,fullScreen? R.style.SplashScreen_Fullscreen:R.style.SplashScreen_SplashTheme);
                mSplashDialog.setContentView(R.layout.launch_screen);
                mSplashDialog.setCancelable(false);

                if (!mSplashDialog.isShowing()) {
                    mSplashDialog.show();
                }
            }
        }
    });
}

为了Activity被销毁的时候,持有的Activity能被及时的回收,这里我们通过 new WeakReference<Activity>(activity); 创建了一个Activity的弱引用。

另外,因为在Android中所有的有关UI操作都必须在主线程,所有我们通过 activity.runOnUiThread(new Runnable()... ,将对话框的显示放在了主线程处理。

上述代码中, show 的第二个参数 fullScreen 表示启动屏是全屏显示(即是否隐藏状态栏),代码会控制对话框加载不同的主题样式R.style.SplashScreen_FullscreenR.style.SplashScreen_SplashTheme来达到是否隐藏状态的需求。

然后,我们可以在 MainActivity.javaonCreate 方法中调 void show(final Activity activity,final boolean fullScreen) 方法来显示启动屏。

@Override
protected void onCreate(Bundle savedInstanceState) {
    SplashScreen.show(this,true);
    super.onCreate(savedInstanceState);
}

提示: SplashScreen.show(this,true); 放在 super.onCreate(savedInstanceState); 之前的位置效果会更好。

2.关闭对话框的方法:

/**
 * 关闭启动屏
 */
public static void hide(Activity activity) {
    if (activity == null) activity = mActivity.get();
    if (activity == null) return;

    activity.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (mSplashDialog != null && mSplashDialog.isShowing()) {
                mSplashDialog.dismiss();
            }
        }
    });
}

上述代码中,我们提供了关闭启动屏的方法。那么如何才能让JS模块调用 void hide(Activity activity) 来关闭启动屏呢?

第二步:向 JS 模块提供 SplashScreen 组件

因为我们需要在js中调用 hide 方法还控制启动屏的关闭。js不能直接调Java,所有我们需要为他们搭建一个桥梁(Native Modules)。

首先,创建一个 ReactContextBaseJavaModule 类型的类,供js调用。

/**
 * SplashScreenModule
 * 出自:http://www.cboy.me
 * GitHub:https://github.com/crazycodeboy
 * Eamil:crazycodeboy@gmail.com
 */
public class SplashScreenModule extends ReactContextBaseJavaModule{
    public SplashScreenModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "SplashScreen";
    }

    /**
     * 打开启动屏
     */
    @ReactMethod
    public void show() {
        SplashScreen.show(getCurrentActivity());
    }

    /**
     * 关闭启动屏
     */
    @ReactMethod
    public void hide() {
        SplashScreen.hide(getCurrentActivity());
    }
}

其次,创建一个 ReactPackage 类型的类,用于向 React Native 注册我们的 SplashScreenModule 组件。

/**
 * SplashScreenReactPackage
 * 出自:http://www.cboy.me
 * GitHub:https://github.com/crazycodeboy
 * Eamil:crazycodeboy@gmail.com
 */
public class SplashScreenReactPackage implements ReactPackage {

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new SplashScreenModule(reactContext));
        return modules;
    }
}

再次,在 MainApplication 中注册 SplashScreenModule 组件。

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new SplashScreenReactPackage()
    );
}

准备工作,做好之后,下面我们就可以在JS中调用 hide 方法来关闭启动屏了。

第三步:在 JS 模块中控制启动屏的关闭

创建一个名为 SplashScreen 的文件,加入下面代码。

/**
 * SplashScreen
 * 启动屏
 * 出自:http://www.cboy.me
 * GitHub:https://github.com/crazycodeboy
 * Eamil:crazycodeboy@gmail.com
 * @flow
 */
'use strict';

import { NativeModules } from 'react-native';
module.exports = NativeModules.SplashScreen;

然后,我们可以在js中调用SplashScreen的hide()方法来关闭启动屏了。

componentDidMount() {
    SplashScreen.hide();
}

不要忘记在使用 SplashScreen 的 js 文件中导入它: import SplashScreen from './SplashScreen

iOS 启动白屏解决方案

在 iOS 中,iOS 支持为程序设置一个 Launch Image 或 Launch Screen File 来作为启动屏,当程序被打开的时候,首先显示的便是设置的这个启动屏了。

那么小伙伴会问了,这个启动屏幕什么时候会消失呢?

AppDelegate 如下方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

该方法返回一个 BOOL类型的值,当系统调用该方并返回值之后,标志着APP启动加载已经完成,系统会将启动屏给关掉。

所以如果我们控制了这个启动屏幕让它在js bundle加载并渲染完成之后再关闭不就解决了iOS 启动白屏了吗?

上面已经说到, - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法执行完成之后,启动屏会被关掉。

所以我们就想办法控制该方实行的时间。

第一步:创建一个名为 SplashScreen 的 Object-C 文件

在 SplashScreen.h 文件中添加如下代码:

//
//  SplashScreen.h
//  SplashScreen
//  出自:http://www.cboy.me
//  GitHub:https://github.com/crazycodeboy
//  Eamil:crazycodeboy@gmail.com


#import "RCTBridgeModule.h"

@interface SplashScreen : NSObject<RCTBridgeModule>
+ (void)show;
@end

在 SplashScreen.m 中添加如下代码:

//  SplashScreen
//  出自:http://www.cboy.me
//  GitHub:https://github.com/crazycodeboy
//  Eamil:crazycodeboy@gmail.com

#import "SplashScreen.h"

static bool waiting = true;

@implementation SplashScreen
- (dispatch_queue_t)methodQueue{
    return dispatch_get_main_queue();
}
RCT_EXPORT_MODULE()

+ (void)show {
    while (waiting) {
        NSDate* later = [NSDate dateWithTimeIntervalSinceNow:0.1];
        [[NSRunLoop mainRunLoop] runUntilDate:later];
    }
}

RCT_EXPORT_METHOD(hide) {
    dispatch_async(dispatch_get_main_queue(),
                   ^{
                       waiting = false;
                   });
}
@end

在上述代码中,我们通过 [[NSRunLoop mainRunLoop] runUntilDate:later]; 来控制 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法执行的时间, 主线程会每隔0.1s阻塞一次,直到 waiting 变量为true,然后我们就可以通过暴露给JS模块的 hide 方法来控制 waiting 变量的值,继而达到控制启动屏幕的关闭。

第二步:在 JS 模块中控制启动屏的关闭

通过第一步我们已经向JS模块暴露了 hide 方法,然我们就可以在JS模块中通过 hide 方法来关闭启动屏幕。 由于iOS在JS模块中控制启动屏的关闭的方法和Android中第三步:在JS模块中控制启动屏的关闭的方法是一样的,这里就不再介绍了。

开源库

为了方便大家使用和解决React Native应用启动白屏的问题,我已经将上述方案做成React Native组件react-native-splash-screen, 开源在了GitHub上,小伙伴们可以下载使用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

温柔少女心

暂无简介

文章
评论
672 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文