美文网首页
React Native白屏优化

React Native白屏优化

作者: 翰墨_42a6 | 来源:发表于2018-10-23 15:50 被阅读0次

    本文针对使用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];
    

    相关文章

      网友评论

          本文标题:React Native白屏优化

          本文链接:https://www.haomeiwen.com/subject/dkxazftx.html