ReactNative系列之三十一业务bundle拆分及动态加载实例

暂时先放出思路,近期会做一期视频解析及源码下载:敬请关注

前言:此步骤完成ReactContext的动态加载与卸载、bundle的拆分、bundle的索引以字符串形式(默认为int形式)解耦~~

第3步之后的每一步将为大家从0开始一步步的去完成建立一个ReactNative项目的支持多业务并且,根据业务模块进行Bundle的灵活拆分,便于业务的灵活扩展~ 总体为common.bundle(1个基础模块)+business.Bundle(N个)。亮点是还可以完成当common.bundle升级后,不会对业务bundle造成影响。

bundle拆分加载

如果创建ReactNative项目失败,可以尝试以下1、2;

1.设置taobao国内镜像库路径

npm config set registry https://registry.npm.taobao.org

npm config set disturl https://npm.taobao.org/dist

2.升级NodeJs版本

sudo npm cache clean -f

sudo npm install -g n

sudo n stable

3.首先创建一个项目,版本为0.50

react-native init split --version 0.50.0

4.编写业务模块A、业务模块B的代码,分别引用common模块。

1)common模块中的类需要由一个总的索引文件进行import.

2))业务模块中的索引文件(就是打包的注册的启动js)需要在文件的第一行引入common中的索引文件

这样做的好处理:方便comm命令进行文件的差分处理。即保证两个文件重复的部分都是头部,否则可能会造成comm比对有部分行失误。

5.我们需要修改下源码,修改源码的目标在于(这块是重点):

1)改造项目,编译ReactNative源码

2)打开Bundle文件,我们会发现每一个__d(function(){}, moduleId), 都有这样的结构,moduleId默认类型为number型,每一个number都是唯一的,通过这个值来索引一个类,用于bundle文件中的内在关联。但是我们需要思考一个问题,当common.bundle的范围是0-100时,业务a为101-200,业务b为201-300时,我们需要解决,common.bundle的及业务a的文件变动。当一个底层的文件发生变动时,分配的moduleId就会有所改变,影响到上层的引用关联。就会出异常!所以我们可以这样做,把分配的moduleId,改变一下形如‘com.yeputiWK.business.Index.page’这样的串时,并且package+类名本身具有唯一性,这样就充分的在各bundle模块中的索引解耦。

从源代码编译ReactNative参考:https://reactnative.cn/docs/building-from-source/ 

编译过程中如果出错请参考:https://blog.csdn.net/yeputi1015/article/details/81487754

6.需要修改metro-bundle源码中的代码,修改3部分内容。

1)类中的require的类

2)各个类自身的索引

3)文件最最底部的,总体require入口

涉及到两个类的修改:

node_modules/metro-bundler/src/Bundler/Bundler.js   修改"3)"中的内容,涉及方法为:

_addRequireCall

node_modules/metro-bundler/src/Resolver/index.js  修改"1)、2)"中的内容,涉及方法为:

resolveRequires   、   wrapModule  及  defineModuleCode

7.打包、拆分包、差分包可以编写一个sh批处理文件,如下:

#------------------
#   echo_color
#   对于一些特殊操作进行颜色提示
#------------------
echoc()
{
    echo -e "\033[36m $1 \033[0m"
}

#-------------------------------
#   echo_error
#   执行过程中产生错误的的信息提示
#-------------------------------
echoe()
{
    echo -e "\033[31m $1 \033[0m"
}

echoc "rn.sh react_native_bundle"
echoc "生成common.bundle"
react-native bundle --platform android --dev false --entry-file common/index.js  --bundle-output android/app/src/main/assets/common.bundle  --assets-dest android/app/src/main/res/
echoc "生成subA.bundle"
react-native bundle --platform android --dev false --entry-file subA/subA.js  --bundle-output android/app/src/main/assets/subA.bundle  --assets-dest android/app/src/main/res/
echoc "生成subB.bundle"
react-native bundle --platform android --dev false --entry-file subB/subB.js  --bundle-output android/app/src/main/assets/subB.bundle  --assets-dest android/app/src/main/res/
echoc "生成subC.bundle"
react-native bundle --platform android --dev false --entry-file subC/subC.js  --bundle-output android/app/src/main/assets/subC.bundle  --assets-dest android/app/src/main/res/
echoc "生成差分subA_.bundle文件"
comm -2 -3 android/app/src/main/assets/subA.bundle android/app/src/main/assets/common.bundle > android/app/src/main/assets/subA_.bundle
echoc "生成差分subB_.bundle文件"
comm -2 -3 android/app/src/main/assets/subB.bundle android/app/src/main/assets/common.bundle > android/app/src/main/assets/subB_.bundle
echoc "生成差分subC_.bundle文件"
comm -2 -3 android/app/src/main/assets/subC.bundle android/app/src/main/assets/common.bundle > android/app/src/main/assets/subC_.bundle

8.动态加载和卸载ReactContext部分

1)程序依然需要继承ReactApplication,并且需要返回ReactNativeHost,host中指定common.bundle

2)在Activity中可通过获取Application中的host去手动加载ReactContext,实际上加载了common.bundle:

((ReactApplication)getApplication()).getReactNativeHost().getReactInstanceManager().createReactContextInBackground();

3)卸载ReactContext

((ReactApplication)getApplication()).getReactNativeHost().getReactInstanceManager().destroy();

4)子业务的加载,当然可以跟据业务需求手动加载,这里我在common.bundle加载成功后的listener里加载所有的业务bundle:

private void createContext() {
    Log.e(TAG, "wk start createContext");
    SoLoader.init(this, /* native exopackage */ false);
    final ReactInstanceManager manager = ((ReactApplication)getApplication()).getReactNativeHost().getReactInstanceManager();
    if (!manager.hasStartedCreatingInitialContext()) {
        manager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
            @Override
            public void onReactContextInitialized(ReactContext context) {
                onContextInitialized();
            }
        });
        ((ReactApplication)getApplication()).getReactNativeHost().getReactInstanceManager().createReactContextInBackground();
    }
}

private void onContextInitialized() {
    text.setText(loaded);
    Toast.makeText(getApplicationContext(), "ReactContext初始化完毕", Toast.LENGTH_LONG).show();
    Log.e(TAG, "wk onContextInitialized");
    loadBundleFromAssets("subA_.bundle");  // 加载子业务bundleA
    loadBundleFromAssets("subB_.bundle");  // 加载子业务bundleB
    loadBundleFromAssets("subC_.bundle");  // 加载子业务bundleC
}

private void loadBundleFromAssets(String bundlepath) {
    String source = "assets://" + bundlepath;
    Log.e("RNN", "wk loadScriptFromAsset:"+source);
    try {
        Method method = CatalystInstanceImpl.class.getDeclaredMethod("loadScriptFromAssets",
                AssetManager.class,
                String.class,
                boolean.class);
        method.setAccessible(true);
        method.invoke(((ReactApplication)getApplication()).getReactNativeHost().getReactInstanceManager().getCurrentReactContext().getCatalystInstance(), this.getAssets(), source, false);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

9)图片资源的处理,在使用打包命令时。被将js中引用到的图片,放入res目录下,并重命名(跟据的规则为包名+文件名,包名间以下划线分隔)。可查看源码中的resolveAssetSource.js了解相关的加载路径的识别

注:关于后续工作,当bundle被拆分后,还需要修改调试模式下相关的bundle加载路径,还需要修改相关的源码~~不过改动都不大,主要是改相应的路径~

猜你喜欢

转载自blog.csdn.net/yeputi1015/article/details/81476369