React Native 啟動(dòng)白屏問(wèn)題解決方案,教程

react-native-splash-screen-.png

目錄

項(xiàng)目源碼:react-native-splash-screen

問(wèn)題描述:

用React Native架構(gòu)的無(wú)論是Android APP還是iOS APP,在啟動(dòng)時(shí)都出現(xiàn)白屏現(xiàn)象,時(shí)間大概1~3s(根據(jù)手機(jī)或模擬器的性能不同而不同)。

問(wèn)題分析:

為什么會(huì)產(chǎn)生白屏?

React Native應(yīng)用在啟動(dòng)時(shí)會(huì)將js bundle讀取到內(nèi)存中,并完成渲染。這期間由于js bundle還沒(méi)有完成裝載并渲染,所以界面顯示的是白屏。

白屏給人的感覺(jué)很不友好,那有沒(méi)有辦法不顯示白屏呢?

上文解釋了:為什么React Native應(yīng)用會(huì)在啟動(dòng)的時(shí)候顯示一會(huì)白屏。既然知道了出現(xiàn)問(wèn)題的原因,那么離解決問(wèn)題也不遠(yuǎn)了。市場(chǎng)上大部分APP在啟動(dòng)的時(shí)候都會(huì)有個(gè)啟動(dòng)屏,啟動(dòng)屏對(duì)于用戶(hù)是比較友好的,一來(lái)展示歡迎信息,二來(lái)顯示一些產(chǎn)品信息或一些廣告,啟動(dòng)頁(yè)對(duì)于程序來(lái)說(shuō),是為程序完成初始化加載數(shù)據(jù),做一些初始化工作的所保留的時(shí)間,啟動(dòng)屏等待的時(shí)間可長(zhǎng)可短,具體根據(jù)業(yè)務(wù)而定。

下面我就教大家如何給React Native 應(yīng)用添加啟動(dòng)屏,并解決啟動(dòng)白屏的問(wèn)題。

Android啟動(dòng)白屏解決方案

我們可以通過(guò)為React Native Android應(yīng)用添加啟動(dòng)屏的方式,來(lái)解決啟動(dòng)白屏的問(wèn)題。我在《React Native Android啟動(dòng)屏,啟動(dòng)白屏,閃現(xiàn)白屏》一文中介紹過(guò)一種為React Native Android應(yīng)用添加啟動(dòng)屏的方法,
不過(guò)那種方法雖好,但牽扯到對(duì)React Native 源碼的修改,如果React Native 版本有更新還需要對(duì)源碼做一些處理,所以以后維護(hù)起來(lái)不是很方便。

下面就向大家介紹另外一種為React Native Android應(yīng)用添加啟動(dòng)屏的方案。

《React Native Android啟動(dòng)屏,啟動(dòng)白屏,閃現(xiàn)白屏》一文中
我們使用的是在根視圖容器上添加一個(gè)視圖作為啟動(dòng)屏,當(dāng)js bundle加載并渲染完成后,再將添加的視圖從根視圖上移除。在根視圖上添加一個(gè)視圖的方式其實(shí)就是為了遮擋白屏,既然是遮擋白屏,我們是不是可以彈出一個(gè)對(duì)話框呢?

小伙伴們肯定會(huì)說(shuō),對(duì)話框也不是全屏啊,主題也不一樣啊,不過(guò)沒(méi)關(guān)系,既然我們可以添加對(duì)話框,那么我們就可以修改對(duì)話框的樣式來(lái)達(dá)到我們需要的效果。

要達(dá)到啟動(dòng)屏的效果,我們需要一個(gè)什么樣效果的對(duì)話框呢?

  1. 在APP啟動(dòng)的時(shí)候顯示;
  2. 在js bundle加載并渲染完成后消失;
  3. 全屏顯示;
  4. 顯示的內(nèi)容可以通過(guò) layout xml 進(jìn)行修改;

上述是我們對(duì)這個(gè)對(duì)話框的基本需求,現(xiàn)在就讓我們來(lái)實(shí)現(xiàn)這一需求:

第一步,創(chuàng)建一個(gè)對(duì)話框組件SplashScreen

為滿(mǎn)足上述需求,對(duì)話框組件需要提供下面兩個(gè)方法:

1.顯示對(duì)話框的方法:

/**
 * 打開(kāi)啟動(dòng)屏
 */
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被銷(xiāo)毀的時(shí)候,持有的Activity能被及時(shí)的回收,這里我們通過(guò)new WeakReference<Activity>(activity);創(chuàng)建了一個(gè)Activity的弱引用。

另外,因?yàn)樵贏ndroid中所有的有關(guān)UI操作都必須在主線程,所有我們通過(guò)activity.runOnUiThread(new Runnable()...,將對(duì)話框的顯示放在了主線程處理。

上述代碼中,show的第二個(gè)參數(shù)fullScreen表示啟動(dòng)屏是全屏顯示(即是否隱藏狀態(tài)欄),代碼會(huì)控制對(duì)話框加載不同的主題樣式R.style.SplashScreen_FullscreenR.style.SplashScreen_SplashTheme來(lái)達(dá)到是否隱藏狀態(tài)的需求。

然后,我們可以在MainActivity.javaonCreate方法中調(diào)void show(final Activity activity,final boolean fullScreen)方法來(lái)顯示啟動(dòng)屏。

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

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

2.關(guān)閉對(duì)話框的方法:

/**
 * 關(guān)閉啟動(dòng)屏
 */
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();
            }
        }
    });
}

上述代碼中,我們提供了關(guān)閉啟動(dòng)屏的方法。那么如何才能讓JS模塊調(diào)用void hide(Activity activity)來(lái)關(guān)閉啟動(dòng)屏呢?

第二步:向JS模塊提供SplashScreen組件

因?yàn)槲覀冃枰趈s中調(diào)用hide方法還控制啟動(dòng)屏的關(guān)閉。js不能直接調(diào)Java,所有我們需要為他們搭建一個(gè)橋梁(Native Modules)。

首先,創(chuàng)建一個(gè)ReactContextBaseJavaModule類(lèi)型的類(lèi),供js調(diào)用。

/**
 * 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";
    }

    /**
     * 打開(kāi)啟動(dòng)屏
     */
    @ReactMethod
    public void show() {
        SplashScreen.show(getCurrentActivity());
    }

    /**
     * 關(guān)閉啟動(dòng)屏
     */
    @ReactMethod
    public void hide() {
        SplashScreen.hide(getCurrentActivity());
    }
}

其次,創(chuàng)建一個(gè)ReactPackage類(lèi)型的類(lèi),用于向React Native注冊(cè)我們的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中注冊(cè)SplashScreenModule組件。

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

準(zhǔn)備工作,做好之后,下面我們就可以在JS中調(diào)用hide方法來(lái)關(guān)閉啟動(dòng)屏了。

第三步:在JS模塊中控制啟動(dòng)屏的關(guān)閉

創(chuàng)建一個(gè)名為SplashScreen的文件,加入下面代碼。

/**
 * SplashScreen
 * 啟動(dòng)屏
 * 出自: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中調(diào)用SplashScreen的hide()方法來(lái)關(guān)閉啟動(dòng)屏了。

componentDidMount() {
    SplashScreen.hide();
}

不要忘記在使用SplashScreen的js文件中導(dǎo)入它哦import SplashScreen from './SplashScreen

iOS啟動(dòng)白屏解決方案

在iOS中,iOS支持為程序設(shè)置一個(gè)Launch Image或Launch Screen File來(lái)作為啟動(dòng)屏,當(dāng)程序被打開(kāi)的時(shí)候,首先顯示的便是設(shè)置的這個(gè)啟動(dòng)屏了。

那么小伙伴會(huì)問(wèn)了,這個(gè)啟動(dòng)屏幕什么時(shí)候會(huì)消失呢?

AppDelegate如下方法:

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

該方法返回一個(gè) BOOL類(lèi)型的值,當(dāng)系統(tǒng)調(diào)用該方并返回值之后,標(biāo)志著APP啟動(dòng)加載已經(jīng)完成,系統(tǒng)會(huì)將啟動(dòng)屏給關(guān)掉。

所以如果我們控制了這個(gè)啟動(dòng)屏幕讓它在js bundle加載并渲染完成之后再關(guān)閉不就解決了iOS 啟動(dòng)白屏了嗎?

上面已經(jīng)說(shuō)到,- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行完成之后,啟動(dòng)屏?xí)魂P(guān)掉。

所以我們就想辦法控制該方實(shí)行的時(shí)間。

第一步:創(chuàng)建一個(gè)名為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

在上述代碼中,我們通過(guò)[[NSRunLoop mainRunLoop] runUntilDate:later];來(lái)控制- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行的時(shí)間,
主線程會(huì)每隔0.1s阻塞一次,直到waiting變量為true,然后我們就可以通過(guò)暴露給JS模塊的hide方法來(lái)控制waiting變量的值,繼而達(dá)到控制啟動(dòng)屏幕的關(guān)閉。

第二步:在JS模塊中控制啟動(dòng)屏的關(guān)閉

通過(guò)第一步我們已經(jīng)向JS模塊暴露了hide方法,然我們就可以在JS模塊中通過(guò)hide方法來(lái)關(guān)閉啟動(dòng)屏幕。
由于iOS在JS模塊中控制啟動(dòng)屏的關(guān)閉的方法和Android中第三步:在JS模塊中控制啟動(dòng)屏的關(guān)閉的方法是一樣的,這里就不再介紹了。

開(kāi)源庫(kù)

為了方便大家使用和解決React Native應(yīng)用啟動(dòng)白屏的問(wèn)題,我已經(jīng)將上述方案做成React Native組件react-native-splash-screen,
開(kāi)源在了GitHub上,小伙伴們可以下載使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容