暂时先放出思路,近期会做一期视频解析及源码下载:敬请关注
前言:此步骤完成ReactContext的动态加载与卸载、bundle的拆分、bundle的索引以字符串形式(默认为int形式)解耦~~
第3步之后的每一步将为大家从0开始一步步的去完成建立一个ReactNative项目的支持多业务并且,根据业务模块进行Bundle的灵活拆分,便于业务的灵活扩展~ 总体为common.bundle(1个基础模块)+business.Bundle(N个)。亮点是还可以完成当common.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加载路径,还需要修改相关的源码~~不过改动都不大,主要是改相应的路径~