React Native 启动白屏问题解决方案,教程

react-native-splash-screen-.png

目录

项目源码:react-native-splash-screen

问题描述:

用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上,小伙伴们可以下载使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容