React Native白屏优化

本文针对使用React Native开发混合应用的过程中iOS端白屏问题,提出了react-native预加载优化方案,本文主要围绕以下几个方面展开分析:

  • 白屏
  • 解决白屏

本文react-native基于0.48.0版本

白屏

在开发React-Native(下面简称RN)页面的时候,都会看到下面加载白屏现象


这个白屏时间段RN框架在做什么勒!!通过对RN源码的跟踪,发现这期间端RN做了很多事。
实例化各个module-->获取JSBundle-->创建JS运行环境-->运行JSBundle
其实在开发中上面步骤RN让开发者就用一句代码就实现了,就是实例化RCTBridge过程,这个过程也就是官方耗时图所示的JS init+Require。花费时间最多的地方

 NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"业务全包名"
                                                   withExtension:@"jsbundle"];
 RCTBridge *bridge = [[RCTBridge alloc]initWithBundleURL:bridgeURL
                                                 moduleProvider:nil
                                                  launchOptions:nil];

解决白屏

上面描述了白屏产生过程,接下来讲讲怎么解决这个问题。

1. 实现简介

简单思路就是不要在进入RN页面的时候才去实例化RCTBridge,提前初始化好后缓存下来,打开页面创建RootView的时候使用已经创建好了的RCTBridge,这样会直接跳过JS init+Require过程。这就是所谓的以空间换时间做法,但是具体做法很很多中情况,下面来看看各种情况具体实现

2. 具体实现

首先我们来看看正常情况怎么创建一个RootView的

//步骤一:创建bridge
NSURL *bridgeURL = [[NSBundle mainBundle] URLForResource:@"包名字"
                                                   withExtension:@"jsbundle"];
RCTBridge *bridge = [[RCTBridge alloc]initWithBundleURL:bridgeURL
                                                 moduleProvider:nil
                                                  launchOptions:nil];
//步骤二:创建rootView
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                             moduleName:[self moduleName]
                                      initialProperties:self.pageParams];
//步骤三:rootView添加到需要显示的父View中
[self.view addSubview:rootView];
情况一:你的APP中就一个RN bundle包(全部缓存)

在你的项目中仅仅有一个jsbundle包,由于只有only 唯一的包,我们在APP一启动的时候就实现步骤一创建bridge(提前缓存),我们这用个单例来保存这个bridge。
1:创建一个单例:

#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 中实例化这个单例

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //放在最前面哟,尽早实例化
    [BridgeManage sharedInstance];
    //其它程序启动处理
    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 实例化需要尽早,如果不第一时间实例化,进入RN页面的时候才调用[BridgeManage sharedInstance]。在第一次进入RN页面的时候也会出现白屏。

情况二:你的APP中有多个RN bundle包

如果APP中是分业务模块开发的,就会出现多个jsbundle包。这个时候就不能将这些jsbundle的Bridge实例后缓存下来,会出现内存过大,浪费用户不用的模块占用手机内存。而且有些模块是通过网络动态下发到APP中的这是无法提前缓存的。一般大家都是用到对应jsbundle的时候创建Rootview实时创建Bridge(当然会出现白屏)。那这种情况怎么优化呢???
首先我们可以看看官方提供的打包shell命令打包出来的jsbundle结构
命令如下:

//官方打包命令
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后面的数字是模块的id,是在RN打包过程中,解析依赖关系,自增长生成。__d结构:

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

一个js文件就一片段__d定义,一个图片也会用__d定义

针对不同模块的入口js文件打包,将生成不同jsbundle对比,可以发现:
  • jsbundle的头部相同
  • 中部很多模块的定义存在大量重复
  • 如果模块js中AppRegistry.registerComponent,尾部的入口模块id基本相同,如上例中的require(11)
    实际上头部和中部重复的模块占用了500K的大小(RN框架js),每个入口js生成的jsbundle都会包含这500K代码。

了解了打包后的jsbundle结构后,出现了一种优化猜想,可不可以各个模块的RootView 公用这些公共的JS代码(在一个JS内核中都可以相互调用),通过尝试发现是可行的。下面说说具体实现。

1.先把RN架构核心库js和业务代码js 想办法分离

分离方法见:ReactNaive分包方法

这是分包加载的第一步,分包出来的包名字我们这样定义core.ios.jsbundle (RN 系统js)和 bussess.ios.jsbundle (RN 各业务js)和 common.ios.jsbundle(RN各业务公共的js)

2.单例实例化核心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加载本地资源图片的根目录哟!!!
//        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,于是定义一个extend 扩展类RCTBridge+ReactExecuteScript.h如下:

#import <React/RCTBridge.h>

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

3:AppDelegate 中实例化这个BridgeManage并向core Bridge中注入common.ios.jsbundle 业务公共js

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //放在最前面哟,尽早实例化
    [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];
    //其它程序启动处理
    return YES;
}

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

推荐阅读更多精彩内容