React Native白屏優(yōu)化

本文針對(duì)使用React Native開(kāi)發(fā)混合應(yīng)用的過(guò)程中iOS端白屏問(wèn)題,提出了react-native預(yù)加載優(yōu)化方案格遭,本文主要圍繞以下幾個(gè)方面展開(kāi)分析:

  • 白屏
  • 解決白屏

本文react-native基于0.48.0版本

白屏

在開(kāi)發(fā)React-Native(下面簡(jiǎn)稱RN)頁(yè)面的時(shí)候义桂,都會(huì)看到下面加載白屏現(xiàn)象


這個(gè)白屏?xí)r間段RN框架在做什么勒!凸克!通過(guò)對(duì)RN源碼的跟蹤瞧挤,發(fā)現(xiàn)這期間端RN做了很多事。
實(shí)例化各個(gè)module-->獲取JSBundle-->創(chuàng)建JS運(yùn)行環(huán)境-->運(yùn)行JSBundle
其實(shí)在開(kāi)發(fā)中上面步驟RN讓開(kāi)發(fā)者就用一句代碼就實(shí)現(xiàn)了,就是實(shí)例化RCTBridge過(guò)程描焰,這個(gè)過(guò)程也就是官方耗時(shí)圖所示的JS init+Require涩僻。花費(fèi)時(shí)間最多的地方

 NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"業(yè)務(wù)全包名"
                                                   withExtension:@"jsbundle"];
 RCTBridge *bridge = [[RCTBridge alloc]initWithBundleURL:bridgeURL
                                                 moduleProvider:nil
                                                  launchOptions:nil];

解決白屏

上面描述了白屏產(chǎn)生過(guò)程栈顷,接下來(lái)講講怎么解決這個(gè)問(wèn)題逆日。

1. 實(shí)現(xiàn)簡(jiǎn)介

簡(jiǎn)單思路就是不要在進(jìn)入RN頁(yè)面的時(shí)候才去實(shí)例化RCTBridge,提前初始化好后緩存下來(lái)萄凤,打開(kāi)頁(yè)面創(chuàng)建RootView的時(shí)候使用已經(jīng)創(chuàng)建好了的RCTBridge室抽,這樣會(huì)直接跳過(guò)JS init+Require過(guò)程。這就是所謂的以空間換時(shí)間做法靡努,但是具體做法很很多中情況坪圾,下面來(lái)看看各種情況具體實(shí)現(xiàn)

2. 具體實(shí)現(xiàn)

首先我們來(lái)看看正常情況怎么創(chuàng)建一個(gè)RootView的

//步驟一:創(chuàng)建bridge
NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"包名字"
                                                   withExtension:@"jsbundle"];
RCTBridge *bridge = [[RCTBridge alloc]initWithBundleURL:bridgeURL
                                                 moduleProvider:nil
                                                  launchOptions:nil];
//步驟二:創(chuàng)建rootView
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                             moduleName:[self moduleName]
                                      initialProperties:self.pageParams];
//步驟三:rootView添加到需要顯示的父View中
[self.view addSubview:rootView];
情況一:你的APP中就一個(gè)RN bundle包(全部緩存)

在你的項(xiàng)目中僅僅有一個(gè)jsbundle包,由于只有only 唯一的包惑朦,我們?cè)贏PP一啟動(dòng)的時(shí)候就實(shí)現(xiàn)步驟一創(chuàng)建bridge(提前緩存)兽泄,我們這用個(gè)單例來(lái)保存這個(gè)bridge。
1:創(chuàng)建一個(gè)單例:

#import <React/RCTBridge.h>
#import "SynthesizeSingleton.h"
@interface BridgeManage : NSObject
SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(BridgeManage)
@property(nonatomic,strong) RCTBridge *bridge;
@end
#import "BridgeManage.h"
@implementation BridgeManage
SYNTHESIZE_SINGLETON_FOR_CLASS(BridgeManage)
- (instancetype)init
{
    self = [super init];
    if(self)
    {
        
        NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"包名"
                                                   withExtension:@"jsbundle"];
        RCTBridge *bridge = [[RCTBridge alloc]initWithBundleURL:bridgeURL
                                                 moduleProvider:nil
                                                  launchOptions:nil];
        _bundleURL = [[NSMutableDictionary alloc]init];
    }
    return self;
}
@end

2:AppDelegate 中實(shí)例化這個(gè)單例

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //放在最前面喲漾月,盡早實(shí)例化
    [BridgeManage sharedInstance];
    //其它程序啟動(dòng)處理
    return YES;
}

3:使用

RCTBridge *bridge =[BridgeManage sharedInstance].bridge;
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                             moduleName:[self moduleName]
                                      initialProperties:self.pageParams];
[self.view addSubview:rootView];

注意:BridgeManage 實(shí)例化需要盡早病梢,如果不第一時(shí)間實(shí)例化,進(jìn)入RN頁(yè)面的時(shí)候才調(diào)用[BridgeManage sharedInstance]梁肿。在第一次進(jìn)入RN頁(yè)面的時(shí)候也會(huì)出現(xiàn)白屏蜓陌。

情況二:你的APP中有多個(gè)RN bundle包

如果APP中是分業(yè)務(wù)模塊開(kāi)發(fā)的,就會(huì)出現(xiàn)多個(gè)jsbundle包吩蔑。這個(gè)時(shí)候就不能將這些jsbundle的Bridge實(shí)例后緩存下來(lái)钮热,會(huì)出現(xiàn)內(nèi)存過(guò)大,浪費(fèi)用戶不用的模塊占用手機(jī)內(nèi)存烛芬。而且有些模塊是通過(guò)網(wǎng)絡(luò)動(dòng)態(tài)下發(fā)到APP中的這是無(wú)法提前緩存的隧期。一般大家都是用到對(duì)應(yīng)jsbundle的時(shí)候創(chuàng)建Rootview實(shí)時(shí)創(chuàng)建Bridge(當(dāng)然會(huì)出現(xiàn)白屏)。那這種情況怎么優(yōu)化呢赘娄?仆潮??
首先我們可以看看官方提供的打包shell命令打包出來(lái)的jsbundle結(jié)構(gòu)
命令如下:

//官方打包命令
react-native bundle --entry-file  enterJsFile  --platform ios --bundle-output ./xxx.jsbundle --assets-dest  photoPath --dev false --reset-cache > /dev/null

__d是RN自定義的define擅憔,__d后面的數(shù)字是模塊的id鸵闪,是在RN打包過(guò)程中,解析依賴關(guān)系暑诸,自增長(zhǎng)生成蚌讼。__d結(jié)構(gòu):

__d(function(t,r,s,c){"use strict";var e=r(31);s.exports=e},30);

一個(gè)js文件就一片段__d定義辟灰,一個(gè)圖片也會(huì)用__d定義

針對(duì)不同模塊的入口js文件打包,將生成不同jsbundle對(duì)比篡石,可以發(fā)現(xiàn):
  • jsbundle的頭部相同
  • 中部很多模塊的定義存在大量重復(fù)
  • 如果模塊js中AppRegistry.registerComponent芥喇,尾部的入口模塊id基本相同,如上例中的require(11)
    實(shí)際上頭部和中部重復(fù)的模塊占用了500K的大谢巳(RN框架js)继控,每個(gè)入口js生成的jsbundle都會(huì)包含這500K代碼。

了解了打包后的jsbundle結(jié)構(gòu)后胖眷,出現(xiàn)了一種優(yōu)化猜想武通,可不可以各個(gè)模塊的RootView 公用這些公共的JS代碼(在一個(gè)JS內(nèi)核中都可以相互調(diào)用),通過(guò)嘗試發(fā)現(xiàn)是可行的珊搀。下面說(shuō)說(shuō)具體實(shí)現(xiàn)冶忱。

1.先把RN架構(gòu)核心庫(kù)js和業(yè)務(wù)代碼js 想辦法分離

分離方法見(jiàn):ReactNaive分包方法

這是分包加載的第一步,分包出來(lái)的包名字我們這樣定義core.ios.jsbundle (RN 系統(tǒng)js)和 bussess.ios.jsbundle (RN 各業(yè)務(wù)js)和 common.ios.jsbundle(RN各業(yè)務(wù)公共的js)

2.單例實(shí)例化核心Bridge

#import <React/RCTBridge.h>
#import "SynthesizeSingleton.h"
@interface BridgeManage : NSObject
SYNTHESIZE_SINGLETON_FOR_CLASS_HEADER(BridgeManage)
@property(nonatomic,strong) RCTBridge *bridge;
-(void)addBundelURL:(NSString*)url;
-(BOOL)isLoadBundleURL:(NSString*)url;
@end
#import "BridgeManage.h"
@interface BridgeManage ()
@property(nonatomic,strong) NSMutableDictionary *bundleURL;
@end
@implementation BridgeManage
SYNTHESIZE_SINGLETON_FOR_CLASS(BridgeManage)
- (instancetype)init
{
    self = [super init];
    if(self)
    {
        //這兒的url決定了rn加載本地資源圖片的根目錄喲>澄觥G羟埂!
//        NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"personalcenter.bundle/core.ios" withExtension:@"jsbundle"];
        NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"core.ios" withExtension:@"jsbundle"];
        _bridge = [[RCTBridge alloc]initWithBundleURL:bridgeURL moduleProvider:nil launchOptions:nil];
        _bundleURL = [[NSMutableDictionary alloc]init];
    }
    return self;
}
-(void)addBundelURL:(NSString*)url{
    self.bundleURL[url]=url;
}
-(BOOL)isLoadBundleURL:(NSString*)url{
    if (self.bundleURL[url]) {
        return YES;
    }
    return NO;
}
@end

2: 由于我們需要使用RCTBridge.m中的私有方法enqueueApplicationScript或者executeApplicationScriptSync劳淆,于是定義一個(gè)extend 擴(kuò)展類RCTBridge+ReactExecuteScript.h如下:

#import <React/RCTBridge.h>

@interface RCTBridge ()
- (void)executeApplicationScriptSync:(NSData *)script url:(NSURL *)url;
@end

3:AppDelegate 中實(shí)例化這個(gè)BridgeManage并向core Bridge中注入common.ios.jsbundle 業(yè)務(wù)公共js

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //放在最前面喲链沼,盡早實(shí)例化
    [BridgeManage sharedInstance];
     NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"common.ios" withExtension:@"jsbundle"];
     NSData *busJSCode = [NSData dataWithContentsOfURL:bundleURL];
        //    dispatch_async(dispatch_get_main_queue(), ^{
        //        [bridge.batchedBridge enqueueApplicationScript:busJSCode url:bundleURL onComplete:^{
        //            NSString *test = @"====";
        //            NSLog(@"==%@",test);
        //        }];
        //    });
     [[BridgeManage sharedInstance].bridge.batchedBridge executeApplicationScriptSync:busJSCode url:bundleURL];
    //其它程序啟動(dòng)處理
    return YES;
}

注意:需要在APPDelegate中import RCTBridge+ReactExecuteScript 不然是無(wú)法調(diào)用executeApplicationScriptSync這個(gè)方法喲。
4:創(chuàng)建RootView

RCTBridge *bridge =[BridgeManage sharedInstance].bridge;
NSURL *bundleURL = [[NSBundle mainBundle] URLForResource:@"bussess.ios" withExtension:@"jsbundle"];
if (![[BridgeManage sharedInstance] isLoadBundleURL:[bundleURL absoluteString]]) {
        NSData *busJSCode = [NSData dataWithContentsOfURL:bundleURL];
        [bridge.batchedBridge executeApplicationScriptSync:busJSCode url:bundleURL];
        [[BridgeManage sharedInstance]addBundelURL:[self sourcePath]];
    }
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                             moduleName:[self moduleName]
                                      initialProperties:self.pageParams];
[self.view addSubview:rootView];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沛鸵,一起剝皮案震驚了整個(gè)濱河市括勺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谒臼,老刑警劉巖朝刊,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蜈缤,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)冯挎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門底哥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人房官,你說(shuō)我怎么就攤上這事趾徽。” “怎么了翰守?”我有些...
    開(kāi)封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵孵奶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蜡峰,道長(zhǎng)了袁,這世上最難降的妖魔是什么朗恳? 我笑而不...
    開(kāi)封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮载绿,結(jié)果婚禮上粥诫,老公的妹妹穿的比我還像新娘。我一直安慰自己崭庸,他們只是感情好怀浆,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著怕享,像睡著了一般执赡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上函筋,一...
    開(kāi)封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天沙合,我揣著相機(jī)與錄音,去河邊找鬼驻呐。 笑死灌诅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的含末。 我是一名探鬼主播猜拾,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼佣盒!你這毒婦竟也來(lái)了挎袜?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肥惭,失蹤者是張志新(化名)和其女友劉穎盯仪,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蜜葱,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡全景,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牵囤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片爸黄。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖揭鳞,靈堂內(nèi)的尸體忽然破棺而出炕贵,到底是詐尸還是另有隱情,我是刑警寧澤野崇,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布称开,位于F島的核電站,受9級(jí)特大地震影響乓梨,放射性物質(zhì)發(fā)生泄漏鳖轰。R本人自食惡果不足惜清酥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脆霎。 院中可真熱鬧总处,春花似錦、人聲如沸睛蛛。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忆肾。三九已至荸频,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間客冈,已是汗流浹背旭从。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留场仲,地道東北人和悦。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像渠缕,于是被迫代替她去往敵國(guó)和親鸽素。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容