RN拆包解决方案(二) bundle加载

2,517 阅读2分钟

前言

本文跟随上一篇RN拆包解决方案(一) bundle拆分,已打出common.bundle和patch.bundle文件为前提,引入工程项目中

iOS原生加载流程

RCTBridge开放私有方法

@interface RCTBridge (ReactExecuteScript)

- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync;

@end

预先加载common包

在App项目完全启动完成后,可预先加载通用包,缓存到内存中

jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"common" withExtension:@"jsbundle"];
bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation
                             moduleProvider:nil
                              launchOptions:launchOptions];

加载业务包

当用户跳转路由到某个RN页面时加载patch部分

NSURL *jsCodeLocationBiz = [[NSBundle mainBundle] URLForResource:@"patch" withExtension:@"jsbundle"];
NSError *error = nil;
NSData *sourceBuz = [NSData dataWithContentsOfFile:jsCodeLocationBiz.path
                                         options:NSDataReadingMappedIfSafe
                                           error:&error];
[bridge.batchedBridge executeSourceCode:sourceBuz sync:NO];

需要注意的是,加载业务包的流程必须在common完全加载完毕后执行,否则会出现加载异常;由于没有提供加载完成的回调,只能通过侦听isLoading状态变化来处理

dispatch_async(dispatch_get_global_queue(0, 0), ^{
  //加载common
  
  while (bridge.isLoading) {//侦听common加载完成
  }
  
  //...
  //加载业务包
});

绑定到视图RCTRootView

RCTRootView *view = [[RCTRootView alloc] initWithBridge:bridge moduleName:moduleName initialProperties:nil];

由此可见,加载common和patch都在同一个RCTBridge容器中执行,最终绑定到RCTRootView展示

Android原生加载流程

使用ReactInstanceManager加载基础包

首先需要初始化RN的运行环境。加载common使公共的模块代码优先执行,不会涉及视图的绑定渲染

jsbundleFile = "assets://common.bundle";
final ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
                .setApplication(application)
                .addPackage(new MainReactPackage())
                .setJSBundleFile(jsbundleFile);
ReactInstanceManager manager = builder.build();

按需加载业务包

通过ReactInstanceManager获取CatalystInstance实例,此实例负责继续加载业务包

ReactContext context = manager.getCurrentReactContext();
CatalystInstance instance = context.getCatalystInstance();

String source = "assets://patch.bundle";
((CatalystInstanceImpl)instance).loadScriptFromAssets(context.getAssets(), source,loadSynchronously);

同理,加载业务包需要在common加载完成后执行

//加载common包
//...
if (!manager.hasStartedCreatingInitialContext()) {
    manager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
        @Override
        public void onReactContextInitialized(ReactContext context) {
            manager.removeReactInstanceEventListener(this);
            //...
            //加载业务包
        }
    });
    manager.createReactContextInBackground();
}

绑定到视图

当用户进入RN页面时在activity层创建并显示

reactRootView = new ReactRootView(this);
reactRootView.startReactApplication(manager, moduleName, null);
setContentView(reactRootView);

总结

RN拆包流程,最终还是要归功于 RN 基于 javascript 设计的灵活性。分步的执行方式能够让我们轻松的将 Bundle 的加载、视图的渲染分步进行,互不影响;