React Native Bundle Split

使用rn-packager拆分react-native的jsbundle(core.android.bundle + business.android.bundle),然后在程序启动时分步加载拆分后的bundle,以达到热更新目的,注:本文档使用的react native版本为0.43

0x00 分步加载jsbundle

将rn-packager打包生成的jsbundle+图片资源统一放到assets目录中,应用程序启动时,复制到files目录,只要保持目录结构不变,js就可以正常访问图片资源。故而,如果需要热更新jsbundle和图片资源时,只需要直接更新files目录中的图片和jsbundle文件即可,具体可以看packager-bundle-split

  • 加载core.android.bundle

    ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
      .setApplication(mApplication)
      .setJSMainModuleName(getJSMainModuleName())
      .setUseDeveloperSupport(getUseDeveloperSupport())
      .setRedBoxHandler(getRedBoxHandler())
      .setUIImplementationProvider(getUIImplementationProvider())
      .setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
    
    for (ReactPackage reactPackage : getPackages()) {
      builder.addPackage(reactPackage);
    }
    
    String jsBundleFile = getJSBundleFile();
    if (jsBundleFile != null) {
      builder.setJSBundleFile(jsBundleFile);
    } else {
      builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
    }
    
    File coreBundleFile = new File(mApplication.getFilesDir(), "rn/core.android.bundle");
    if (!coreBundleFile.exists()) {
      Log.e("ReactNativeHost", "copy assets://core.android.bundle to " + coreBundleFile);
      AssetsUtils.copyFile(mApplication, "core.android.bundle", coreBundleFile.getAbsolutePath());
    }
    // 加载core.android.bundle
    builder.setJSBundleLoader(JSBundleLoader.createFileLoader(coreBundleFile.getAbsolutePath()));
    Log.e("ReactNativeHost", "set core bundle");
    return builder.build();
    
  • 加载business.android.bundle

    public void loadBussinessBundle(final File bundleFile) {
    if (mMethod_LoadScriptFile == null) {
      try {
        mMethod_LoadScriptFile = com.facebook.react.cxxbridge.CatalystInstanceImpl.class.getDeclaredMethod("loadScriptFromFile", new Class[]{String.class, String.class});
        mMethod_LoadScriptFile.setAccessible(true);
      } catch (NoSuchMethodException e) {
        Log.e("ReactNativeHost", "cannot found method: CatalystInstanceImpl.loadScriptFromFile(String, String)", e);
        return;
      }
    }
    
    CatalystInstance catalystInstance = mReactContext.getCatalystInstance();
    String businessBundlePath = bundleFile.getAbsolutePath();
    Log.e("ReactNativeHost", "loadBussinessBundle " + businessBundlePath + "...");
    try {
      mMethod_LoadScriptFile.invoke(catalystInstance, businessBundlePath, businessBundlePath);
      Log.e("ReactNativeHost", "loadBussinessBundle " + businessBundlePath + " done.");
    } catch (Throwable e) {
      Log.e("ReactNativeHost", "loadBussinessBundle " + businessBundlePath + " error.");
      Log.e("ReactNativeHost", "error invoke method: CatalystInstanceImpl.loadScriptFromFile(String, String)", e);
    }
    }
    
  • 热更新jsbundle/图片 假设从assets复制到files目录后,rn目录结构如下:

    files
    |--rn
    |--core.android.bundle
    |--business.android.bundle
    |--drawable-mdpi/image_liking.png
    

同时使用require的方式加载图片,

<Image source={require('./image/liking.png')}/>

如果需要热更新business.android.bundle或者image_liking.png,直接从服务器下载然后替换files/rn目录对应的资源,然后recreateReactContextInBackground()重新加载即可。

0x01 如何运行?

# 1. clone代码
git clone https://github.com/coofee/TestBundleSplit

# 2. 安装rn-packager依赖
cd rn-packager
npm install

# 3. 安装tests例子依赖
cd tests
npm install

# 4. 生成core.android.bundle and core.android.manifest.json
node ../bin/rnpackager bundle --entry-file node_modules/react-native/Libraries/react-native/react-native.js --bundle-output assets/core.android.bundle --platform android --dev false --assets-dest assets --manifest-output assets/core.android.manifest.json

# 5. 使用core.android.manifest.json生成app.bundle
node ../bin/rnpackager bundle --entry-file index.js --bundle-output assets/HelloWorldApp.android.bundle --platform android --dev false --assets-dest assets --manifest-file assets/core.android.manifest.json 

# 6. 复制core.android.bundle和HelloWorldApp.android.bundle到app/src/assets.
cp assets/core.android.bundle ../../android/app/src/main/assets/core.android.bundle

cp assets/HelloWorldApp.android.bundle ../../android/app/src/main/assets/HelloWorldApp.android.bundle

# 7. 安装android app
cd ../../android
# mac/linux执行安装app.
./gradlew :app:installDebug
# windows执行安装app.
./gradlew.bat :app:installDebug

0x02 Libraries